add exceptions to prelude functions

This commit is contained in:
Chris Wanstrath 2025-10-29 15:36:18 -07:00
parent 4f961d3039
commit f8d2236292
5 changed files with 54 additions and 12 deletions

View File

@ -39,7 +39,7 @@ export const globals = {
switch (value.type) { switch (value.type) {
case 'string': case 'array': return value.value.length case 'string': case 'array': return value.value.length
case 'dict': return value.value.size case 'dict': return value.value.size
default: return 0 default: throw new Error(`length: expected string, array, or dict, got ${value.type}`)
} }
}, },
@ -65,7 +65,24 @@ export const globals = {
identity: (v: any) => v, identity: (v: any) => v,
// collections // collections
at: (collection: any, index: number | string) => collection[index], at: (collection: any, index: number | string) => {
const value = toValue(collection)
if (value.type === 'string' || value.type === 'array') {
const idx = typeof index === 'number' ? index : parseInt(index as string)
if (idx < 0 || idx >= value.value.length) {
throw new Error(`at: index ${idx} out of bounds for ${value.type} of length ${value.value.length}`)
}
return value.value[idx]
} else if (value.type === 'dict') {
const key = String(index)
if (!value.value.has(key)) {
throw new Error(`at: key '${key}' not found in dict`)
}
return value.value.get(key)
} else {
throw new Error(`at: expected string, array, or dict, got ${value.type}`)
}
},
range: (start: number, end: number | null) => { range: (start: number, end: number | null) => {
if (end === null) { if (end === null) {
end = start end = start

View File

@ -52,8 +52,14 @@ export const list = {
first: (list: any[]) => list[0] ?? null, first: (list: any[]) => list[0] ?? null,
last: (list: any[]) => list[list.length - 1] ?? null, last: (list: any[]) => list[list.length - 1] ?? null,
rest: (list: any[]) => list.slice(1), rest: (list: any[]) => list.slice(1),
take: (list: any[], n: number) => list.slice(0, n), take: (list: any[], n: number) => {
drop: (list: any[], n: number) => list.slice(n), if (n < 0) throw new Error(`take: count must be non-negative, got ${n}`)
return list.slice(0, n)
},
drop: (list: any[], n: number) => {
if (n < 0) throw new Error(`drop: count must be non-negative, got ${n}`)
return list.slice(n)
},
append: (list: any[], item: any) => [...list, item], append: (list: any[], item: any) => [...list, item],
prepend: (list: any[], item: any) => [item, ...list], prepend: (list: any[], item: any) => [item, ...list],
'index-of': (list: any[], item: any) => list.indexOf(item), 'index-of': (list: any[], item: any) => list.indexOf(item),

View File

@ -3,12 +3,24 @@ export const math = {
floor: (n: number) => Math.floor(n), floor: (n: number) => Math.floor(n),
ceil: (n: number) => Math.ceil(n), ceil: (n: number) => Math.ceil(n),
round: (n: number) => Math.round(n), round: (n: number) => Math.round(n),
min: (...nums: number[]) => Math.min(...nums), min: (...nums: number[]) => {
max: (...nums: number[]) => Math.max(...nums), if (nums.length === 0) throw new Error('min: expected at least one argument')
return Math.min(...nums)
},
max: (...nums: number[]) => {
if (nums.length === 0) throw new Error('max: expected at least one argument')
return Math.max(...nums)
},
pow: (base: number, exp: number) => Math.pow(base, exp), pow: (base: number, exp: number) => Math.pow(base, exp),
sqrt: (n: number) => Math.sqrt(n), sqrt: (n: number) => {
if (n < 0) throw new Error(`sqrt: cannot take square root of negative number ${n}`)
return Math.sqrt(n)
},
random: () => Math.random(), random: () => Math.random(),
clamp: (n: number, min: number, max: number) => Math.min(Math.max(n, min), max), clamp: (n: number, min: number, max: number) => {
if (min > max) throw new Error(`clamp: min (${min}) must be less than or equal to max (${max})`)
return Math.min(Math.max(n, min), max)
},
sign: (n: number) => Math.sign(n), sign: (n: number) => Math.sign(n),
trunc: (n: number) => Math.trunc(n), trunc: (n: number) => Math.trunc(n),

View File

@ -21,7 +21,11 @@ export const str = {
'replace-all': (str: string, search: string, replacement: string) => str.replaceAll(search, replacement), 'replace-all': (str: string, search: string, replacement: string) => str.replaceAll(search, replacement),
slice: (str: string, start: number, end?: number | null) => str.slice(start, end ?? undefined), slice: (str: string, start: number, end?: number | null) => str.slice(start, end ?? undefined),
substring: (str: string, start: number, end?: number | null) => str.substring(start, end ?? undefined), substring: (str: string, start: number, end?: number | null) => str.substring(start, end ?? undefined),
repeat: (str: string, count: number) => str.repeat(count), repeat: (str: string, count: number) => {
if (count < 0) throw new Error(`repeat: count must be non-negative, got ${count}`)
if (!Number.isInteger(count)) throw new Error(`repeat: count must be an integer, got ${count}`)
return str.repeat(count)
},
'pad-start': (str: string, length: number, pad: string = ' ') => str.padStart(length, pad), 'pad-start': (str: string, length: number, pad: string = ' ') => str.padStart(length, pad),
'pad-end': (str: string, length: number, pad: string = ' ') => str.padEnd(length, pad), 'pad-end': (str: string, length: number, pad: string = ' ') => str.padEnd(length, pad),
lines: (str: string) => str.split('\n'), lines: (str: string) => str.split('\n'),

View File

@ -176,9 +176,12 @@ describe('introspection', () => {
await expect(`length 'hello'`).toEvaluateTo(5, globals) await expect(`length 'hello'`).toEvaluateTo(5, globals)
await expect(`length [1 2 3]`).toEvaluateTo(3, globals) await expect(`length [1 2 3]`).toEvaluateTo(3, globals)
await expect(`length [a=1 b=2]`).toEvaluateTo(2, globals) await expect(`length [a=1 b=2]`).toEvaluateTo(2, globals)
await expect(`length 42`).toEvaluateTo(0, globals) })
await expect(`length true`).toEvaluateTo(0, globals)
await expect(`length null`).toEvaluateTo(0, globals) test('length throws on invalid types', async () => {
await expect(`try: length 42 catch e: 'error' end`).toEvaluateTo('error', globals)
await expect(`try: length true catch e: 'error' end`).toEvaluateTo('error', globals)
await expect(`try: length null catch e: 'error' end`).toEvaluateTo('error', globals)
}) })
test('inspect formats values', async () => { test('inspect formats values', async () => {