diff --git a/bin/repl b/bin/repl index 052f481..c6ff98c 100755 --- a/bin/repl +++ b/bin/repl @@ -144,19 +144,33 @@ async function repl() { return } - codeHistory.push(trimmed) - try { const compiler = new Compiler(trimmed, Object.keys(globals)) - vm.appendBytecode(compiler.bytecode) + // Save VM state before appending bytecode, in case execution fails + const savedInstructions = [...vm.instructions] + const savedConstants = [...vm.constants] + const savedPc = vm.pc + const savedScope = vm.scope + const savedStopped = vm.stopped - const result = await vm.continue() + try { + vm.appendBytecode(compiler.bytecode) + const result = await vm.continue() - console.log(`${colors.dim}=>${colors.reset} ${formatValue(result)}`) + codeHistory.push(trimmed) + console.log(`${colors.dim}=>${colors.reset} ${formatValue(result)}`) + } catch (error: any) { + vm.instructions = savedInstructions + vm.constants = savedConstants + vm.pc = savedPc + vm.scope = savedScope + vm.stopped = savedStopped + + console.log(`\n${colors.red}Error:${colors.reset} ${error.message}`) + } } catch (error: any) { console.log(`\n${colors.red}Error:${colors.reset} ${error.message}`) - codeHistory.pop() } rl.prompt() diff --git a/bun.lock b/bun.lock index 005ca60..afb8aaa 100644 --- a/bun.lock +++ b/bun.lock @@ -62,7 +62,7 @@ "hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="], - "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#030eb7487165b3ba502965a8b7fa09c4b5fdb0da", { "peerDependencies": { "typescript": "^5" } }, "030eb7487165b3ba502965a8b7fa09c4b5fdb0da"], + "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#c69b172c78853756ec8acba5bc33d93eb6a571c6", { "peerDependencies": { "typescript": "^5" } }, "c69b172c78853756ec8acba5bc33d93eb6a571c6"], "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], diff --git a/package.json b/package.json index 2ac8e0c..cdf55cc 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev": "bun generate-parser && bun --hot src/server/server.tsx", "generate-parser": "lezer-generator src/parser/shrimp.grammar --typeScript -o src/parser/shrimp.ts", "repl": "bun generate-parser && bun bin/repl", - "update-reef": "cd packages/ReefVM && git pull origin main" + "update-reef": "rm -rf ~/.bun/install/cache/ && bun update reefvm" }, "dependencies": { "@codemirror/view": "^6.38.3", diff --git a/src/prelude/list.ts b/src/prelude/list.ts index eb013ef..ec38e23 100644 --- a/src/prelude/list.ts +++ b/src/prelude/list.ts @@ -1,3 +1,5 @@ +import { type Value, toValue, toNull } from 'reefvm' + export const list = { slice: (list: any[], start: number, end?: number) => list.slice(start, end), map: async (list: any[], cb: Function) => { @@ -40,9 +42,41 @@ export const list = { return true }, + // mutating + push: (list: Value, item: Value) => { + if (list.type !== 'array') return toNull() + return toValue(list.value.push(item)) + }, + pop: (list: Value) => { + if (list.type !== 'array') return toNull() + return toValue(list.value.pop()) + }, + shift: (list: Value) => { + if (list.type !== 'array') return toNull() + return toValue(list.value.shift()) + }, + unshift: (list: Value, item: Value) => { + if (list.type !== 'array') return toNull() + return toValue(list.value.unshift(item)) + }, + splice: (list: Value, start: Value, deleteCount: Value, ...items: Value[]) => { + const realList = list.value as any[] + const realStart = start.value as number + const realDeleteCount = deleteCount.value as number + const realItems = items.map(item => item.value) + return toValue(realList.splice(realStart, realDeleteCount, ...realItems)) + }, + // sequence operations reverse: (list: any[]) => list.slice().reverse(), - sort: (list: any[], cb?: (a: any, b: any) => number) => list.slice().sort(cb), + sort: async (list: any[], cb?: (a: any, b: any) => number) => { + const arr = [...list] + if (!cb) return arr.sort() + for (let i = 0; i < arr.length; i++) + for (let j = i + 1; j < arr.length; j++) + if ((await cb(arr[i], arr[j])) > 0) [arr[i], arr[j]] = [arr[j], arr[i]] + return arr + }, concat: (...lists: any[][]) => lists.flat(1), flatten: (list: any[], depth: number = 1) => list.flat(depth), unique: (list: any[]) => Array.from(new Set(list)), @@ -86,4 +120,13 @@ export const list = { } return groups }, -} \ No newline at end of file +} + + + // raw functions deal directly in Value types, meaning we can modify collection + // careful - they MUST return a Value! + ; (list.splice as any).raw = true + ; (list.push as any).raw = true + ; (list.pop as any).raw = true + ; (list.shift as any).raw = true + ; (list.unshift as any).raw = true \ No newline at end of file diff --git a/src/prelude/tests/prelude.test.ts b/src/prelude/tests/prelude.test.ts index ef7d8d6..f061e29 100644 --- a/src/prelude/tests/prelude.test.ts +++ b/src/prelude/tests/prelude.test.ts @@ -341,6 +341,84 @@ describe('collections', () => { await expect(`list.index-of [1 2 3] 5`).toEvaluateTo(-1, globals) }) + test('list.push adds to end and mutates array', async () => { + await expect(`arr = [1 2]; list.push arr 3; arr`).toEvaluateTo([1, 2, 3], globals) + }) + + test('list.push returns the size of the array', async () => { + await expect(`arr = [1 2]; arr | list.push 3`).toEvaluateTo(3, globals) + }) + + test('list.pop removes from end and mutates array', async () => { + await expect(`arr = [1 2 3]; list.pop arr; arr`).toEvaluateTo([1, 2], globals) + }) + + test('list.pop returns removed element', async () => { + await expect(`list.pop [1 2 3]`).toEvaluateTo(3, globals) + }) + + test('list.pop returns null for empty array', async () => { + await expect(`list.pop []`).toEvaluateTo(null, globals) + }) + + test('list.shift removes from start and mutates array', async () => { + await expect(`arr = [1 2 3]; list.shift arr; arr`).toEvaluateTo([2, 3], globals) + }) + + test('list.shift returns removed element', async () => { + await expect(`list.shift [1 2 3]`).toEvaluateTo(1, globals) + }) + + test('list.shift returns null for empty array', async () => { + await expect(`list.shift []`).toEvaluateTo(null, globals) + }) + + test('list.unshift adds to start and mutates array', async () => { + await expect(`arr = [2 3]; list.unshift arr 1; arr`).toEvaluateTo([1, 2, 3], globals) + }) + + test('list.unshift returns the length of the array', async () => { + await expect(`arr = [2 3]; arr | list.unshift 1`).toEvaluateTo(3, globals) + }) + + test('list.splice removes elements and mutates array', async () => { + await expect(`arr = [1 2 3 4 5]; list.splice arr 1 2; arr`).toEvaluateTo([1, 4, 5], globals) + }) + + test('list.splice returns removed elements', async () => { + await expect(`list.splice [1 2 3 4 5] 1 2`).toEvaluateTo([2, 3], globals) + }) + + test('list.splice from start', async () => { + await expect(`list.splice [1 2 3 4 5] 0 2`).toEvaluateTo([1, 2], globals) + }) + + test('list.splice to end', async () => { + await expect(`arr = [1 2 3 4 5]; list.splice arr 3 2; arr`).toEvaluateTo([1, 2, 3], globals) + }) + + test('list.sort with no callback sorts ascending', async () => { + await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5], globals) + }) + + test('list.sort with callback sorts using comparator', async () => { + await expect(` + desc = do a b: + b - a + end + list.sort [3 1 4 1 5] desc + `).toEvaluateTo([5, 4, 3, 1, 1], globals) + }) + + test('list.sort with callback for strings by length', async () => { + await expect(` + by-length = do a b: + (length a) - (length b) + end + list.sort ['cat' 'a' 'dog' 'elephant'] by-length + `).toEvaluateTo(['a', 'cat', 'dog', 'elephant'], globals) + }) + test('list.any? checks if any element matches', async () => { await expect(` gt-three = do x: x > 3 end