From 83fad9a68f327f6edde7949cba29263e3a090cb3 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 29 Oct 2025 15:04:23 -0700 Subject: [PATCH 1/4] fix repl error recovery --- bin/repl | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) 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() From cc8d64b3ec462ed6fe4fb4969a91f9bda3808f68 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 29 Oct 2025 15:39:42 -0700 Subject: [PATCH 2/4] update reef --- bun.lock | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index 005ca60..ecc1249 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#0b5d3e634cb07f316d3b119e54bfb8b196d8a26f", { "peerDependencies": { "typescript": "^5" } }, "0b5d3e634cb07f316d3b119e54bfb8b196d8a26f"], "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", From 2abf3558d508dfed9895963518dd446ab8f56833 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 29 Oct 2025 16:03:59 -0700 Subject: [PATCH 3/4] yay --- bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index ecc1249..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#0b5d3e634cb07f316d3b119e54bfb8b196d8a26f", { "peerDependencies": { "typescript": "^5" } }, "0b5d3e634cb07f316d3b119e54bfb8b196d8a26f"], + "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=="], From 789481f4ef7b855ec59b8b03def9b351c81f9465 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 29 Oct 2025 19:13:03 -0700 Subject: [PATCH 4/4] [a b] = [1 2 3] --- src/compiler/compiler.ts | 28 ++++++++++++++++++--- src/compiler/tests/compiler.test.ts | 22 +++++++++++++++++ src/compiler/utils.ts | 16 +++++++++--- src/parser/shrimp.grammar | 2 +- src/parser/shrimp.ts | 8 +++--- src/parser/tests/basics.test.ts | 38 +++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 72b0f23..7581f17 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -235,11 +235,31 @@ export class Compiler { } case terms.Assign: { - const { identifier, right } = getAssignmentParts(node) + const assignParts = getAssignmentParts(node) const instructions: ProgramItem[] = [] - instructions.push(...this.#compileNode(right, input)) - instructions.push(['DUP']) // Keep a copy on the stack after storing - const identifierName = input.slice(identifier.from, identifier.to) + + // right-hand side + instructions.push(...this.#compileNode(assignParts.right, input)) + + // array destructuring: [ a b ] = [ 1 2 3 4 ] + if ('arrayPattern' in assignParts) { + const identifiers = assignParts.arrayPattern ?? [] + if (identifiers.length === 0) return instructions + + for (let i = 0; i < identifiers.length; i++) { + instructions.push(['DUP']) + instructions.push(['PUSH', i]) + instructions.push(['DOT_GET']) + instructions.push(['STORE', input.slice(identifiers[i]!.from, identifiers[i]!.to)]) + } + + // original array still on stack as the return value + return instructions + } + + // simple assignment: x = value + instructions.push(['DUP']) + const identifierName = input.slice(assignParts.identifier.from, assignParts.identifier.to) instructions.push(['STORE', identifierName]) return instructions diff --git a/src/compiler/tests/compiler.test.ts b/src/compiler/tests/compiler.test.ts index 4d662c3..2f5dcac 100644 --- a/src/compiler/tests/compiler.test.ts +++ b/src/compiler/tests/compiler.test.ts @@ -64,6 +64,28 @@ describe('compiler', () => { expect('sum = 2 + 3; sum').toEvaluateTo(5) }) + test('array destructuring with two variables', () => { + expect('[ a b ] = [ 1 2 3 4 ]; a').toEvaluateTo(1) + expect('[ a b ] = [ 1 2 3 4 ]; b').toEvaluateTo(2) + }) + + test('array destructuring with one variable', () => { + expect('[ x ] = [ 42 ]; x').toEvaluateTo(42) + }) + + test('array destructuring with missing elements assigns null', () => { + expect('[ a b c ] = [ 1 2 ]; c').toEvaluateTo(null) + }) + + test('array destructuring returns the original array', () => { + expect('[ a b ] = [ 1 2 3 4 ]').toEvaluateTo([1, 2, 3, 4]) + }) + + test('array destructuring with emoji identifiers', () => { + expect('[ 🚀 💎 ] = [ 1 2 ]; 🚀').toEvaluateTo(1) + expect('[ 🚀 💎 ] = [ 1 2 ]; 💎').toEvaluateTo(2) + }) + test('parentheses', () => { expect('(2 + 3) * 4').toEvaluateTo(20) }) diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts index 82b4025..99b56c6 100644 --- a/src/compiler/utils.ts +++ b/src/compiler/utils.ts @@ -40,15 +40,23 @@ export const getAssignmentParts = (node: SyntaxNode) => { const children = getAllChildren(node) const [left, equals, right] = children - if (!left || left.type.id !== terms.AssignableIdentifier) { + if (!equals || !right) { throw new CompilerError( - `Assign left child must be an AssignableIdentifier, got ${left ? left.type.name : 'none'}`, + `Assign expected 3 children, got ${children.length}`, node.from, node.to ) - } else if (!equals || !right) { + } + + // array destructuring + if (left && left.type.id === terms.Array) { + const identifiers = getAllChildren(left).filter(child => child.type.id === terms.Identifier) + return { arrayPattern: identifiers, right } + } + + if (!left || left.type.id !== terms.AssignableIdentifier) { throw new CompilerError( - `Assign expected 3 children, got ${children.length}`, + `Assign left child must be an AssignableIdentifier or Array, got ${left ? left.type.name : 'none'}`, node.from, node.to ) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index 635035a..01c82f0 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -148,7 +148,7 @@ Params { } Assign { - AssignableIdentifier Eq consumeToTerminator + (AssignableIdentifier | Array) Eq consumeToTerminator } BinOp { diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 6e19a7f..e9ba516 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -7,9 +7,9 @@ import {highlighting} from "./highlight" const spec_Identifier = {__proto__:null,end:80, null:86, if:96, elseif:104, else:108} export const parser = LRParser.deserialize({ version: 14, - states: "3UQYQbOOO#hQcO'#CvO$eOSO'#CxO$sQbO'#EVOOQ`'#DR'#DROOQa'#DO'#DOO%vQbO'#DWO&{QcO'#DzOOQa'#Dz'#DzO)PQcO'#DyO)xQRO'#CwO*]QcO'#DuO*tQcO'#DuO+VQbO'#CuO+}OpO'#CsOOQ`'#Dv'#DvO,SQbO'#DuO,bQbO'#E]OOQ`'#D]'#D]O-VQRO'#DeOOQ`'#Du'#DuO-[QQO'#DtOOQ`'#Dt'#DtOOQ`'#Df'#DfQYQbOOO-dQbO'#DPOOQa'#Dy'#DyOOQ`'#DZ'#DZOOQ`'#E['#E[OOQ`'#Dm'#DmO-nQbO,59^O.RQbO'#CzO.ZQWO'#C{OOOO'#D|'#D|OOOO'#Dg'#DgO.oOSO,59dOOQa,59d,59dOOQ`'#Di'#DiO.}QbO'#DSO/VQQO,5:qOOQ`'#Dh'#DhO/[QbO,59rO/cQQO,59jOOQa,59r,59rO/nQbO,59rO,bQbO,59cO,bQbO,59cO,bQbO,59cO,bQbO,59tO,bQbO,59tO,bQbO,59tO/xQRO,59aO0PQRO,59aO0bQRO,59aO0]QQO,59aO0mQQO,59aO0uObO,59_O1QQbO'#DnO1]QbO,59]O1nQRO,5:wO1uQRO,5:wO2QQbO,5:POOQ`,5:`,5:`OOQ`-E7d-E7dOOQ`,59k,59kOOQ`-E7k-E7kOOOO,59f,59fOOOO,59g,59gOOOO-E7e-E7eOOQa1G/O1G/OOOQ`-E7g-E7gO2[QbO1G0]OOQ`-E7f-E7fO2iQQO1G/UOOQa1G/^1G/^O2tQbO1G/^OOQO'#Dk'#DkO2iQQO1G/UOOQa1G/U1G/UOOQ`'#Dl'#DlO2tQbO1G/^OOQa1G.}1G.}O3gQcO1G.}O3qQcO1G.}O3{QcO1G.}OOQa1G/`1G/`O5_QcO1G/`O5fQcO1G/`O5mQcO1G/`OOQa1G.{1G.{OOQa1G.y1G.yO!ZQbO'#CvO%}QbO'#CrOOQ`,5:Y,5:YOOQ`-E7l-E7lO5tQbO1G0cOOQ`1G/k1G/kO6RQbO7+%wO6WQbO7+%xO6hQQO7+$pOOQa7+$p7+$pO6sQbO7+$xOOQa7+$x7+$xOOQO-E7i-E7iOOQ`-E7j-E7jOOQ`'#D_'#D_O6}QbO7+%}O7SQbO7+&OOOQ`< (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 15, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 1135 + tokenPrec: 1164 }) diff --git a/src/parser/tests/basics.test.ts b/src/parser/tests/basics.test.ts index da6d4bb..b9584ad 100644 --- a/src/parser/tests/basics.test.ts +++ b/src/parser/tests/basics.test.ts @@ -594,6 +594,44 @@ describe('Comments', () => { }) }) +describe('Array destructuring', () => { + test('parses array pattern with two variables', () => { + expect('[ a b ] = [ 1 2 3 4]').toMatchTree(` + Assign + Array + Identifier a + Identifier b + Eq = + Array + Number 1 + Number 2 + Number 3 + Number 4`) + }) + + test('parses array pattern with one variable', () => { + expect('[ x ] = [ 42 ]').toMatchTree(` + Assign + Array + Identifier x + Eq = + Array + Number 42`) + }) + + test('parses array pattern with emoji identifiers', () => { + expect('[ 🚀 💎 ] = [ 1 2 ]').toMatchTree(` + Assign + Array + Identifier 🚀 + Identifier 💎 + Eq = + Array + Number 1 + Number 2`) + }) +}) + describe('Conditional ops', () => { test('or can be chained', () => { expect(`