From d93ce851785816bb4b9b47714470f3fb961c4c10 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 12:20:12 -0800 Subject: [PATCH 1/8] split out prelude info tests --- src/prelude/tests/info.test.ts | 72 ++++++++++++++++++++++++++++++ src/prelude/tests/prelude.test.ts | 73 ------------------------------- 2 files changed, 72 insertions(+), 73 deletions(-) create mode 100644 src/prelude/tests/info.test.ts diff --git a/src/prelude/tests/info.test.ts b/src/prelude/tests/info.test.ts new file mode 100644 index 0000000..5a0b6fa --- /dev/null +++ b/src/prelude/tests/info.test.ts @@ -0,0 +1,72 @@ +import { expect, describe, test } from 'bun:test' +import { globals, colors } from '#prelude' + +describe('type predicates', () => { + test('string? checks for string type', async () => { + await expect(`string? 'hello'`).toEvaluateTo(true, globals) + await expect(`string? 42`).toEvaluateTo(false, globals) + }) + + test('number? checks for number type', async () => { + await expect(`number? 42`).toEvaluateTo(true, globals) + await expect(`number? 'hello'`).toEvaluateTo(false, globals) + }) + + test('boolean? checks for boolean type', async () => { + await expect(`boolean? true`).toEvaluateTo(true, globals) + await expect(`boolean? 42`).toEvaluateTo(false, globals) + }) + + test('array? checks for array type', async () => { + await expect(`array? [1 2 3]`).toEvaluateTo(true, globals) + await expect(`array? 42`).toEvaluateTo(false, globals) + }) + + test('dict? checks for dict type', async () => { + await expect(`dict? [a=1]`).toEvaluateTo(true, globals) + await expect(`dict? []`).toEvaluateTo(false, globals) + }) + + test('null? checks for null type', async () => { + await expect(`null? null`).toEvaluateTo(true, globals) + await expect(`null? 42`).toEvaluateTo(false, globals) + }) + + test('some? checks for non-null', async () => { + await expect(`some? 42`).toEvaluateTo(true, globals) + await expect(`some? null`).toEvaluateTo(false, globals) + }) +}) + + +describe('introspection', () => { + test('type returns proper types', async () => { + await expect(`type 'hello'`).toEvaluateTo('string', globals) + await expect(`type 42`).toEvaluateTo('number', globals) + await expect(`type true`).toEvaluateTo('boolean', globals) + await expect(`type false`).toEvaluateTo('boolean', globals) + await expect(`type null`).toEvaluateTo('null', globals) + await expect(`type [1 2 3]`).toEvaluateTo('array', globals) + await expect(`type [a=1 b=2]`).toEvaluateTo('dict', globals) + }) + + test('length', async () => { + await expect(`length 'hello'`).toEvaluateTo(5, globals) + await expect(`length [1 2 3]`).toEvaluateTo(3, globals) + await expect(`length [a=1 b=2]`).toEvaluateTo(2, 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 () => { + await expect(`inspect 'hello'`).toEvaluateTo("\u001b[32m'hello\u001b[32m'\u001b[0m", globals) + }) + + test('describe describes values', async () => { + await expect(`describe 'hello'`).toEvaluateTo("#", globals) + }) +}) diff --git a/src/prelude/tests/prelude.test.ts b/src/prelude/tests/prelude.test.ts index 40b7809..9429226 100644 --- a/src/prelude/tests/prelude.test.ts +++ b/src/prelude/tests/prelude.test.ts @@ -98,43 +98,6 @@ describe('string operations', () => { }) }) -describe('type predicates', () => { - test('string? checks for string type', async () => { - await expect(`string? 'hello'`).toEvaluateTo(true, globals) - await expect(`string? 42`).toEvaluateTo(false, globals) - }) - - test('number? checks for number type', async () => { - await expect(`number? 42`).toEvaluateTo(true, globals) - await expect(`number? 'hello'`).toEvaluateTo(false, globals) - }) - - test('boolean? checks for boolean type', async () => { - await expect(`boolean? true`).toEvaluateTo(true, globals) - await expect(`boolean? 42`).toEvaluateTo(false, globals) - }) - - test('array? checks for array type', async () => { - await expect(`array? [1 2 3]`).toEvaluateTo(true, globals) - await expect(`array? 42`).toEvaluateTo(false, globals) - }) - - test('dict? checks for dict type', async () => { - await expect(`dict? [a=1]`).toEvaluateTo(true, globals) - await expect(`dict? []`).toEvaluateTo(false, globals) - }) - - test('null? checks for null type', async () => { - await expect(`null? null`).toEvaluateTo(true, globals) - await expect(`null? 42`).toEvaluateTo(false, globals) - }) - - test('some? checks for non-null', async () => { - await expect(`some? 42`).toEvaluateTo(true, globals) - await expect(`some? null`).toEvaluateTo(false, globals) - }) -}) - describe('boolean logic', () => { test('not negates value', async () => { await expect(`not true`).toEvaluateTo(false, globals) @@ -161,42 +124,6 @@ describe('utilities', () => { }) }) -describe('introspection', () => { - test('type returns proper types', async () => { - await expect(`type 'hello'`).toEvaluateTo('string', globals) - await expect(`type 42`).toEvaluateTo('number', globals) - await expect(`type true`).toEvaluateTo('boolean', globals) - await expect(`type false`).toEvaluateTo('boolean', globals) - await expect(`type null`).toEvaluateTo('null', globals) - await expect(`type [1 2 3]`).toEvaluateTo('array', globals) - await expect(`type [a=1 b=2]`).toEvaluateTo('dict', globals) - }) - - test('length', async () => { - await expect(`length 'hello'`).toEvaluateTo(5, globals) - await expect(`length [1 2 3]`).toEvaluateTo(3, globals) - await expect(`length [a=1 b=2]`).toEvaluateTo(2, 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 () => { - // Just test that inspect returns something for now - // (we'd need more complex assertion to check the actual format) - await expect(`type (inspect 'hello')`).toEvaluateTo('string', globals) - }) - - test('describe describes values', async () => { - // Just test that inspect returns something for now - // (we'd need more complex assertion to check the actual format) - await expect(`describe 'hello'`).toEvaluateTo("#", globals) - }) -}) - describe('collections', () => { test('literal array creates array from arguments', async () => { await expect(`[ 1 2 3 ]`).toEvaluateTo([1, 2, 3], globals) From f9b0aa2db5f90c1f6399c0bdddff69925321fee2 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 12:32:45 -0800 Subject: [PATCH 2/8] add var? and var --- src/prelude/index.ts | 22 ++++++++++++------- src/prelude/tests/info.test.ts | 35 ++++++++++++++++++------------- src/prelude/tests/prelude.test.ts | 12 +++++++++++ 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/prelude/index.ts b/src/prelude/index.ts index cc46ead..488414b 100644 --- a/src/prelude/index.ts +++ b/src/prelude/index.ts @@ -1,7 +1,7 @@ // The prelude creates all the builtin Shrimp functions. import { - type Value, toValue, + type Value, type VM, toValue, extractParamInfo, isWrapped, getOriginalFunction, } from 'reefvm' @@ -34,13 +34,11 @@ export const globals = { const val = toValue(v) return `#<${val.type}: ${formatValue(val)}>` }, - length: (v: any) => { - const value = toValue(v) - switch (value.type) { - case 'string': case 'array': return value.value.length - case 'dict': return value.value.size - default: throw new Error(`length: expected string, array, or dict, got ${value.type}`) - } + var: function (this: VM, v: any) { + return typeof v === 'string' ? this.scope.get(v) : v + }, + 'var?': function (this: VM, v: string) { + return typeof v !== 'string' || this.scope.has(v) }, // type predicates @@ -65,6 +63,14 @@ export const globals = { identity: (v: any) => v, // collections + length: (v: any) => { + const value = toValue(v) + switch (value.type) { + case 'string': case 'array': return value.value.length + case 'dict': return value.value.size + default: throw new Error(`length: expected string, array, or dict, got ${value.type}`) + } + }, at: (collection: any, index: number | string) => { const value = toValue(collection) if (value.type === 'string' || value.type === 'array') { diff --git a/src/prelude/tests/info.test.ts b/src/prelude/tests/info.test.ts index 5a0b6fa..9c24a8a 100644 --- a/src/prelude/tests/info.test.ts +++ b/src/prelude/tests/info.test.ts @@ -1,5 +1,25 @@ import { expect, describe, test } from 'bun:test' -import { globals, colors } from '#prelude' +import { globals } from '#prelude' + +describe('var and var?', () => { + test('var? checks if a variable exists', async () => { + await expect(`var? 'nada'`).toEvaluateTo(false, globals) + await expect(`var? 'info'`).toEvaluateTo(false, globals) + await expect(`abc = abc; var? 'abc'`).toEvaluateTo(true, globals) + await expect(`var? 'var?'`).toEvaluateTo(true, globals) + + await expect(`var? 'dict'`).toEvaluateTo(true, globals) + await expect(`var? dict`).toEvaluateTo(true, globals) + }) + + test('var returns a value or null', async () => { + await expect(`var 'nada'`).toEvaluateTo(null, globals) + await expect(`var nada`).toEvaluateTo(null, globals) + await expect(`var 'info'`).toEvaluateTo(null, globals) + await expect(`abc = my-string; var 'abc'`).toEvaluateTo('my-string', globals) + await expect(`abc = my-string; var abc`).toEvaluateTo(null, globals) + }) +}) describe('type predicates', () => { test('string? checks for string type', async () => { @@ -38,7 +58,6 @@ describe('type predicates', () => { }) }) - describe('introspection', () => { test('type returns proper types', async () => { await expect(`type 'hello'`).toEvaluateTo('string', globals) @@ -50,18 +69,6 @@ describe('introspection', () => { await expect(`type [a=1 b=2]`).toEvaluateTo('dict', globals) }) - test('length', async () => { - await expect(`length 'hello'`).toEvaluateTo(5, globals) - await expect(`length [1 2 3]`).toEvaluateTo(3, globals) - await expect(`length [a=1 b=2]`).toEvaluateTo(2, 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 () => { await expect(`inspect 'hello'`).toEvaluateTo("\u001b[32m'hello\u001b[32m'\u001b[0m", globals) }) diff --git a/src/prelude/tests/prelude.test.ts b/src/prelude/tests/prelude.test.ts index 9429226..3125f9b 100644 --- a/src/prelude/tests/prelude.test.ts +++ b/src/prelude/tests/prelude.test.ts @@ -125,6 +125,18 @@ describe('utilities', () => { }) describe('collections', () => { + test('length', async () => { + await expect(`length 'hello'`).toEvaluateTo(5, globals) + await expect(`length [1 2 3]`).toEvaluateTo(3, globals) + await expect(`length [a=1 b=2]`).toEvaluateTo(2, 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('literal array creates array from arguments', async () => { await expect(`[ 1 2 3 ]`).toEvaluateTo([1, 2, 3], globals) await expect(`['a' 'b']`).toEvaluateTo(['a', 'b'], globals) From 6112d7e5a2804bbfd1c5b0e26dfaf6e1be2195b0 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 13:06:46 -0800 Subject: [PATCH 3/8] allow function calls in if/else if test expressions --- src/parser/shrimp.grammar | 8 ++- src/parser/shrimp.ts | 12 ++-- src/parser/tests/control-flow.test.ts | 81 +++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index 01f95b6..352e2bb 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -120,12 +120,16 @@ FunctionDef { Do Params colon (consumeToTerminator | newlineOrSemicolon block) CatchExpr? FinallyExpr? end } +ifTest { + ConditionalOp | expression | FunctionCall +} + IfExpr { - if (ConditionalOp | expression) colon Block ElseIfExpr* ElseExpr? end + if ifTest colon Block ElseIfExpr* ElseExpr? end } ElseIfExpr { - else if (ConditionalOp | expression) colon Block + else if ifTest colon Block } ElseExpr { diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index 5afc5d8..c6a98c1 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -7,11 +7,11 @@ import {highlighting} from "./highlight" const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, else:136} export const parser = LRParser.deserialize({ version: 14, - states: "9OQYQbOOO#zQcO'#C{O$zOSO'#C}OOQa'#DT'#DTO&TQbO'#DdO'iQcO'#E]OOQa'#E]'#E]O(lQcO'#E]O)nQcO'#E[O*UQRO'#C|O+eQcO'#EWO+uQcO'#EWO,PQbO'#CzO,wOpO'#CxOOQ`'#EX'#EXO,|QbO'#EWO-WQRO'#DtOOQ`'#EW'#EWO-lQQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO-tQbO'#DWO.PQbO'#DhO.tQQO'#DkO.PQbO'#DmO.PQbO'#DoO.yQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Ek'#EkOOQ`'#EO'#EOO/TQbO,59cO/}QbO'#DPO0VQWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0kOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO0yQbO,5:OO1QQQO,59oOOQa,5:O,5:OO1]QbO,5:OO1gQbO,5:aO.PQbO,59hO.PQbO,59hO.PQbO,59hO.PQbO,5:PO.PQbO,5:PO.PQbO,5:PO1zQRO,59fO2RQRO,59fO2dQRO,59fO2_QQO,59fO2oQQO,59fO2wObO,59dO3SQbO'#EPO3_QbO,59bO3yQbO,5:UO1gQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`'#Dy'#DyO4aQbO'#DXO4lQbO'#DYOOQO'#Dz'#DzO4dQQO'#DXO4zQQO,59rO5kQRO,5:SO5rQRO,5:SO3yQbO,5:VO5}QcO,5:XO6yQcO,5:XO7ZQcO,5:XO7eQRO,5:ZO7lQRO,5:ZOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7v-E7vO7wQQO1G/ZOOQa1G/j1G/jO8SQbO1G/jOOQO'#D|'#D|O7wQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O8SQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO9OQcO1G/SO9YQcO1G/SO9dQcO1G/SOOQa1G/k1G/kO;YQcO1G/kO;aQcO1G/kO;hQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;oQbO'#CwOOQ`,5:k,5:kOOQ`-E7}-E7}OOQ`'#D_'#D_O;|QbO'#D_ORQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O>]QQO'#D{O>bQbO'#EhOOQ`,59y,59yO?UQbO'#D]O?ZQQO'#D`OOQ`7+%[7+%[O?`QbO7+%[O?eQbO7+%[O?mQbO7+$xO?xQbO7+$xO@iQbO7+%YOOQ`7+%]7+%]O@nQbO7+%]O@sQbO7+%]O@{QbO7+%aOOQa<bAN>bOOQ`AN>OAN>OOBcQbOAN>OOBhQbOAN>OOOQ`AN>cAN>cOOQ`-E8O-E8OOOQ`AN>gAN>gOBpQbOAN>gO.PQbO,5:]O3yQbO,5:_OOQ`7+$}7+$}OOQ`G23jG23jOBuQbOG23jPBXQbO'#DqOOQ`G24RG24ROBzQRO1G/wOCRQRO1G/wOOQ`1G/y1G/yOOQ`LD)ULD)UO3yQbO7+%cOOQ`<kQbO1G/pOOQ`1G/z1G/zOOQ`-E7w-E7wO>vQQO,59sOOQO,59t,59tOOQO-E7x-E7xO?OQbO1G/^O4qQbO1G/nO?fQbO1G/qO4qQbO1G/uO?qQQO7+$uOOQa7+$u7+$uO?|QbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O@WQQO'#D{O@]QbO'#EhOOQ`,59y,59yOAPQbO'#D]OAUQQO'#D`OOQ`7+%[7+%[OAZQbO7+%[OA`QbO7+%[OAhQbO7+$xOAsQbO7+$xOBdQbO7+%YOOQ`7+%]7+%]OBiQbO7+%]OBnQbO7+%]OBvQbO7+%aOOQa<bAN>bOOQ`AN>OAN>OOD^QbOAN>OODcQbOAN>OOOQ`AN>cAN>cOOQ`-E8O-E8OOOQ`AN>gAN>gODkQbOAN>gO.|QbO,5:]O4qQbO,5:_OOQ`7+$}7+$}OOQ`G23jG23jODpQbOG23jPDSQbO'#DqOOQ`G24RG24RODuQQO1G/wOOQ`1G/y1G/yOOQ`LD)ULD)UO4qQbO7+%cOOQ`<q#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#hx[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#jq#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#hx[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 1578 + tokenPrec: 1652 }) diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts index af0f704..fa24a64 100644 --- a/src/parser/tests/control-flow.test.ts +++ b/src/parser/tests/control-flow.test.ts @@ -156,6 +156,87 @@ describe('if/else if/else', () => { keyword end `) }) + + test('parses function calls in if tests', () => { + expect(`if var? 'abc': true end`).toMatchTree(` + IfExpr + keyword if + FunctionCall + Identifier var? + PositionalArg + String + StringFragment abc + colon : + Block + Boolean true + keyword end + `) + }) + + test('parses function calls in if tests', () => { + expect(`if (var? 'abc'): true end`).toMatchTree(` + IfExpr + keyword if + ParenExpr + FunctionCall + Identifier var? + PositionalArg + String + StringFragment abc + colon : + Block + Boolean true + keyword end + `) + }) + + + test('parses function calls in else-if tests', () => { + expect(`if false: true else if var? 'abc': true end`).toMatchTree(` + IfExpr + keyword if + Boolean false + colon : + Block + Boolean true + ElseIfExpr + keyword else + keyword if + FunctionCall + Identifier var? + PositionalArg + String + StringFragment abc + colon : + Block + Boolean true + keyword end + `) + }) + + test('parses function calls in else-if tests', () => { + expect(`if false: true else if (var? 'abc'): true end`).toMatchTree(` + IfExpr + keyword if + Boolean false + colon : + Block + Boolean true + ElseIfExpr + keyword else + keyword if + ParenExpr + FunctionCall + Identifier var? + PositionalArg + String + StringFragment abc + colon : + Block + Boolean true + keyword end + `) + }) }) describe('while', () => { From 5f46346213626ff9f0688c21e110b6723543d5bb Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 13:18:54 -0800 Subject: [PATCH 4/8] allow if expr in parens --- src/parser/shrimp.grammar | 2 +- src/parser/shrimp.terms.ts | 62 +++++++++++++-------------- src/parser/shrimp.ts | 16 +++---- src/parser/tests/control-flow.test.ts | 16 +++++++ 4 files changed, 56 insertions(+), 40 deletions(-) diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar index 352e2bb..64c44df 100644 --- a/src/parser/shrimp.grammar +++ b/src/parser/shrimp.grammar @@ -188,7 +188,7 @@ BinOp { } ParenExpr { - leftParen (ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen + leftParen (IfExpr | ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen } expression { diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts index 05f3d5a..568b4af 100644 --- a/src/parser/shrimp.terms.ts +++ b/src/parser/shrimp.terms.ts @@ -30,36 +30,36 @@ export const DotGet = 28, Number = 29, ParenExpr = 30, - FunctionCallOrIdentifier = 31, - BinOp = 32, - String = 33, - StringFragment = 34, - Interpolation = 35, - EscapeSeq = 36, - Boolean = 37, - Regex = 38, - Dict = 39, - NamedArg = 40, - NamedArgPrefix = 41, - FunctionDef = 42, - Params = 43, - NamedParam = 44, - Null = 45, - colon = 46, - CatchExpr = 47, - keyword = 68, - Block = 49, - FinallyExpr = 50, - Underscore = 53, - Array = 54, - ConditionalOp = 55, - PositionalArg = 56, - WhileExpr = 58, - FunctionCallWithBlock = 60, - TryExpr = 61, - Throw = 63, - IfExpr = 65, - ElseIfExpr = 67, - ElseExpr = 69, + IfExpr = 31, + keyword = 69, + ConditionalOp = 33, + String = 34, + StringFragment = 35, + Interpolation = 36, + EscapeSeq = 37, + Boolean = 38, + Regex = 39, + Dict = 40, + NamedArg = 41, + NamedArgPrefix = 42, + FunctionDef = 43, + Params = 44, + NamedParam = 45, + Null = 46, + colon = 47, + CatchExpr = 48, + Block = 50, + FinallyExpr = 51, + Underscore = 54, + Array = 55, + ElseIfExpr = 56, + ElseExpr = 58, + FunctionCallOrIdentifier = 59, + BinOp = 60, + PositionalArg = 61, + WhileExpr = 63, + FunctionCallWithBlock = 65, + TryExpr = 66, + Throw = 68, CompoundAssign = 70, Assign = 71 diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts index c6a98c1..4f09662 100644 --- a/src/parser/shrimp.ts +++ b/src/parser/shrimp.ts @@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer" import {tokenizer, specializeKeyword} from "./tokenizer" import {trackScope} from "./scopeTracker" import {highlighting} from "./highlight" -const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, else:136} +const spec_Identifier = {__proto__:null,if:64, null:92, catch:98, finally:104, end:106, else:114, while:128, try:134, throw:138} export const parser = LRParser.deserialize({ version: 14, - states: "9[QYQbOOO#zQcO'#C{O$zOSO'#C}OOQa'#DT'#DTO&TQbO'#DdO'iQcO'#E]OOQa'#E]'#E]O(oQcO'#E]O)qQcO'#E[O*XQRO'#C|O+hQcO'#EWO+xQcO'#EWO,SQbO'#CzO,zOpO'#CxOOQ`'#EX'#EXO-PQbO'#EWO-ZQRO'#DtOOQ`'#EW'#EWO-oQQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO-wQbO'#DWO.SQbO'#DhO.wQQO'#DkO.SQbO'#DmO.|QbO'#DoO/qQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Ek'#EkOOQ`'#EO'#EOO/{QbO,59cO0uQbO'#DPO0}QWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO1cOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO1qQbO,5:OO1xQQO,59oOOQa,5:O,5:OO2TQbO,5:OO2_QbO,5:aO.SQbO,59hO.SQbO,59hO.SQbO,59hO.SQbO,5:PO.SQbO,5:PO.SQbO,5:PO2rQRO,59fO2yQRO,59fO3[QRO,59fO3VQQO,59fO3gQQO,59fO3oObO,59dO3zQbO'#EPO4VQbO,59bO4qQbO,5:UO2_QbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`'#Dy'#DyO5XQbO'#DXO5dQbO'#DYOOQO'#Dz'#DzO5[QQO'#DXO5rQQO,59rO6cQRO,5:SO6jQRO,5:SO4qQbO,5:VO6uQcO,5:XO7qQcO,5:XO8RQcO,5:XO8]QcO'#E[O8dQRO'#ElO8kQRO'#ElOOQO'#El'#ElO8vQQO,5:ZOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7v-E7vO8{QQO1G/ZOOQa1G/j1G/jO9WQbO1G/jOOQO'#D|'#D|O8{QQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O9WQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO:SQcO1G/SO:^QcO1G/SO:hQcO1G/SOOQa1G/k1G/kO<^QcO1G/kOkQbO1G/pOOQ`1G/z1G/zOOQ`-E7w-E7wO>vQQO,59sOOQO,59t,59tOOQO-E7x-E7xO?OQbO1G/^O4qQbO1G/nO?fQbO1G/qO4qQbO1G/uO?qQQO7+$uOOQa7+$u7+$uO?|QbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O@WQQO'#D{O@]QbO'#EhOOQ`,59y,59yOAPQbO'#D]OAUQQO'#D`OOQ`7+%[7+%[OAZQbO7+%[OA`QbO7+%[OAhQbO7+$xOAsQbO7+$xOBdQbO7+%YOOQ`7+%]7+%]OBiQbO7+%]OBnQbO7+%]OBvQbO7+%aOOQa<bAN>bOOQ`AN>OAN>OOD^QbOAN>OODcQbOAN>OOOQ`AN>cAN>cOOQ`-E8O-E8OOOQ`AN>gAN>gODkQbOAN>gO.|QbO,5:]O4qQbO,5:_OOQ`7+$}7+$}OOQ`G23jG23jODpQbOG23jPDSQbO'#DqOOQ`G24RG24RODuQQO1G/wOOQ`1G/y1G/yOOQ`LD)ULD)UO4qQbO7+%cOOQ`<]QbO1G/uOOQ`1G/z1G/zOOQ`-E7w-E7wO>hQQO,59tOOQO,59u,59uOOQO-E7x-E7xO>pQbO1G/_O3vQbO1G/RO3vQbO1G/sO?TQbO1G/vO?`QQO7+$vOOQa7+$v7+$vO?kQbO7+%VOOQa7+%V7+%VOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O?uQQO'#D{O?zQbO'#EiOOQ`,59z,59zO@kQbO'#D^O@pQQO'#DaOOQ`7+%a7+%aO@uQbO7+%aO@zQbO7+%aOASQbO7+$yOA_QbO7+$yOA{QbO7+$mOBTQbO7+%_OOQ`7+%b7+%bOBYQbO7+%bOB_QbO7+%bOOQa<gAN>gOOQ`AN>PAN>POCuQbOAN>POCzQbOAN>POOQ`-E7|-E7|OOQ`AN=sAN=sODSQbOAN=sO-[QbO,5:QO3vQbO,5:SOOQ`AN>hAN>hOOQ`7+%O7+%OOOQ`G23kG23kODXQbOG23kPD^QbO'#DfOOQ`G23_G23_ODcQQO1G/lOOQ`1G/n1G/nOOQ`LD)VLD)VO3vQbO7+%WOOQ`<q#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#hx[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#jq#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUsS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UsS#[QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZsS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#T~~'aO#R~U'hUsS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUsS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWsSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYsSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWsSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWsSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^sSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[sSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UzQsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWsSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^sSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^sSwQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXwQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UwQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWsSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbsSwQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[sSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUsS!PQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#ZQsSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVsSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#YQsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#U~U8vU#^QsSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUsS!WQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]sSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^sSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^sSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#hx[#VWsSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#XWsSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#WWsSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^sSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], - tokenPrec: 1652 + tokenPrec: 1634 }) diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts index fa24a64..1bacc31 100644 --- a/src/parser/tests/control-flow.test.ts +++ b/src/parser/tests/control-flow.test.ts @@ -237,6 +237,22 @@ describe('if/else if/else', () => { keyword end `) }) + + test('allows if/else in parens', () => { + expect(`eh? = (if true: true end)`).toMatchTree(` + Assign + AssignableIdentifier eh? + Eq = + ParenExpr + IfExpr + keyword if + Boolean true + colon : + Block + Boolean true + keyword end + `) + }) }) describe('while', () => { From 0e96911879e21803b11e6d3b6f68301011a06216 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 13:33:32 -0800 Subject: [PATCH 5/8] add Shrimp class as a nicer way to run code --- src/index.ts | 28 ++++++++++++++++++++++++++++ src/tests/shrimp.test.ts | 21 +++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/tests/shrimp.test.ts diff --git a/src/index.ts b/src/index.ts index 48fa3e7..dad8935 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,34 @@ export { Compiler } from '#compiler/compiler' export { parser } from '#parser/shrimp' export { globals } from '#prelude' +export class Shrimp { + vm: VM + + constructor() { + this.vm = new VM({ instructions: [], constants: [], labels: new Map() }, shrimpGlobals) + } + + async run(code: string | Bytecode): Promise { + let bytecode + + if (typeof code === 'string') { + const compiler = new Compiler(code, Object.keys(shrimpGlobals)) + bytecode = compiler.bytecode + } else { + bytecode = code + } + + this.vm.appendBytecode(bytecode) + await this.vm.continue() + return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!) : null + } + + get(name: string): any { + const value = this.vm.scope.get(name) + return value ? fromValue(value) : null + } +} + export async function runFile(path: string, globals?: Record): Promise { const code = readFileSync(path, 'utf-8') return await runCode(code, globals) diff --git a/src/tests/shrimp.test.ts b/src/tests/shrimp.test.ts new file mode 100644 index 0000000..b71c458 --- /dev/null +++ b/src/tests/shrimp.test.ts @@ -0,0 +1,21 @@ +import { describe } from 'bun:test' +import { expect, test } from 'bun:test' +import { Shrimp } from '..' + +describe('Shrimp', () => { + test('allows running Shrimp code', async () => { + const shrimp = new Shrimp() + + expect(await shrimp.run(`1 + 5`)).toEqual(6) + + await shrimp.run(`abc = true`) + expect(shrimp.get('abc')).toEqual(true) + + await shrimp.run(`name = Bob`) + expect(shrimp.get('abc')).toEqual(true) + expect(shrimp.get('name')).toEqual('Bob') + + await shrimp.run(`abc = false`) + expect(shrimp.get('abc')).toEqual(false) + }) +}) \ No newline at end of file From a535dc9605f86483200da586ad77407ae9cc9a90 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 13:41:08 -0800 Subject: [PATCH 6/8] Shrimp accepts custom globals --- src/index.ts | 9 ++++++--- src/tests/shrimp.test.ts | 14 +++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index dad8935..963ba25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,16 +9,19 @@ export { globals } from '#prelude' export class Shrimp { vm: VM + private globals?: Record - constructor() { - this.vm = new VM({ instructions: [], constants: [], labels: new Map() }, shrimpGlobals) + constructor(globals?: Record) { + const emptyBytecode = { instructions: [], constants: [], labels: new Map() } + this.vm = new VM(emptyBytecode, Object.assign({}, shrimpGlobals, globals ?? {})) + this.globals = globals } async run(code: string | Bytecode): Promise { let bytecode if (typeof code === 'string') { - const compiler = new Compiler(code, Object.keys(shrimpGlobals)) + const compiler = new Compiler(code, Object.keys(Object.assign({}, shrimpGlobals, this.globals ?? {}))) bytecode = compiler.bytecode } else { bytecode = code diff --git a/src/tests/shrimp.test.ts b/src/tests/shrimp.test.ts index b71c458..2713f3e 100644 --- a/src/tests/shrimp.test.ts +++ b/src/tests/shrimp.test.ts @@ -5,8 +5,12 @@ import { Shrimp } from '..' describe('Shrimp', () => { test('allows running Shrimp code', async () => { const shrimp = new Shrimp() - expect(await shrimp.run(`1 + 5`)).toEqual(6) + expect(await shrimp.run(`type 5`)).toEqual('number') + }) + + test('maintains state across runs', async () => { + const shrimp = new Shrimp() await shrimp.run(`abc = true`) expect(shrimp.get('abc')).toEqual(true) @@ -18,4 +22,12 @@ describe('Shrimp', () => { await shrimp.run(`abc = false`) expect(shrimp.get('abc')).toEqual(false) }) + + test('allows setting your own globals', async () => { + const shrimp = new Shrimp({ hiya: () => 'hey there' }) + + await shrimp.run('abc = hiya') + expect(shrimp.get('abc')).toEqual('hey there') + expect(await shrimp.run('type abc')).toEqual('string') + }) }) \ No newline at end of file From 600330ba7f5b856036fcd59c2ad04e05bf5877f9 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 14:01:00 -0800 Subject: [PATCH 7/8] Shrimp.run accepts locals --- src/index.ts | 7 +++++-- src/tests/shrimp.test.ts | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 963ba25..3a91bed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,18 +17,21 @@ export class Shrimp { this.globals = globals } - async run(code: string | Bytecode): Promise { + async run(code: string | Bytecode, locals?: Record): Promise { let bytecode if (typeof code === 'string') { - const compiler = new Compiler(code, Object.keys(Object.assign({}, shrimpGlobals, this.globals ?? {}))) + const compiler = new Compiler(code, Object.keys(Object.assign({}, shrimpGlobals, this.globals ?? {}, locals ?? {}))) bytecode = compiler.bytecode } else { bytecode = code } + if (locals) this.vm.pushScope(locals) this.vm.appendBytecode(bytecode) await this.vm.continue() + if (locals) this.vm.popScope() + return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!) : null } diff --git a/src/tests/shrimp.test.ts b/src/tests/shrimp.test.ts index 2713f3e..d8ddb7d 100644 --- a/src/tests/shrimp.test.ts +++ b/src/tests/shrimp.test.ts @@ -29,5 +29,26 @@ describe('Shrimp', () => { await shrimp.run('abc = hiya') expect(shrimp.get('abc')).toEqual('hey there') expect(await shrimp.run('type abc')).toEqual('string') + + // still there + expect(await shrimp.run('hiya')).toEqual('hey there') + }) + + test('allows setting your own locals', async () => { + const shrimp = new Shrimp({ 'my-global': () => 'hey there' }) + + await shrimp.run('abc = my-global') + expect(shrimp.get('abc')).toEqual('hey there') + + await shrimp.run('abc = my-global', { 'my-global': 'now a local' }) + expect(shrimp.get('abc')).toEqual('now a local') + + const shrimp2 = new Shrimp() + await shrimp.run('abc = nothing') + expect(shrimp.get('abc')).toEqual('nothing') + await shrimp.run('abc = nothing', { nothing: 'something' }) + expect(shrimp.get('abc')).toEqual('something') + await shrimp.run('abc = nothing') + expect(shrimp.get('abc')).toEqual('nothing') }) }) \ No newline at end of file From 1a3e041001358ce6b10078c46a6585e76633310f Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Wed, 5 Nov 2025 14:18:53 -0800 Subject: [PATCH 8/8] update reef --- bun.lock | 36 ++++++++++++++++++------------------ package.json | 4 ++-- src/tests/shrimp.test.ts | 1 - 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/bun.lock b/bun.lock index 559b57e..f00ae05 100644 --- a/bun.lock +++ b/bun.lock @@ -2,7 +2,7 @@ "lockfileVersion": 1, "workspaces": { "": { - "name": "bun-react-template", + "name": "shrimp", "dependencies": { "@codemirror/view": "^6.38.3", "@lezer/generator": "^1.8.0", @@ -20,39 +20,39 @@ }, }, "packages": { - "@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-61Hfv3cF07XvUxNeC3E7jhG8XNi1Yom1G0lRC936oLnlF+jrbrv8rc/J98XlYzcsAoTVupfsf5fLej1aI8kyIg=="], + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.19.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-q6NenYkEy2fn9+JyjIxMWcNjzTL/IhwqfzOut1/G3PrIFkrbl4AL7Wkse5tLrQUUyqGoAKU5+Pi5jnnXxH5HGw=="], - "@codemirror/commands": ["@codemirror/commands@6.8.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="], + "@codemirror/commands": ["@codemirror/commands@6.10.0", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w=="], "@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="], - "@codemirror/lint": ["@codemirror/lint@6.8.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA=="], + "@codemirror/lint": ["@codemirror/lint@6.9.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ=="], "@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="], "@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="], - "@codemirror/view": ["@codemirror/view@6.38.3", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-x2t87+oqwB1mduiQZ6huIghjMt4uZKFEdj66IcXw7+a5iBEvv9lh7EWDRHI7crnD4BMGpnyq/RzmCGbiEZLcvQ=="], + "@codemirror/view": ["@codemirror/view@6.38.6", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw=="], - "@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="], + "@lezer/common": ["@lezer/common@1.3.0", "", {}, "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ=="], "@lezer/generator": ["@lezer/generator@1.8.0", "", { "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" }, "bin": { "lezer-generator": "src/lezer-generator.cjs" } }, "sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg=="], - "@lezer/highlight": ["@lezer/highlight@1.2.1", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA=="], + "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], - "@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="], + "@lezer/lr": ["@lezer/lr@1.4.3", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA=="], "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], - "@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="], + "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], - "@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="], + "@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="], - "@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="], + "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], "bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="], - "bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="], + "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], @@ -60,17 +60,17 @@ "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="], + "hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="], - "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#0f39e9401eb7a0a7c906e150127f9829458a79b6", { "peerDependencies": { "typescript": "^5" } }, "0f39e9401eb7a0a7c906e150127f9829458a79b6"], + "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#33ea94a2476f87f22408dce8b7beb3587070e01b", { "peerDependencies": { "typescript": "^5" } }, "33ea94a2476f87f22408dce8b7beb3587070e01b"], - "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], - "tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="], + "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], - "typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="], + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], } diff --git a/package.json b/package.json index b728a9a..f3fd090 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,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": "rm -rf ~/.bun/install/cache/ && bun update reefvm" + "update-reef": "rm -rf ~/.bun/install/cache/ && rm bun.lock && bun update reefvm" }, "dependencies": { "@codemirror/view": "^6.38.3", @@ -29,4 +29,4 @@ "singleQuote": true, "printWidth": 100 } -} +} \ No newline at end of file diff --git a/src/tests/shrimp.test.ts b/src/tests/shrimp.test.ts index d8ddb7d..3f7a6f8 100644 --- a/src/tests/shrimp.test.ts +++ b/src/tests/shrimp.test.ts @@ -43,7 +43,6 @@ describe('Shrimp', () => { await shrimp.run('abc = my-global', { 'my-global': 'now a local' }) expect(shrimp.get('abc')).toEqual('now a local') - const shrimp2 = new Shrimp() await shrimp.run('abc = nothing') expect(shrimp.get('abc')).toEqual('nothing') await shrimp.run('abc = nothing', { nothing: 'something' })