From 5f4bf60062e5a6c79aa77ec6479145dfc2c1f9b8 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 19:29:19 -0800
Subject: [PATCH 01/12] while loops
---
src/compiler/compiler.ts | 21 +++++
src/compiler/tests/while.test.ts | 37 +++++++++
src/parser/shrimp.grammar | 21 +++++
src/parser/shrimp.terms.ts | 23 +++---
src/parser/shrimp.ts | 18 ++--
src/parser/tests/control-flow.test.ts | 115 ++++++++++++++++++++++++++
6 files changed, 216 insertions(+), 19 deletions(-)
create mode 100644 src/compiler/tests/while.test.ts
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index efcff49..be8814b 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -54,6 +54,7 @@ export class Compiler {
fnLabelCount = 0
ifLabelCount = 0
tryLabelCount = 0
+ loopLabelCount = 0
bytecode: Bytecode
pipeCounter = 0
@@ -388,6 +389,8 @@ export class Compiler {
return instructions
}
+ case terms.MultiLineBlock:
+ case terms.SingleLineBlock:
case terms.ThenBlock:
case terms.SingleLineThenBlock:
case terms.TryBlock: {
@@ -629,6 +632,24 @@ export class Compiler {
return instructions
}
+ case terms.WhileExpr: {
+ const [_while, test, _colon, block] = getAllChildren(node)
+ const instructions: ProgramItem[] = []
+
+ this.loopLabelCount++
+ const startLoop = `.loop_${this.loopLabelCount}:`
+ const endLoop = `.end_loop_${this.loopLabelCount}:`
+
+ instructions.push([`${startLoop}:`])
+ instructions.push(...this.#compileNode(test!, input))
+ instructions.push(['JUMP_IF_FALSE', endLoop])
+ instructions.push(...this.#compileNode(block!, input))
+ instructions.push(['JUMP', startLoop])
+ instructions.push([`${endLoop}:`])
+
+ return instructions
+ }
+
default:
throw new CompilerError(
`Compiler doesn't know how to handle a "${node.type.name}" node.`,
diff --git a/src/compiler/tests/while.test.ts b/src/compiler/tests/while.test.ts
new file mode 100644
index 0000000..2685208
--- /dev/null
+++ b/src/compiler/tests/while.test.ts
@@ -0,0 +1,37 @@
+import { describe } from 'bun:test'
+import { expect, test } from 'bun:test'
+
+describe('while', () => {
+ test('basic variable', () => {
+ expect(`
+ a = true
+ b = ''
+ while a:
+ a = false
+ b = done
+ end
+ b`)
+ .toEvaluateTo('done')
+ })
+
+ test('basic expression', () => {
+ expect(`
+ a = 0
+ while a < 10:
+ a += 1
+ end
+ a`)
+ .toEvaluateTo(10)
+ })
+
+ test('compound expression', () => {
+ expect(`
+ a = 1
+ b = 0
+ while a > 0 and b < 100:
+ b += 1
+ end
+ b`)
+ .toEvaluateTo(100)
+ })
+})
\ No newline at end of file
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index a3daaf4..1f321b2 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -50,6 +50,7 @@ item {
consumeToTerminator {
PipeExpr |
+ WhileExpr |
ambiguousFunctionCall |
TryExpr |
Throw |
@@ -70,6 +71,26 @@ pipeOperand {
FunctionCall | FunctionCallOrIdentifier
}
+WhileExpr {
+ singleLineWhileExpr | multiLineWhileExpr
+}
+
+SingleLineBlock {
+ consumeToTerminator
+}
+
+MultiLineBlock {
+ block
+}
+
+singleLineWhileExpr {
+ @specialize[@name=keyword] (ConditionalOp | expression) colon SingleLineBlock @specialize[@name=keyword]
+}
+
+multiLineWhileExpr {
+ @specialize[@name=keyword] (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock @specialize[@name=keyword]
+}
+
FunctionCallOrIdentifier {
DotGet | Identifier
}
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index 07b57b4..6765bad 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -47,19 +47,22 @@ export const
Null = 45,
colon = 46,
CatchExpr = 47,
- keyword = 69,
+ keyword = 73,
TryBlock = 49,
FinallyExpr = 50,
Underscore = 53,
Array = 54,
ConditionalOp = 55,
PositionalArg = 56,
- TryExpr = 58,
- Throw = 60,
- IfExpr = 62,
- SingleLineThenBlock = 64,
- ThenBlock = 65,
- ElseIfExpr = 66,
- ElseExpr = 68,
- CompoundAssign = 70,
- Assign = 71
+ WhileExpr = 58,
+ SingleLineBlock = 60,
+ MultiLineBlock = 61,
+ TryExpr = 62,
+ Throw = 64,
+ IfExpr = 66,
+ SingleLineThenBlock = 68,
+ ThenBlock = 69,
+ ElseIfExpr = 70,
+ ElseExpr = 72,
+ CompoundAssign = 74,
+ Assign = 75
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index 0877626..91f269f 100644
--- a/src/parser/shrimp.ts
+++ b/src/parser/shrimp.ts
@@ -4,14 +4,14 @@ 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, try:118, throw:122, if:126, elseif:134, else:138}
+const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:126, throw:130, if:134, elseif:142, else:146}
export const parser = LRParser.deserialize({
version: 14,
- states: ":QQYQbOOO#tQcO'#C{O$qOSO'#C}O%PQbO'#EhOOQ`'#DW'#DWOOQa'#DT'#DTO&VQbO'#DdO'hQcO'#E]OOQa'#E]'#E]O(kQcO'#E]O)mQcO'#E[O*QQRO'#C|O+^QcO'#EWO+nQcO'#EWO+xQbO'#CzO,pOpO'#CxOOQ`'#EX'#EXO,uQbO'#EWO,|QQO'#EnOOQ`'#Dh'#DhO-RQbO'#DjO-RQbO'#EpOOQ`'#Dl'#DlO-vQRO'#DtOOQ`'#EW'#EWO.[QQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO.dQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Em'#EmOOQ`'#EO'#EOO.nQbO,59cO/bQbO'#DPO/jQWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0OOSO,59iOOQa,59i,59iOOQ`'#Dy'#DyO0^QbO'#DXO0iQbO'#DYOOQO'#Dz'#DzO0aQQO'#DXO0wQQO,5;SOOQ`'#Dx'#DxO0|QbO,5:OO1TQQO,59oOOQa,5:O,5:OO1`QbO,5:OO1jQbO,5:aO-RQbO,59hO-RQbO,59hO-RQbO,59hO-RQbO,5:PO-RQbO,5:PO-RQbO,5:PO1zQRO,59fO2RQRO,59fO2dQRO,59fO2_QQO,59fO2oQQO,59fO2wObO,59dO3SQbO'#EPO3_QbO,59bO3vQbO,5;YO4ZQcO,5:UO5PQcO,5:UO5aQcO,5:UO6VQRO,5;[O6^QRO,5;[O1jQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7w-E7wO6iQQO,59sOOQO,59t,59tOOQO-E7x-E7xO6qQbO1G0nOOQ`-E7v-E7vO7UQQO1G/ZOOQa1G/j1G/jO7aQbO1G/jOOQO'#D|'#D|O7UQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O7aQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO8YQcO1G/SO8dQcO1G/SO8nQcO1G/SOOQa1G/k1G/kO:^QcO1G/kO:eQcO1G/kO:lQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!aQbO'#C{O:sQbO'#CwOOQ`,5:k,5:kOOQ`-E7}-E7}O;QQbO1G0tO;]QbO1G0uO;yQbO1G0vOOQ`1G/z1G/zO<^QbO7+&YO;]QbO7+&[O`QbO7+&aOOQ`'#Dn'#DnO>kQbO7+&bO>pQbO7+&cOOQ`<pQbO7+%bOOQ`7+%d7+%dOOQ`<QQbO7+%UOOQa7+%U7+%UOOQO-E8O-E8OOOQ`-E8P-E8POOQ`'#Dj'#DjO>[QbO7+&dO>aQbO7+&eO>zQbO'#D]O?PQQO'#D`OOQ`7+&f7+&fO?UQbO7+&fO?ZQbO7+&fOOQ`'#EP'#EPO?cQQO'#EPO?hQbO'#EmOOQ`'#D_'#D_O@_QbO7+&gOOQ`'#Dr'#DrO@jQbO7+&hO@oQbO7+&iOOQ`<aQbO1G/fOOQ`1G/f1G/fOOQ`AN?lAN?lOOQ`AN?mAN?mODRQbOAN?mO-PQbO'#DtOOQ`'#EU'#EUODWQbOAN?oODcQQO'#DvOOQ`AN?oAN?oODhQbOAN?oOOQ`G25OG25OOOQ`G25QG25QODmQbOG25QODrQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25XG25XOE`QRO,5:`OEgQRO,5:`OOQ`-E8S-E8SOOQ`G25ZG25ZOErQbOG25ZOEwQQO,5:bOOQ`LD*lLD*lOOQ`<aQbO1G/|O@oQbO7+%fOOQ`7+%h7+%hOOQ`<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!{YOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!|YOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!|YOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#W~~'aO#U~U'hUrS#RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#dQOt#{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#^QrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#X~U8vU#cQrSOt#{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[#YWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#[WrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#ZWrSOt#{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: 1619
+ tokenPrec: 1705
})
diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts
index 70efddb..a62d74a 100644
--- a/src/parser/tests/control-flow.test.ts
+++ b/src/parser/tests/control-flow.test.ts
@@ -154,3 +154,118 @@ describe('if/elseif/else', () => {
`)
})
})
+
+describe('while', () => {
+ test('infinite loop', () => {
+ expect(`while true: true end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ Boolean true
+ colon :
+ SingleLineBlock
+ Boolean true
+ keyword end`)
+ })
+
+ test('basic expression', () => {
+ expect(`while a > 0: true end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ ConditionalOp
+ Identifier a
+ Gt >
+ Number 0
+ colon :
+ SingleLineBlock
+ Boolean true
+ keyword end`)
+ })
+
+
+ test('compound expression', () => {
+ expect(`while a > 0 and b < 100 and c < 1000: true end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ ConditionalOp
+ ConditionalOp
+ ConditionalOp
+ Identifier a
+ Gt >
+ Number 0
+ And and
+ ConditionalOp
+ Identifier b
+ Lt <
+ Number 100
+ And and
+ ConditionalOp
+ Identifier c
+ Lt <
+ Number 1000
+ colon :
+ SingleLineBlock
+ Boolean true
+ keyword end`)
+ })
+
+ test('multiline infinite loop', () => {
+ expect(`
+ while true:
+ true
+ end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ Boolean true
+ colon :
+ MultiLineBlock
+ Boolean true
+ keyword end`)
+ })
+
+ test('multiline basic expression', () => {
+ expect(`
+ while a > 0:
+ true
+ end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ ConditionalOp
+ Identifier a
+ Gt >
+ Number 0
+ colon :
+ MultiLineBlock
+ Boolean true
+ keyword end`)
+ })
+
+
+ test('multiline compound expression', () => {
+ expect(`
+ while a > 0 and b < 100 and c < 1000:
+ true
+ end`).toMatchTree(`
+ WhileExpr
+ keyword while
+ ConditionalOp
+ ConditionalOp
+ ConditionalOp
+ Identifier a
+ Gt >
+ Number 0
+ And and
+ ConditionalOp
+ Identifier b
+ Lt <
+ Number 100
+ And and
+ ConditionalOp
+ Identifier c
+ Lt <
+ Number 1000
+ colon :
+ MultiLineBlock
+ Boolean true
+ keyword end`)
+ })
+})
\ No newline at end of file
--
2.50.1
From ee0e6c6c411a4a73cb67c44515de1edaa63b188a Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 19:51:43 -0800
Subject: [PATCH 02/12] Add some grammar aliases for readability
---
src/parser/shrimp.grammar | 57 ++++++++++++++++----------------------
src/parser/shrimp.terms.ts | 4 +--
src/parser/shrimp.ts | 14 +++++-----
3 files changed, 33 insertions(+), 42 deletions(-)
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index 1f321b2..f95f93d 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -25,9 +25,19 @@
Underscore { "_" }
Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar
"|"[@name=operator]
-
}
+end { @specialize[@name=keyword] }
+while { @specialize[@name=keyword] }
+if { @specialize[@name=keyword] }
+else { @specialize[@name=keyword] }
+elseif { @specialize[@name=keyword] }
+try { @specialize[@name=keyword] }
+catch { @specialize[@name=keyword] }
+finally { @specialize[@name=keyword] }
+throw { @specialize[@name=keyword] }
+null { @specialize[@name=Null] }
+
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
@external specialize {Identifier} specializeKeyword from "./tokenizer" { Do }
@@ -47,7 +57,6 @@ item {
newlineOrSemicolon // allow blank lines
}
-
consumeToTerminator {
PipeExpr |
WhileExpr |
@@ -72,7 +81,7 @@ pipeOperand {
}
WhileExpr {
- singleLineWhileExpr | multiLineWhileExpr
+ while (ConditionalOp | expression) colon (newlineOrSemicolon MultiLineBlock | SingleLineBlock) end
}
SingleLineBlock {
@@ -83,14 +92,6 @@ MultiLineBlock {
block
}
-singleLineWhileExpr {
- @specialize[@name=keyword] (ConditionalOp | expression) colon SingleLineBlock @specialize[@name=keyword]
-}
-
-multiLineWhileExpr {
- @specialize[@name=keyword] (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock @specialize[@name=keyword]
-}
-
FunctionCallOrIdentifier {
DotGet | Identifier
}
@@ -107,7 +108,6 @@ arg {
PositionalArg | NamedArg
}
-
PositionalArg {
expression | FunctionDef | Underscore
}
@@ -117,15 +117,7 @@ NamedArg {
}
FunctionDef {
- singleLineFunctionDef | multilineFunctionDef
-}
-
-singleLineFunctionDef {
- Do Params colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]
-}
-
-multilineFunctionDef {
- Do Params colon newlineOrSemicolon block CatchExpr? FinallyExpr? @specialize[@name=keyword]
+ Do Params colon (consumeToTerminator | newlineOrSemicolon block) CatchExpr? FinallyExpr? end
}
IfExpr {
@@ -133,19 +125,19 @@ IfExpr {
}
singleLineIf {
- @specialize[@name=keyword] (ConditionalOp | expression) colon SingleLineThenBlock @specialize[@name=keyword]
+ if (ConditionalOp | expression) colon SingleLineThenBlock end
}
multilineIf {
- @specialize[@name=keyword] (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock ElseIfExpr* ElseExpr? @specialize[@name=keyword]
+ if (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock ElseIfExpr* ElseExpr? end
}
ElseIfExpr {
- @specialize[@name=keyword] (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock
+ elseif (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock
}
ElseExpr {
- @specialize[@name=keyword] colon newlineOrSemicolon ThenBlock
+ else colon newlineOrSemicolon ThenBlock
}
ThenBlock {
@@ -161,19 +153,19 @@ TryExpr {
}
singleLineTry {
- @specialize[@name=keyword] colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]
+ try colon consumeToTerminator CatchExpr? FinallyExpr? end
}
multilineTry {
- @specialize[@name=keyword] colon newlineOrSemicolon TryBlock CatchExpr? FinallyExpr? @specialize[@name=keyword]
+ try colon newlineOrSemicolon TryBlock CatchExpr? FinallyExpr? end
}
CatchExpr {
- @specialize[@name=keyword] Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator)
+ catch Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator)
}
FinallyExpr {
- @specialize[@name=keyword] colon (newlineOrSemicolon TryBlock | consumeToTerminator)
+ finally colon (newlineOrSemicolon TryBlock | consumeToTerminator)
}
TryBlock {
@@ -181,7 +173,7 @@ TryBlock {
}
Throw {
- @specialize[@name=keyword] (BinOp | ConditionalOp | expression)
+ throw (BinOp | ConditionalOp | expression)
}
ConditionalOp {
@@ -200,7 +192,7 @@ Params {
}
NamedParam {
- NamedArgPrefix (String | Number | Boolean | @specialize[@name=Null])
+ NamedArgPrefix (String | Number | Boolean | null)
}
Assign {
@@ -238,7 +230,6 @@ expression {
}
String { "'" stringContent* "'" }
-
}
stringContent {
@@ -274,7 +265,7 @@ Array {
// to go through ambiguousFunctionCall (which is what we want semantically).
// Yes, it is annoying and I gave up trying to use GLR to fix it.
expressionWithoutIdentifier {
- ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | @specialize[@name=Null]
+ ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | null
}
block {
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index 6765bad..96a8259 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -55,8 +55,8 @@ export const
ConditionalOp = 55,
PositionalArg = 56,
WhileExpr = 58,
- SingleLineBlock = 60,
- MultiLineBlock = 61,
+ MultiLineBlock = 60,
+ SingleLineBlock = 61,
TryExpr = 62,
Throw = 64,
IfExpr = 66,
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index 91f269f..38ed0db 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:126, throw:130, if:134, elseif:142, else:146}
export const parser = LRParser.deserialize({
version: 14,
- states: ";lQYQbOOO#wQcO'#C{O$tOSO'#C}O%SQbO'#ElOOQ`'#DW'#DWOOQa'#DT'#DTO&YQbO'#DdO'kQcO'#EaOOQa'#Ea'#EaO(nQcO'#EaO)pQcO'#E`O*TQRO'#C|O+aQcO'#E[O+qQcO'#E[O+{QbO'#CzO,sOpO'#CxOOQ`'#E]'#E]O,xQbO'#E[O-PQbO'#ErOOQ`'#Dh'#DhO-tQQO'#EtOOQ`'#Dl'#DlO-PQbO'#DnO-PQbO'#EvOOQ`'#Dp'#DpO-yQRO'#DxOOQ`'#E['#E[O._QQO'#EZOOQ`'#EZ'#EZOOQ`'#Dz'#DzQYQbOOO.gQbO'#DUOOQa'#E`'#E`OOQ`'#Df'#DfOOQ`'#Eq'#EqOOQ`'#ES'#ESO.qQbO,59cO/eQbO'#DPO/mQWO'#DQOOOO'#Ec'#EcOOOO'#D{'#D{O0ROSO,59iOOQa,59i,59iOOQ`'#D}'#D}O0aQbO'#DXO0lQbO'#DYOOQO'#EO'#EOO0dQQO'#DXO0zQQO,5;WOOQ`'#D|'#D|O1PQbO,5:OO1WQQO,59oOOQa,5:O,5:OO1cQbO,5:OO1mQbO,5:eO-PQbO,59hO-PQbO,59hO-PQbO,59hO-PQbO,5:PO-PQbO,5:PO-PQbO,5:PO2QQRO,59fO2XQRO,59fO2jQRO,59fO2eQQO,59fO2uQQO,59fO2}ObO,59dO3YQbO'#ETO3eQbO,59bO4hQRO,5;^O4oQRO,5;^O4zQbO,5;`O5bQcO,5:YO6WQcO,5:YO6hQcO,5:YO6rQRO,5;bO6yQRO,5;bO1mQbO,5:dOOQ`,5:u,5:uOOQ`-E7x-E7xOOQ`,59p,59pOOQ`-E8Q-E8QOOOO,59k,59kOOOO,59l,59lOOOO-E7y-E7yOOQa1G/T1G/TOOQ`-E7{-E7{O7UQQO,59sOOQO,59t,59tOOQO-E7|-E7|O7^QbO1G0rOOQ`-E7z-E7zO7tQQO1G/ZOOQa1G/j1G/jO8PQbO1G/jOOQO'#EQ'#EQO7tQQO1G/ZOOQa1G/Z1G/ZOOQ`'#ER'#ERO8PQbO1G/jOOQ`1G0P1G0POOQa1G/S1G/SO8xQcO1G/SO9SQcO1G/SO9^QcO1G/SOOQa1G/k1G/kO:|QcO1G/kO;TQcO1G/kO;[QcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;cQbO'#CwOOQ`,5:o,5:oOOQ`-E8R-E8RO;pQbO1G0xOQQbO7+%UOOQa7+%U7+%UOOQO-E8O-E8OOOQ`-E8P-E8POOQ`'#Dj'#DjO>[QbO7+&dO>aQbO7+&eO>zQbO'#D]O?PQQO'#D`OOQ`7+&f7+&fO?UQbO7+&fO?ZQbO7+&fOOQ`'#EP'#EPO?cQQO'#EPO?hQbO'#EmOOQ`'#D_'#D_O@_QbO7+&gOOQ`'#Dr'#DrO@jQbO7+&hO@oQbO7+&iOOQ`<aQbO1G/fOOQ`1G/f1G/fOOQ`AN?lAN?lOOQ`AN?mAN?mODRQbOAN?mO-PQbO'#DtOOQ`'#EU'#EUODWQbOAN?oODcQQO'#DvOOQ`AN?oAN?oODhQbOAN?oOOQ`G25OG25OOOQ`G25QG25QODmQbOG25QODrQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25XG25XOE`QRO,5:`OEgQRO,5:`OOQ`-E8S-E8SOOQ`G25ZG25ZOErQbOG25ZOEwQQO,5:bOOQ`LD*lLD*lOOQ`<aQbO1G/|O@oQbO7+%fOOQ`7+%h7+%hOOQ`<PQbO7+$xO[QbO7+%YO>uQbO7+%YO>zQbO'#D]O?PQQO'#D`OOQ`7+&b7+&bO?UQbO7+&bO?ZQbO7+&bOOQ`'#EP'#EPO?cQQO'#EPO?hQbO'#ElOOQ`'#D_'#D_O@_QbO7+&cOOQ`'#Dr'#DrO@jQbO7+&dO@oQbO7+&eOOQa<OAN>OOCXQbOAN>OOC^QbOAN>OOOQ`AN>`AN>`OCfQbO1G/cO>[QbO1G/fOOQ`1G/f1G/fOOQ`AN?hAN?hOOQ`AN?iAN?iOC|QbOAN?iOOQ`'#EU'#EUODRQbOAN?kO-mQbO'#DtOD^QQO'#DvOOQ`AN?kAN?kODcQbOAN?kOOQ`G23jG23jODhQbOG23jODmQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25TG25TOOQ`-E8S-E8SOOQ`G25VG25VOEZQbOG25VOE`QRO,5:`OEgQRO,5:`OErQQO,5:bOOQ`LD)ULD)UOOQ`<[QbO1G/|O@oQbO7+%fOOQ`7+%h7+%hOOQ`<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!{YOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!|YOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!|YOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#W~~'aO#U~U'hUrS#RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#dQOt#{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#^QrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#X~U8vU#cQrSOt#{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[#YWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#[WrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#ZWrSOt#{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!{YOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!|YOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!|YOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#W~~'aO#U~U'hUrS#RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#bQOt#{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#^QrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#X~U8vU#aQrSOt#{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[#YWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#[WrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#ZWrSOt#{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: 1705
+ tokenPrec: 1700
})
--
2.50.1
From dc557deb4000549aaa565f1ac5103272f94de741 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 19:55:41 -0800
Subject: [PATCH 03/12] Collapse all blocks into SingleLineBlock and
MultiLineBlock
---
src/compiler/compiler.ts | 5 +----
src/compiler/utils.ts | 2 +-
src/parser/shrimp.grammar | 26 +++++++-------------------
src/parser/shrimp.terms.ts | 23 ++++++++++-------------
src/parser/shrimp.ts | 16 ++++++++--------
src/parser/tests/basics.test.ts | 4 ++--
src/parser/tests/control-flow.test.ts | 24 ++++++++++++------------
src/parser/tests/exceptions.test.ts | 22 +++++++++++-----------
8 files changed, 52 insertions(+), 70 deletions(-)
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index be8814b..f872e17 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -390,10 +390,7 @@ export class Compiler {
}
case terms.MultiLineBlock:
- case terms.SingleLineBlock:
- case terms.ThenBlock:
- case terms.SingleLineThenBlock:
- case terms.TryBlock: {
+ case terms.SingleLineBlock: {
const children = getAllChildren(node)
const instructions: ProgramItem[] = []
diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts
index 6151563..5738cac 100644
--- a/src/compiler/utils.ts
+++ b/src/compiler/utils.ts
@@ -309,7 +309,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
export const getTryExprParts = (node: SyntaxNode, input: string) => {
const children = getAllChildren(node)
- // First child is always 'try' keyword, second is colon, third is TryBlock or statement
+ // First child is always 'try' keyword, second is colon, third is MultiLineBlock or statement
const [tryKeyword, _colon, tryBlock, ...rest] = children
if (!tryKeyword || !tryBlock) {
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index f95f93d..e8787cb 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -125,27 +125,19 @@ IfExpr {
}
singleLineIf {
- if (ConditionalOp | expression) colon SingleLineThenBlock end
+ if (ConditionalOp | expression) colon SingleLineBlock end
}
multilineIf {
- if (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock ElseIfExpr* ElseExpr? end
+ if (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock ElseIfExpr* ElseExpr? end
}
ElseIfExpr {
- elseif (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock
+ elseif (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock
}
ElseExpr {
- else colon newlineOrSemicolon ThenBlock
-}
-
-ThenBlock {
- block
-}
-
-SingleLineThenBlock {
- consumeToTerminator
+ else colon newlineOrSemicolon MultiLineBlock
}
TryExpr {
@@ -157,19 +149,15 @@ singleLineTry {
}
multilineTry {
- try colon newlineOrSemicolon TryBlock CatchExpr? FinallyExpr? end
+ try colon newlineOrSemicolon MultiLineBlock CatchExpr? FinallyExpr? end
}
CatchExpr {
- catch Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator)
+ catch Identifier colon (newlineOrSemicolon MultiLineBlock | consumeToTerminator)
}
FinallyExpr {
- finally colon (newlineOrSemicolon TryBlock | consumeToTerminator)
-}
-
-TryBlock {
- block
+ finally colon (newlineOrSemicolon MultiLineBlock | consumeToTerminator)
}
Throw {
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index 96a8259..743e237 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -47,22 +47,19 @@ export const
Null = 45,
colon = 46,
CatchExpr = 47,
- keyword = 73,
- TryBlock = 49,
+ keyword = 70,
+ MultiLineBlock = 49,
FinallyExpr = 50,
Underscore = 53,
Array = 54,
ConditionalOp = 55,
PositionalArg = 56,
WhileExpr = 58,
- MultiLineBlock = 60,
- SingleLineBlock = 61,
- TryExpr = 62,
- Throw = 64,
- IfExpr = 66,
- SingleLineThenBlock = 68,
- ThenBlock = 69,
- ElseIfExpr = 70,
- ElseExpr = 72,
- CompoundAssign = 74,
- Assign = 75
+ SingleLineBlock = 60,
+ TryExpr = 61,
+ Throw = 63,
+ IfExpr = 65,
+ ElseIfExpr = 67,
+ ElseExpr = 69,
+ CompoundAssign = 71,
+ Assign = 72
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index 38ed0db..84be190 100644
--- a/src/parser/shrimp.ts
+++ b/src/parser/shrimp.ts
@@ -4,14 +4,14 @@ 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:126, throw:130, if:134, elseif:142, else:146}
+const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, elseif:136, else:140}
export const parser = LRParser.deserialize({
version: 14,
- states: ":|QYQbOOO#wQcO'#C{O$tOSO'#C}OOQa'#DT'#DTO%}QbO'#DdO'`QcO'#EaOOQa'#Ea'#EaO(cQcO'#EaO)eQcO'#E`O)xQRO'#C|O+UQcO'#E[O+fQcO'#E[O+pQbO'#CzO,hOpO'#CxOOQ`'#E]'#E]O,mQbO'#E[OOQ`'#Dl'#DlOOQ`'#Dp'#DpO,tQRO'#DxOOQ`'#E['#E[O-YQQO'#EZOOQ`'#EZ'#EZOOQ`'#Dz'#DzQYQbOOO-bQbO'#DWO-mQbO'#DhO.bQQO'#EpO-mQbO'#DnO-mQbO'#ErO.gQbO'#DUOOQa'#E`'#E`OOQ`'#Df'#DfOOQ`'#Eo'#EoOOQ`'#ES'#ESO.qQbO,59cO/eQbO'#DPO/mQWO'#DQOOOO'#Ec'#EcOOOO'#D{'#D{O0ROSO,59iOOQa,59i,59iOOQ`'#D|'#D|O0aQbO,5:OO0hQQO,59oOOQa,5:O,5:OO0sQbO,5:OO0}QbO,5:eO-mQbO,59hO-mQbO,59hO-mQbO,59hO-mQbO,5:PO-mQbO,5:PO-mQbO,5:PO1bQRO,59fO1iQRO,59fO1zQRO,59fO1uQQO,59fO2VQQO,59fO2_ObO,59dO2jQbO'#ETO2uQbO,59bO0}QbO,5:dOOQ`,5:u,5:uOOQ`-E7x-E7xOOQ`'#D}'#D}O3^QbO'#DXO3iQbO'#DYOOQO'#EO'#EOO3aQQO'#DXO3wQQO,59rO4hQRO,5:SO4oQRO,5:SO4zQbO,5;[O5bQcO,5:YO6WQcO,5:YO6hQcO,5:YO6rQRO,5;^O6yQRO,5;^OOQ`,59p,59pOOQ`-E8Q-E8QOOOO,59k,59kOOOO,59l,59lOOOO-E7y-E7yOOQa1G/T1G/TOOQ`-E7z-E7zO7UQQO1G/ZOOQa1G/j1G/jO7aQbO1G/jOOQO'#EQ'#EQO7UQQO1G/ZOOQa1G/Z1G/ZOOQ`'#ER'#ERO7aQbO1G/jOOQ`1G0P1G0POOQa1G/S1G/SO8YQcO1G/SO8dQcO1G/SO8nQcO1G/SOOQa1G/k1G/kO:^QcO1G/kO:eQcO1G/kO:lQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O:sQbO'#CwOOQ`,5:o,5:oOOQ`-E8R-E8ROOQ`1G0O1G0OOOQ`-E7{-E7{O;QQQO,59sOOQO,59t,59tOOQO-E7|-E7|O;YQbO1G/^O;pQbO1G/nOPQbO7+$xO[QbO7+%YO>uQbO7+%YO>zQbO'#D]O?PQQO'#D`OOQ`7+&b7+&bO?UQbO7+&bO?ZQbO7+&bOOQ`'#EP'#EPO?cQQO'#EPO?hQbO'#ElOOQ`'#D_'#D_O@_QbO7+&cOOQ`'#Dr'#DrO@jQbO7+&dO@oQbO7+&eOOQa<OAN>OOCXQbOAN>OOC^QbOAN>OOOQ`AN>`AN>`OCfQbO1G/cO>[QbO1G/fOOQ`1G/f1G/fOOQ`AN?hAN?hOOQ`AN?iAN?iOC|QbOAN?iOOQ`'#EU'#EUODRQbOAN?kO-mQbO'#DtOD^QQO'#DvOOQ`AN?kAN?kODcQbOAN?kOOQ`G23jG23jODhQbOG23jODmQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25TG25TOOQ`-E8S-E8SOOQ`G25VG25VOEZQbOG25VOE`QRO,5:`OEgQRO,5:`OErQQO,5:bOOQ`LD)ULD)UOOQ`<[QbO1G/|O@oQbO7+%fOOQ`7+%h7+%hOOQ`<PQbO7+$xO[QbO7+%YO>uQbO7+%YO>zQbO'#D]O?PQQO'#D`OOQ`7+&_7+&_O?UQbO7+&_O?ZQbO7+&_OOQ`'#D|'#D|O?cQQO'#D|O?hQbO'#EiOOQ`'#D_'#D_O@_QbO7+&`O@jQbO7+&aO@oQbO7+&bOOQa<OAN>OOCXQbOAN>OOC^QbOAN>OOOQ`AN>`AN>`OCfQbO1G/cO>[QbO1G/fOOQ`1G/f1G/fOOQ`AN?eAN?eOOQ`AN?fAN?fOC|QbOAN?fOOQ`'#ER'#ERODRQbOAN?hO-mQbO'#DqOD^QQO'#DsOOQ`AN?hAN?hODcQbOAN?hOOQ`G23jG23jODhQbOG23jODmQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25QG25QOOQ`-E8P-E8POOQ`G25SG25SOEZQbOG25SOE`QRO,5:]OEgQRO,5:]OErQQO,5:_OOQ`LD)ULD)UOOQ`<[QbO1G/yO@oQbO7+%cOOQ`7+%e7+%eOOQ`<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!{YOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!|YOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!|YOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#W~~'aO#U~U'hUrS#RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#bQOt#{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#^QrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#X~U8vU#aQrSOt#{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[#YWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#[WrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#ZWrSOt#{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!xYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#[QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!yYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!yYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#T~~'aO#R~U'hUrS#OQOt#{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#ZQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#YQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#U~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[#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#XWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#WWrSOt#{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: 1700
diff --git a/src/parser/tests/basics.test.ts b/src/parser/tests/basics.test.ts
index d890f91..47be1bd 100644
--- a/src/parser/tests/basics.test.ts
+++ b/src/parser/tests/basics.test.ts
@@ -752,7 +752,7 @@ Assign
EqEq ==
Number 5
colon :
- ThenBlock
+ MultiLineBlock
Boolean true
keyword end
keyword end
@@ -794,7 +794,7 @@ Assign
EqEq ==
Number 5
colon :
- ThenBlock
+ MultiLineBlock
Boolean true
keyword end
keyword end
diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts
index a62d74a..69eb903 100644
--- a/src/parser/tests/control-flow.test.ts
+++ b/src/parser/tests/control-flow.test.ts
@@ -12,7 +12,7 @@ describe('if/elseif/else', () => {
EqEq ==
Number 1
colon :
- SingleLineThenBlock
+ SingleLineBlock
String
StringFragment cool
keyword end
@@ -26,7 +26,7 @@ describe('if/elseif/else', () => {
keyword if
Identifier x
colon :
- SingleLineThenBlock
+ SingleLineBlock
Number 2
keyword end
`)
@@ -44,7 +44,7 @@ describe('if/elseif/else', () => {
Lt <
Number 9
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier yes
keyword end
@@ -61,13 +61,13 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-else
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier x
ElseExpr
keyword else
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier y
keyword end
@@ -84,14 +84,14 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-elseif
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword elseif
Identifier another-condition
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier y
keyword end
@@ -112,27 +112,27 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-elseif-else
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword elseif
Identifier another-condition
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier y
ElseIfExpr
keyword elseif
Identifier yet-another-condition
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier z
ElseExpr
keyword else
colon :
- ThenBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier oh-no
keyword end
@@ -148,7 +148,7 @@ describe('if/elseif/else', () => {
keyword if
Boolean true
colon :
- SingleLineThenBlock
+ SingleLineBlock
Number 2
keyword end
`)
diff --git a/src/parser/tests/exceptions.test.ts b/src/parser/tests/exceptions.test.ts
index 039a279..105bab0 100644
--- a/src/parser/tests/exceptions.test.ts
+++ b/src/parser/tests/exceptions.test.ts
@@ -12,14 +12,14 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
- TryBlock
+ MultiLineBlock
FunctionCall
Identifier handle-error
PositionalArg
@@ -37,13 +37,13 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier do-work
FinallyExpr
keyword finally
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@@ -61,14 +61,14 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
- TryBlock
+ MultiLineBlock
FunctionCall
Identifier handle-error
PositionalArg
@@ -76,7 +76,7 @@ describe('try/catch/finally/throw', () => {
FinallyExpr
keyword finally
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@@ -199,7 +199,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier e
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier empty-string
keyword end
@@ -227,7 +227,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier close-resources
keyword end
@@ -259,7 +259,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier err
colon :
- TryBlock
+ MultiLineBlock
FunctionCall
Identifier log
PositionalArg
@@ -269,7 +269,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
- TryBlock
+ MultiLineBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
--
2.50.1
From 950eef0e69f122eb772d22f311e4bc85c9a310d0 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 20:05:27 -0800
Subject: [PATCH 04/12] no more single vs multiline blocks
---
src/compiler/compiler.ts | 3 +-
src/compiler/utils.ts | 2 +-
src/parser/shrimp.grammar | 38 +++++-------------
src/parser/shrimp.terms.ts | 19 +++++----
src/parser/shrimp.ts | 18 ++++-----
src/parser/tests/basics.test.ts | 4 +-
src/parser/tests/control-flow.test.ts | 36 +++++++++---------
src/parser/tests/exceptions.test.ts | 55 +++++++++++++++------------
8 files changed, 80 insertions(+), 95 deletions(-)
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index f872e17..86cb600 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -389,8 +389,7 @@ export class Compiler {
return instructions
}
- case terms.MultiLineBlock:
- case terms.SingleLineBlock: {
+ case terms.Block: {
const children = getAllChildren(node)
const instructions: ProgramItem[] = []
diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts
index 5738cac..893c5eb 100644
--- a/src/compiler/utils.ts
+++ b/src/compiler/utils.ts
@@ -309,7 +309,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
export const getTryExprParts = (node: SyntaxNode, input: string) => {
const children = getAllChildren(node)
- // First child is always 'try' keyword, second is colon, third is MultiLineBlock or statement
+ // First child is always 'try' keyword, second is colon, third is Block
const [tryKeyword, _colon, tryBlock, ...rest] = children
if (!tryKeyword || !tryBlock) {
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index e8787cb..ce0fa45 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -81,15 +81,11 @@ pipeOperand {
}
WhileExpr {
- while (ConditionalOp | expression) colon (newlineOrSemicolon MultiLineBlock | SingleLineBlock) end
+ while (ConditionalOp | expression) colon Block end
}
-SingleLineBlock {
- consumeToTerminator
-}
-
-MultiLineBlock {
- block
+Block {
+ consumeToTerminator | newlineOrSemicolon block
}
FunctionCallOrIdentifier {
@@ -121,43 +117,27 @@ FunctionDef {
}
IfExpr {
- singleLineIf | multilineIf
-}
-
-singleLineIf {
- if (ConditionalOp | expression) colon SingleLineBlock end
-}
-
-multilineIf {
- if (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock ElseIfExpr* ElseExpr? end
+ if (ConditionalOp | expression) colon Block ElseIfExpr* ElseExpr? end
}
ElseIfExpr {
- elseif (ConditionalOp | expression) colon newlineOrSemicolon MultiLineBlock
+ elseif (ConditionalOp | expression) colon Block
}
ElseExpr {
- else colon newlineOrSemicolon MultiLineBlock
+ else colon Block
}
TryExpr {
- singleLineTry | multilineTry
-}
-
-singleLineTry {
- try colon consumeToTerminator CatchExpr? FinallyExpr? end
-}
-
-multilineTry {
- try colon newlineOrSemicolon MultiLineBlock CatchExpr? FinallyExpr? end
+ try colon Block CatchExpr? FinallyExpr? end
}
CatchExpr {
- catch Identifier colon (newlineOrSemicolon MultiLineBlock | consumeToTerminator)
+ catch Identifier colon Block
}
FinallyExpr {
- finally colon (newlineOrSemicolon MultiLineBlock | consumeToTerminator)
+ finally colon Block
}
Throw {
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index 743e237..a844f1f 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -47,19 +47,18 @@ export const
Null = 45,
colon = 46,
CatchExpr = 47,
- keyword = 70,
- MultiLineBlock = 49,
+ keyword = 69,
+ Block = 49,
FinallyExpr = 50,
Underscore = 53,
Array = 54,
ConditionalOp = 55,
PositionalArg = 56,
WhileExpr = 58,
- SingleLineBlock = 60,
- TryExpr = 61,
- Throw = 63,
- IfExpr = 65,
- ElseIfExpr = 67,
- ElseExpr = 69,
- CompoundAssign = 71,
- Assign = 72
+ TryExpr = 60,
+ Throw = 62,
+ IfExpr = 64,
+ ElseIfExpr = 66,
+ ElseExpr = 68,
+ CompoundAssign = 70,
+ Assign = 71
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index 84be190..7a852b9 100644
--- a/src/parser/shrimp.ts
+++ b/src/parser/shrimp.ts
@@ -4,14 +4,14 @@ 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, elseif:136, else:140}
+const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:122, throw:126, if:130, elseif:134, else:138}
export const parser = LRParser.deserialize({
version: 14,
- states: ":jQYQbOOO#wQcO'#C{O$tOSO'#C}OOQa'#DT'#DTO%}QbO'#DdO'`QcO'#E^OOQa'#E^'#E^O(cQcO'#E^O)eQcO'#E]O)xQRO'#C|O+UQcO'#EXO+fQcO'#EXO+pQbO'#CzO,hOpO'#CxOOQ`'#EY'#EYO,mQbO'#EXOOQ`'#Dk'#DkOOQ`'#Do'#DoO,tQRO'#DuOOQ`'#EX'#EXO-YQQO'#EWOOQ`'#EW'#EWOOQ`'#Dw'#DwQYQbOOO-bQbO'#DWO-mQbO'#DhO.bQQO'#EmO-mQbO'#DmO-mQbO'#EoO.gQbO'#DUOOQa'#E]'#E]OOQ`'#Df'#DfOOQ`'#El'#ElOOQ`'#EP'#EPO.qQbO,59cO/eQbO'#DPO/mQWO'#DQOOOO'#E`'#E`OOOO'#Dx'#DxO0ROSO,59iOOQa,59i,59iOOQ`'#Dy'#DyO0aQbO,5:OO0hQQO,59oOOQa,5:O,5:OO0sQbO,5:OO0}QbO,5:bO-mQbO,59hO-mQbO,59hO-mQbO,59hO-mQbO,5:PO-mQbO,5:PO-mQbO,5:PO1bQRO,59fO1iQRO,59fO1zQRO,59fO1uQQO,59fO2VQQO,59fO2_ObO,59dO2jQbO'#EQO2uQbO,59bO0}QbO,5:aOOQ`,5:r,5:rOOQ`-E7u-E7uOOQ`'#Dz'#DzO3^QbO'#DXO3iQbO'#DYOOQO'#D{'#D{O3aQQO'#DXO3wQQO,59rO4hQRO,5:SO4oQRO,5:SO4zQbO,5;XO5bQcO,5:XO6WQcO,5:XO6hQcO,5:XO6rQRO,5;ZO6yQRO,5;ZOOQ`,59p,59pOOQ`-E7}-E7}OOOO,59k,59kOOOO,59l,59lOOOO-E7v-E7vOOQa1G/T1G/TOOQ`-E7w-E7wO7UQQO1G/ZOOQa1G/j1G/jO7aQbO1G/jOOQO'#D}'#D}O7UQQO1G/ZOOQa1G/Z1G/ZOOQ`'#EO'#EOO7aQbO1G/jOOQ`1G/|1G/|OOQa1G/S1G/SO8YQcO1G/SO8dQcO1G/SO8nQcO1G/SOOQa1G/k1G/kO:^QcO1G/kO:eQcO1G/kO:lQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O:sQbO'#CwOOQ`,5:l,5:lOOQ`-E8O-E8OOOQ`1G/{1G/{OOQ`-E7x-E7xO;QQQO,59sOOQO,59t,59tOOQO-E7y-E7yO;YQbO1G/^O;pQbO1G/nOPQbO7+$xO[QbO7+%YO>uQbO7+%YO>zQbO'#D]O?PQQO'#D`OOQ`7+&_7+&_O?UQbO7+&_O?ZQbO7+&_OOQ`'#D|'#D|O?cQQO'#D|O?hQbO'#EiOOQ`'#D_'#D_O@_QbO7+&`O@jQbO7+&aO@oQbO7+&bOOQa<OAN>OOCXQbOAN>OOC^QbOAN>OOOQ`AN>`AN>`OCfQbO1G/cO>[QbO1G/fOOQ`1G/f1G/fOOQ`AN?eAN?eOOQ`AN?fAN?fOC|QbOAN?fOOQ`'#ER'#ERODRQbOAN?hO-mQbO'#DqOD^QQO'#DsOOQ`AN?hAN?hODcQbOAN?hOOQ`G23jG23jODhQbOG23jODmQbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25QG25QOOQ`-E8P-E8POOQ`G25SG25SOEZQbOG25SOE`QRO,5:]OEgQRO,5:]OErQQO,5:_OOQ`LD)ULD)UOOQ`<[QbO1G/yO@oQbO7+%cOOQ`7+%e7+%eOOQ`<WQQO7+$uOOQa7+$u7+$uO>cQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{O>mQbO7+$xO>xQbO7+$xO?iQbO7+%YOOQ`'#D{'#D{O?nQQO'#D{O?sQbO'#EhOOQ`,59y,59yO@jQbO'#D]O@oQQO'#D`OOQ`7+%[7+%[O@tQbO7+%[O@yQbO7+%[OARQbO7+%`OOQa<OAN>OOBgQbOAN>OOBlQbOAN>OO5pQbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`-E8O-E8OOOQ`AN>fAN>fOBtQbOAN>fOByQRO,5:[OCQQRO,5:[O5pQbO,5:^OOQ`G23jG23jOC]QbOG23jOOQ`7+$}7+$}OOQ`G24QG24QO5pQbO1G/vOOQ`1G/x1G/xOOQ`LD)ULD)UOOQ`7+%b7+%b",
+ stateData: "Ce~O!wOS!xOS~OdPOe`OfUOg]OhfOmUOuUOvUO}UO!]gO!_hO!aiO!cjO!}[O#QQO#XRO#YSO#ZcO~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO!ZoX#ZoX#`oX#^oX!QoX!ToX!UoX!eoX!goX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~P!dOrsO#QvO#SqO#TrO~OdlOfUOg]OmUOuUOvUOykO}UO!}[O#QQO#XRO#YSO#ZwO~O#]zO~P%YOP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#`#PX!Q#PX!T#PX!U#PX!e#PX!g#PX~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO#^#PX~P&[OV|O~P&[OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!e!zX!g!zX~P(vOP!OOQ!OOR!POS!POT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO^}O~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!e!zX!g!zX~OP!OOQ!OOR!POS!PO~P+VOT!ROU!SO~P+VOdPOfUOg]OhfOmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!|!YO~O!Z!ZO~P+VOV|O_!]O`!]Oa!]Ob!]Oc!]O~O#Z!^O#`!^O~Od!`Oy!bO!O{P~OdlOfUOg]OmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!O!hO~OhfO!V!nO~P.VOhfOykO!VmO!Zka#Zka#`ka#^ka!Qka!Tka!Uka!eka!gka~P.VOd!pO!}[O~O#Q!qO#S!qO#T!qO#U!qO#V!qO#W!qO~OrsO#Q!sO#SqO#TrO~O#]!vO~P%YOykO#Z!xO#]!zO~O#Z!{O#]!vO~P.VOe`O!]gO!_hO!aiO!cjO~P,YO#^#WO~P(vOP!OOQ!OOR!POS!PO#^#WO~OT!ROU!SO#^#WO~O!Z!ZO#^#WO~Od#XOm#XO!}[O~Od#YOg]O!}[O~O!Z!ZO#Zja#`ja#^ja!Qja!Tja!Uja!eja!gja~Od!`Oy!bO!O{X~Om#aOu#aO}#aO#QQO~O!O#cO~OT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~O!O#dO~P4rOT!ROU!SO!O#dO~Oe`O!]gO!_hO!aiO!cjO#Z#fO~P,YO#Z!`a#`!`a!Q!`a!T!`a!U!`a!e!`a!g!`a~P*[O#Z!`a#`!`a!Q!`a!T!`a!U!`a!e!`a!g!`a~OP!OOQ!OOR!POS!PO~P6qOT!ROU!SO~P6qO!O#hO~P4rOT!ROU!SO!O#hO~OykO#Z!xO#]#jO~O#Z!{O#]#lO~P.VO^}ORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!epi!gpi~OPpiQpi~P8mOP!OOQ!OO~P8mOP!OOQ!OORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!epi!gpi~OW!QOX!QOY!QOZ!QO[!QO]!QOT!Xi#Z!Xi#`!Xi#^!Xi!O!Xi!Q!Xi!T!Xi!U!Xi!e!Xi!g!Xi~OU!SO~P:nOU!SO~P;QOU!Xi~P:nOhfOykO!VmO~P.VOy!bO!O{a~Oe`O!]gO!_hO!aiO!cjO#Z#pO~P,YOe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[P!T#[P!U#[P!e#[P!g#[P~P,YO!Q#vO!T#wO!U#xO~OykO#Z!xO#]#|O~O#Z!{O#]#}O~P.VO!Q#vO!T#wO!U$OO~Oe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[P!T#[P!U#[P~P,YO!U$SO~O#Z$TO~Oe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[X!T#[X!U#[X!e#[X!g#[X~P,YOd$VO~O!O$WO~O!U$XO~O!T#wO!U$XO~O!U$_O!e$]O!g$^O~O!U$aO~O!T#wO!U$aO~O!Q#vO!T#wO!U$aO~O!O$dO~O!U$fO~O!U$hO!e$]O!g$^O~O!O$lO~O!U$hO~O!U$mO~O!T#wO!U$mO~O!U$pO~O!O$qO~P4rOT!ROU!SO!O$qO~O!U$sO~Omv~",
+ goto: "3b#`PPPPPPPPPPPPPPPPPPPPPPPPPP#a#u$YP%X#u&^&{P'x'xPP'|(vP)Z)y)|PP*SP*]*rPPP+S,O,vP,}P,}P,}P,}P-`P-dP,},}-j-p-v-|.S.^.e.o.y/S/ZPPPP/a/e0QPP0i2RP3PPPPPPPPP3TPP3ZnaOe|!]!h#c#d#f#h#p#t$W$d$l$qR!W[s^O[e|!Z!]!h#c#d#f#h#p#t$W$d$l$qpPO[e|!]!h#c#d#f#h#p#t$W$d$l$q|lPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]R#Y!ZpTO[e|!]!h#c#d#f#h#p#t$W$d$l$q|UPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]Q!pqQ#X!YR#Z!ZnYOe|!]!h#c#d#f#h#p#t$W$d$l$qQ!U[Q!jiQ#O!OR#R!P!nUOPST[egijkpx{|}!O!P!Q!R!S!]!h!w!|#Y#Z#c#d#f#h#k#p#t$W$]$d$l$qR#a!bTsQu!oUOPST[egijkpx{|}!O!P!Q!R!S!]!h!w!|#Y#Z#c#d#f#h#k#p#t$W$]$d$l$qYnPTp#Y#ZQySQ!uxX!xy!u!y#inaOe|!]!h#c#d#f#h#p#t$W$d$l$qYmPTp#Y#ZQ!W[R!nkR!efX!cf!a!d#`Q#z#gQ$Q#oR$c$RQ#g!hQ#q#dQ#{#hQ$e$WQ$o$dQ$r$lR$t$qQ#y#gQ$P#oQ$Y#zS$b$Q$RR$n$c!OUPST[gijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]oVOe|!]!h#c#d#f#h#p#t$W$d$l$qnZOe|!]!h#c#d#f#h#p#t$W$d$l$qQ!V[Q!ggQ!kiQ!mjQ#S!SQ#U!RR$k$]ZnPTp#Y#ZoaOe|!]!h#c#d#f#h#p#t$W$d$l$qT$Z#{$[Q$`#{R$i$[QeOR!_eQuQR!ruQxSR!txQ!afR#_!aQ!dfQ#`!aT#b!d#`S#t#f#pR$U#tQ!yyQ#i!uT#m!y#iQ!|{Q#k!wT#n!|#kWpPT#Y#ZR!opS![_!XR#]![Q$[#{R$g$[TdOeSbOeQ!}|Q#^!]^#e!h#d#h$W$d$l$qQ#o#cV#s#f#p#tn_Oe|!]!h#c#d#f#h#p#t$W$d$l$qQ!X[R#[!ZpXO[e|!]!h#c#d#f#h#p#t$W$d$l$qYmPTp#Y#ZQ{SQ!fgQ!iiQ!ljQ!nkQ!wxW!{{!w!|#kQ#O}Q#P!OQ#Q!PQ#S!QQ#T!RQ#V!SR$j$]nWOe|!]!h#c#d#f#h#p#t$W$d$l$q|lPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]R!T[TtQuQ#u#fR$R#pZoPTp#Y#Z",
+ nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr keyword CompoundAssign Assign",
+ maxTerm: 108,
context: trackScope,
nodeProps: [
["closedBy", 46,"end"]
@@ -19,9 +19,9 @@ export const parser = LRParser.deserialize({
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 11,
- tokenData: "C_~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O+X!O!P#{!P!Q-n!Q![)S![!]6Z!]!^%T!^!}#{!}#O6t#O#P8j#P#Q8o#Q#R#{#R#S9Y#S#T#{#T#Y,Y#Y#Z9s#Z#b,Y#b#c>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!xYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#[QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!yYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!yYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#T~~'aO#R~U'hUrS#OQOt#{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#ZQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#YQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#U~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[#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#XWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#WWrSOt#{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: 1700
+ tokenPrec: 1582
})
diff --git a/src/parser/tests/basics.test.ts b/src/parser/tests/basics.test.ts
index 47be1bd..b4092a9 100644
--- a/src/parser/tests/basics.test.ts
+++ b/src/parser/tests/basics.test.ts
@@ -752,7 +752,7 @@ Assign
EqEq ==
Number 5
colon :
- MultiLineBlock
+ Block
Boolean true
keyword end
keyword end
@@ -794,7 +794,7 @@ Assign
EqEq ==
Number 5
colon :
- MultiLineBlock
+ Block
Boolean true
keyword end
keyword end
diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts
index 69eb903..9319682 100644
--- a/src/parser/tests/control-flow.test.ts
+++ b/src/parser/tests/control-flow.test.ts
@@ -12,7 +12,7 @@ describe('if/elseif/else', () => {
EqEq ==
Number 1
colon :
- SingleLineBlock
+ Block
String
StringFragment cool
keyword end
@@ -26,7 +26,7 @@ describe('if/elseif/else', () => {
keyword if
Identifier x
colon :
- SingleLineBlock
+ Block
Number 2
keyword end
`)
@@ -44,7 +44,7 @@ describe('if/elseif/else', () => {
Lt <
Number 9
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier yes
keyword end
@@ -61,13 +61,13 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-else
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier x
ElseExpr
keyword else
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier y
keyword end
@@ -84,14 +84,14 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-elseif
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword elseif
Identifier another-condition
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier y
keyword end
@@ -112,27 +112,27 @@ describe('if/elseif/else', () => {
keyword if
Identifier with-elseif-else
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword elseif
Identifier another-condition
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier y
ElseIfExpr
keyword elseif
Identifier yet-another-condition
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier z
ElseExpr
keyword else
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier oh-no
keyword end
@@ -148,7 +148,7 @@ describe('if/elseif/else', () => {
keyword if
Boolean true
colon :
- SingleLineBlock
+ Block
Number 2
keyword end
`)
@@ -162,7 +162,7 @@ describe('while', () => {
keyword while
Boolean true
colon :
- SingleLineBlock
+ Block
Boolean true
keyword end`)
})
@@ -176,7 +176,7 @@ describe('while', () => {
Gt >
Number 0
colon :
- SingleLineBlock
+ Block
Boolean true
keyword end`)
})
@@ -203,7 +203,7 @@ describe('while', () => {
Lt <
Number 1000
colon :
- SingleLineBlock
+ Block
Boolean true
keyword end`)
})
@@ -217,7 +217,7 @@ describe('while', () => {
keyword while
Boolean true
colon :
- MultiLineBlock
+ Block
Boolean true
keyword end`)
})
@@ -234,7 +234,7 @@ describe('while', () => {
Gt >
Number 0
colon :
- MultiLineBlock
+ Block
Boolean true
keyword end`)
})
@@ -264,7 +264,7 @@ describe('while', () => {
Lt <
Number 1000
colon :
- MultiLineBlock
+ Block
Boolean true
keyword end`)
})
diff --git a/src/parser/tests/exceptions.test.ts b/src/parser/tests/exceptions.test.ts
index 105bab0..e89c80e 100644
--- a/src/parser/tests/exceptions.test.ts
+++ b/src/parser/tests/exceptions.test.ts
@@ -12,14 +12,14 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
- MultiLineBlock
+ Block
FunctionCall
Identifier handle-error
PositionalArg
@@ -37,13 +37,13 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier do-work
FinallyExpr
keyword finally
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@@ -61,14 +61,14 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
- MultiLineBlock
+ Block
FunctionCall
Identifier handle-error
PositionalArg
@@ -76,7 +76,7 @@ describe('try/catch/finally/throw', () => {
FinallyExpr
keyword finally
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@@ -91,15 +91,17 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- FunctionCall
- Identifier parse-number
- PositionalArg
- Identifier input
+ Block
+ FunctionCall
+ Identifier parse-number
+ PositionalArg
+ Identifier input
CatchExpr
keyword catch
Identifier err
colon :
- Number 0
+ Block
+ Number 0
keyword end
`)
})
@@ -109,18 +111,21 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- FunctionCallOrIdentifier
- Identifier work
+ Block
+ FunctionCallOrIdentifier
+ Identifier work
CatchExpr
keyword catch
Identifier err
colon :
- Number 0
+ Block
+ Number 0
FinallyExpr
keyword finally
colon :
- FunctionCallOrIdentifier
- Identifier cleanup
+ Block
+ FunctionCallOrIdentifier
+ Identifier cleanup
keyword end
`)
})
@@ -164,13 +169,15 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
- FunctionCallOrIdentifier
- Identifier work
+ Block
+ FunctionCallOrIdentifier
+ Identifier work
CatchExpr
keyword catch
Identifier err
colon :
- Number 0
+ Block
+ Number 0
keyword end
`)
})
@@ -199,7 +206,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier e
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier empty-string
keyword end
@@ -227,7 +234,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier close-resources
keyword end
@@ -259,7 +266,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier err
colon :
- MultiLineBlock
+ Block
FunctionCall
Identifier log
PositionalArg
@@ -269,7 +276,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
- MultiLineBlock
+ Block
FunctionCallOrIdentifier
Identifier cleanup
keyword end
--
2.50.1
From f1eaafee195ea6ff849a39dd7145dc948046100a Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 20:11:59 -0800
Subject: [PATCH 05/12] just in case
---
src/compiler/tests/while.test.ts | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/compiler/tests/while.test.ts b/src/compiler/tests/while.test.ts
index 2685208..c3afdb9 100644
--- a/src/compiler/tests/while.test.ts
+++ b/src/compiler/tests/while.test.ts
@@ -34,4 +34,15 @@ describe('while', () => {
b`)
.toEvaluateTo(100)
})
+
+ test('returns value', () => {
+ expect(`
+ a = 0
+ ret = while a < 10:
+ a += 1
+ done
+ end
+ ret`)
+ .toEvaluateTo('done')
+ })
})
\ No newline at end of file
--
2.50.1
From e68624b6081a48e0eccfc3346f3bed59da99e7a8 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Mon, 3 Nov 2025 20:16:15 -0800
Subject: [PATCH 06/12] elseif -> else if
---
src/compiler/tests/compiler.test.ts | 12 +++++------
src/compiler/utils.ts | 2 +-
src/parser/shrimp.grammar | 3 +--
src/parser/shrimp.terms.ts | 6 +++---
src/parser/shrimp.ts | 18 ++++++++---------
src/parser/tests/control-flow.test.ts | 29 +++++++++++++++------------
6 files changed, 36 insertions(+), 34 deletions(-)
diff --git a/src/compiler/tests/compiler.test.ts b/src/compiler/tests/compiler.test.ts
index 9418f0b..743f738 100644
--- a/src/compiler/tests/compiler.test.ts
+++ b/src/compiler/tests/compiler.test.ts
@@ -154,18 +154,18 @@ describe('compiler', () => {
end`).toEvaluateTo('white')
})
- test('if elseif', () => {
+ test('if else if', () => {
expect(`if false:
boromir
- elseif true:
+ else if true:
frodo
end`).toEvaluateTo('frodo')
})
- test('if elseif else', () => {
+ test('if else if else', () => {
expect(`if false:
destroyed
- elseif true:
+ else if true:
fire
else:
darkness
@@ -173,9 +173,9 @@ describe('compiler', () => {
expect(`if false:
king
- elseif false:
+ else if false:
elf
- elseif true:
+ else if true:
dwarf
else:
scattered
diff --git a/src/compiler/utils.ts b/src/compiler/utils.ts
index 893c5eb..ed0dfcc 100644
--- a/src/compiler/utils.ts
+++ b/src/compiler/utils.ts
@@ -210,7 +210,7 @@ export const getIfExprParts = (node: SyntaxNode, input: string) => {
}
elseThenBlock = parts.at(-1)
} else if (child.type.id === terms.ElseIfExpr) {
- const [_keyword, conditional, _colon, thenBlock] = parts
+ const [_else, _if, conditional, _colon, thenBlock] = parts
if (!conditional || !thenBlock) {
const names = parts.map((p) => p.type.name).join(', ')
const message = `ElseIfExpr expected conditional and thenBlock, got ${names}`
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index ce0fa45..4ed609a 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -31,7 +31,6 @@ end { @specialize[@name=keyword] }
while { @specialize[@name=keyword] }
if { @specialize[@name=keyword] }
else { @specialize[@name=keyword] }
-elseif { @specialize[@name=keyword] }
try { @specialize[@name=keyword] }
catch { @specialize[@name=keyword] }
finally { @specialize[@name=keyword] }
@@ -121,7 +120,7 @@ IfExpr {
}
ElseIfExpr {
- elseif (ConditionalOp | expression) colon Block
+ else if (ConditionalOp | expression) colon Block
}
ElseExpr {
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index a844f1f..498ae00 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -47,7 +47,7 @@ export const
Null = 45,
colon = 46,
CatchExpr = 47,
- keyword = 69,
+ keyword = 67,
Block = 49,
FinallyExpr = 50,
Underscore = 53,
@@ -60,5 +60,5 @@ export const
IfExpr = 64,
ElseIfExpr = 66,
ElseExpr = 68,
- CompoundAssign = 70,
- Assign = 71
+ CompoundAssign = 69,
+ Assign = 70
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index 7a852b9..c25351b 100644
--- a/src/parser/shrimp.ts
+++ b/src/parser/shrimp.ts
@@ -4,14 +4,14 @@ 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:122, throw:126, if:130, elseif:134, else:138}
+const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:122, throw:126, if:130, else:134}
export const parser = LRParser.deserialize({
version: 14,
- states: "7vQYQbOOO#}QcO'#C{O$zOSO'#C}OOQa'#DT'#DTO&TQbO'#DdO'lQcO'#E]OOQa'#E]'#E]O(oQcO'#E]O)qQcO'#E[O*[QRO'#C|O+nQcO'#EWO,OQcO'#EWO,YQbO'#CzO-QOpO'#CxOOQ`'#EX'#EXO-VQbO'#EWO-^QRO'#DtOOQ`'#EW'#EWO-rQQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO-zQbO'#DWO.VQbO'#DhO.zQQO'#DjO.VQbO'#DlO.VQbO'#DnO/PQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Ek'#EkOOQ`'#EO'#EOO/ZQbO,59cO0TQbO'#DPO0]QWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0qOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO1PQbO,5:OO1WQQO,59oOOQa,5:O,5:OO1cQbO,5:OO1mQbO,5:aO.VQbO,59hO.VQbO,59hO.VQbO,59hO.VQbO,5:PO.VQbO,5:PO.VQbO,5:PO2QQRO,59fO2XQRO,59fO2jQRO,59fO2eQQO,59fO2uQQO,59fO2}ObO,59dO3YQbO'#EPO3eQbO,59bO1mQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`'#Dy'#DyO4SQbO'#DXO4_QbO'#DYOOQO'#Dz'#DzO4VQQO'#DXO4mQQO,59rO5^QRO,5:SO5eQRO,5:SO5pQbO,5:UO6WQcO,5:WO7YQcO,5:WO7jQcO,5:WO7tQRO,5:YO7{QRO,5:YOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7v-E7vO8WQQO1G/ZOOQa1G/j1G/jO8cQbO1G/jOOQO'#D|'#D|O8WQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O8cQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO9bQcO1G/SO9lQcO1G/SO9vQcO1G/SOOQa1G/k1G/kO;rQcO1G/kO;yQcO1G/kOWQQO7+$uOOQa7+$u7+$uO>cQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{O>mQbO7+$xO>xQbO7+$xO?iQbO7+%YOOQ`'#D{'#D{O?nQQO'#D{O?sQbO'#EhOOQ`,59y,59yO@jQbO'#D]O@oQQO'#D`OOQ`7+%[7+%[O@tQbO7+%[O@yQbO7+%[OARQbO7+%`OOQa<OAN>OOBgQbOAN>OOBlQbOAN>OO5pQbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`-E8O-E8OOOQ`AN>fAN>fOBtQbOAN>fOByQRO,5:[OCQQRO,5:[O5pQbO,5:^OOQ`G23jG23jOC]QbOG23jOOQ`7+$}7+$}OOQ`G24QG24QO5pQbO1G/vOOQ`1G/x1G/xOOQ`LD)ULD)UOOQ`7+%b7+%b",
- stateData: "Ce~O!wOS!xOS~OdPOe`OfUOg]OhfOmUOuUOvUO}UO!]gO!_hO!aiO!cjO!}[O#QQO#XRO#YSO#ZcO~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO!ZoX#ZoX#`oX#^oX!QoX!ToX!UoX!eoX!goX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~P!dOrsO#QvO#SqO#TrO~OdlOfUOg]OmUOuUOvUOykO}UO!}[O#QQO#XRO#YSO#ZwO~O#]zO~P%YOP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#`#PX!Q#PX!T#PX!U#PX!e#PX!g#PX~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO#^#PX~P&[OV|O~P&[OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!e!zX!g!zX~P(vOP!OOQ!OOR!POS!POT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO^}O~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!e!zX!g!zX~OP!OOQ!OOR!POS!PO~P+VOT!ROU!SO~P+VOdPOfUOg]OhfOmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!|!YO~O!Z!ZO~P+VOV|O_!]O`!]Oa!]Ob!]Oc!]O~O#Z!^O#`!^O~Od!`Oy!bO!O{P~OdlOfUOg]OmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!O!hO~OhfO!V!nO~P.VOhfOykO!VmO!Zka#Zka#`ka#^ka!Qka!Tka!Uka!eka!gka~P.VOd!pO!}[O~O#Q!qO#S!qO#T!qO#U!qO#V!qO#W!qO~OrsO#Q!sO#SqO#TrO~O#]!vO~P%YOykO#Z!xO#]!zO~O#Z!{O#]!vO~P.VOe`O!]gO!_hO!aiO!cjO~P,YO#^#WO~P(vOP!OOQ!OOR!POS!PO#^#WO~OT!ROU!SO#^#WO~O!Z!ZO#^#WO~Od#XOm#XO!}[O~Od#YOg]O!}[O~O!Z!ZO#Zja#`ja#^ja!Qja!Tja!Uja!eja!gja~Od!`Oy!bO!O{X~Om#aOu#aO}#aO#QQO~O!O#cO~OT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~O!O#dO~P4rOT!ROU!SO!O#dO~Oe`O!]gO!_hO!aiO!cjO#Z#fO~P,YO#Z!`a#`!`a!Q!`a!T!`a!U!`a!e!`a!g!`a~P*[O#Z!`a#`!`a!Q!`a!T!`a!U!`a!e!`a!g!`a~OP!OOQ!OOR!POS!PO~P6qOT!ROU!SO~P6qO!O#hO~P4rOT!ROU!SO!O#hO~OykO#Z!xO#]#jO~O#Z!{O#]#lO~P.VO^}ORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!epi!gpi~OPpiQpi~P8mOP!OOQ!OO~P8mOP!OOQ!OORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!epi!gpi~OW!QOX!QOY!QOZ!QO[!QO]!QOT!Xi#Z!Xi#`!Xi#^!Xi!O!Xi!Q!Xi!T!Xi!U!Xi!e!Xi!g!Xi~OU!SO~P:nOU!SO~P;QOU!Xi~P:nOhfOykO!VmO~P.VOy!bO!O{a~Oe`O!]gO!_hO!aiO!cjO#Z#pO~P,YOe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[P!T#[P!U#[P!e#[P!g#[P~P,YO!Q#vO!T#wO!U#xO~OykO#Z!xO#]#|O~O#Z!{O#]#}O~P.VO!Q#vO!T#wO!U$OO~Oe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[P!T#[P!U#[P~P,YO!U$SO~O#Z$TO~Oe`O!]gO!_hO!aiO!cjO#Z#rO!Q#[X!T#[X!U#[X!e#[X!g#[X~P,YOd$VO~O!O$WO~O!U$XO~O!T#wO!U$XO~O!U$_O!e$]O!g$^O~O!U$aO~O!T#wO!U$aO~O!Q#vO!T#wO!U$aO~O!O$dO~O!U$fO~O!U$hO!e$]O!g$^O~O!O$lO~O!U$hO~O!U$mO~O!T#wO!U$mO~O!U$pO~O!O$qO~P4rOT!ROU!SO!O$qO~O!U$sO~Omv~",
- goto: "3b#`PPPPPPPPPPPPPPPPPPPPPPPPPP#a#u$YP%X#u&^&{P'x'xPP'|(vP)Z)y)|PP*SP*]*rPPP+S,O,vP,}P,}P,}P,}P-`P-dP,},}-j-p-v-|.S.^.e.o.y/S/ZPPPP/a/e0QPP0i2RP3PPPPPPPPP3TPP3ZnaOe|!]!h#c#d#f#h#p#t$W$d$l$qR!W[s^O[e|!Z!]!h#c#d#f#h#p#t$W$d$l$qpPO[e|!]!h#c#d#f#h#p#t$W$d$l$q|lPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]R#Y!ZpTO[e|!]!h#c#d#f#h#p#t$W$d$l$q|UPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]Q!pqQ#X!YR#Z!ZnYOe|!]!h#c#d#f#h#p#t$W$d$l$qQ!U[Q!jiQ#O!OR#R!P!nUOPST[egijkpx{|}!O!P!Q!R!S!]!h!w!|#Y#Z#c#d#f#h#k#p#t$W$]$d$l$qR#a!bTsQu!oUOPST[egijkpx{|}!O!P!Q!R!S!]!h!w!|#Y#Z#c#d#f#h#k#p#t$W$]$d$l$qYnPTp#Y#ZQySQ!uxX!xy!u!y#inaOe|!]!h#c#d#f#h#p#t$W$d$l$qYmPTp#Y#ZQ!W[R!nkR!efX!cf!a!d#`Q#z#gQ$Q#oR$c$RQ#g!hQ#q#dQ#{#hQ$e$WQ$o$dQ$r$lR$t$qQ#y#gQ$P#oQ$Y#zS$b$Q$RR$n$c!OUPST[gijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]oVOe|!]!h#c#d#f#h#p#t$W$d$l$qnZOe|!]!h#c#d#f#h#p#t$W$d$l$qQ!V[Q!ggQ!kiQ!mjQ#S!SQ#U!RR$k$]ZnPTp#Y#ZoaOe|!]!h#c#d#f#h#p#t$W$d$l$qT$Z#{$[Q$`#{R$i$[QeOR!_eQuQR!ruQxSR!txQ!afR#_!aQ!dfQ#`!aT#b!d#`S#t#f#pR$U#tQ!yyQ#i!uT#m!y#iQ!|{Q#k!wT#n!|#kWpPT#Y#ZR!opS![_!XR#]![Q$[#{R$g$[TdOeSbOeQ!}|Q#^!]^#e!h#d#h$W$d$l$qQ#o#cV#s#f#p#tn_Oe|!]!h#c#d#f#h#p#t$W$d$l$qQ!X[R#[!ZpXO[e|!]!h#c#d#f#h#p#t$W$d$l$qYmPTp#Y#ZQ{SQ!fgQ!iiQ!ljQ!nkQ!wxW!{{!w!|#kQ#O}Q#P!OQ#Q!PQ#S!QQ#T!RQ#V!SR$j$]nWOe|!]!h#c#d#f#h#p#t$W$d$l$q|lPSTgijkpx{}!O!P!Q!R!S!w!|#Y#Z#k$]R!T[TtQuQ#u#fR$R#pZoPTp#Y#Z",
- nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr keyword CompoundAssign Assign",
- maxTerm: 108,
+ states: "7|QYQbOOO#zQcO'#C{O$wOSO'#C}OOQa'#DT'#DTO&QQbO'#DdO'fQcO'#E[OOQa'#E['#E[O(iQcO'#E[O)kQcO'#EZO*RQRO'#C|O+bQcO'#EVO+rQcO'#EVO+|QbO'#CzO,tOpO'#CxOOQ`'#EW'#EWO,yQbO'#EVO-QQRO'#DsOOQ`'#EV'#EVO-fQQO'#EUOOQ`'#EU'#EUOOQ`'#Du'#DuQYQbOOO-nQbO'#DWO-yQbO'#DhO.nQQO'#DjO-yQbO'#DlO-yQbO'#DnO.sQbO'#DUOOQa'#EZ'#EZOOQ`'#Df'#DfOOQ`'#Ej'#EjOOQ`'#D}'#D}O.}QbO,59cO/tQbO'#DPO/|QWO'#DQOOOO'#E^'#E^OOOO'#Dv'#DvO0bOSO,59iOOQa,59i,59iOOQ`'#Dw'#DwO0pQbO,5:OO0wQQO,59oOOQa,5:O,5:OO1SQbO,5:OO1^QbO,5:`O-yQbO,59hO-yQbO,59hO-yQbO,59hO-yQbO,5:PO-yQbO,5:PO-yQbO,5:PO1qQRO,59fO1xQRO,59fO2ZQRO,59fO2UQQO,59fO2fQQO,59fO2nObO,59dO2yQbO'#EOO3UQbO,59bO1^QbO,5:_OOQ`,5:p,5:pOOQ`-E7s-E7sOOQ`'#Dx'#DxO3pQbO'#DXO3{QbO'#DYOOQO'#Dy'#DyO3sQQO'#DXO4ZQQO,59rO4zQRO,5:SO5RQRO,5:SO5^QbO,5:UO5tQcO,5:WO6pQcO,5:WO7QQcO,5:WO7[QRO,5:YO7cQRO,5:YOOQ`,59p,59pOOQ`-E7{-E7{OOOO,59k,59kOOOO,59l,59lOOOO-E7t-E7tOOQa1G/T1G/TOOQ`-E7u-E7uO7nQQO1G/ZOOQa1G/j1G/jO7yQbO1G/jOOQO'#D{'#D{O7nQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D|'#D|O7yQbO1G/jOOQ`1G/z1G/zOOQa1G/S1G/SO8uQcO1G/SO9PQcO1G/SO9ZQcO1G/SOOQa1G/k1G/kO;PQcO1G/kO;WQcO1G/kO;_QcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;fQbO'#CwOOQ`,5:j,5:jOOQ`-E7|-E7|OOQ`1G/y1G/yOOQ`-E7v-E7vO;sQQO,59sOOQO,59t,59tOOQO-E7w-E7wO;{QbO1G/^O5^QbO1G/nOOQ`'#D_'#D_OSQbO7+$xO>sQbO7+%YOOQ`'#Dz'#DzO>xQQO'#DzO>}QbO'#EgOOQ`,59y,59yO?qQbO'#D]O?vQQO'#D`OOQ`7+%[7+%[O?{QbO7+%[O@QQbO7+%[O@YQbO7+%`OOQa<OAN>OOAkQbOAN>OOApQbOAN>OO5^QbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`-E7}-E7}OOQ`AN>fAN>fOAxQbOAN>fO-yQbO,5:[O5^QbO,5:^OOQ`G23jG23jOA}QbOG23jOOQ`7+$}7+$}PAaQbO'#DpOOQ`G24QG24QOBSQRO1G/vOBZQRO1G/vOOQ`1G/x1G/xOOQ`LD)ULD)UO5^QbO7+%bOOQ`<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!vYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!wYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!wYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#R~~'aO#P~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#XQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#WQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#S~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[#TWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#UWrSOt#{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: 1582
+ tokenPrec: 1540
})
diff --git a/src/parser/tests/control-flow.test.ts b/src/parser/tests/control-flow.test.ts
index 9319682..af0f704 100644
--- a/src/parser/tests/control-flow.test.ts
+++ b/src/parser/tests/control-flow.test.ts
@@ -2,7 +2,7 @@ import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
-describe('if/elseif/else', () => {
+describe('if/else if/else', () => {
test('parses single line if', () => {
expect(`if y == 1: 'cool' end`).toMatchTree(`
IfExpr
@@ -74,21 +74,22 @@ describe('if/elseif/else', () => {
`)
})
- test('parses multiline if with elseif', () => {
- expect(`if with-elseif:
+ test('parses multiline if with else if', () => {
+ expect(`if with-else-if:
x
- elseif another-condition:
+ else if another-condition:
y
end`).toMatchTree(`
IfExpr
keyword if
- Identifier with-elseif
+ Identifier with-else-if
colon :
Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
- keyword elseif
+ keyword else
+ keyword if
Identifier another-condition
colon :
Block
@@ -98,32 +99,34 @@ describe('if/elseif/else', () => {
`)
})
- test('parses multiline if with multiple elseif and else', () => {
- expect(`if with-elseif-else:
+ test('parses multiline if with multiple else if and else', () => {
+ expect(`if with-else-if-else:
x
- elseif another-condition:
+ else if another-condition:
y
- elseif yet-another-condition:
+ else if yet-another-condition:
z
else:
oh-no
end`).toMatchTree(`
IfExpr
keyword if
- Identifier with-elseif-else
+ Identifier with-else-if-else
colon :
Block
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
- keyword elseif
+ keyword else
+ keyword if
Identifier another-condition
colon :
Block
FunctionCallOrIdentifier
Identifier y
ElseIfExpr
- keyword elseif
+ keyword else
+ keyword if
Identifier yet-another-condition
colon :
Block
--
2.50.1
From 6f531a2ebfac99a87063fcb82a451016eac15a7a Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sat, 1 Nov 2025 11:17:39 -0700
Subject: [PATCH 07/12] ./bin/shrimp parse file
---
bin/shrimp | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/bin/shrimp b/bin/shrimp
index 49cd7f3..5706ab9 100755
--- a/bin/shrimp
+++ b/bin/shrimp
@@ -2,9 +2,11 @@
import { Compiler } from '../src/compiler/compiler'
import { colors, globals } from '../src/prelude'
+import { parser } from '../src/parser/shrimp'
+import { treeToString } from '../src/utils/tree'
import { VM, fromValue, bytecodeToString } from 'reefvm'
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
-import { randomUUID } from "crypto"
+import { randomUUID } from 'crypto'
import { spawn } from 'child_process'
import { join } from 'path'
@@ -32,6 +34,17 @@ async function compileFile(filePath: string) {
}
}
+async function parseFile(filePath: string) {
+ try {
+ const code = readFileSync(filePath, 'utf-8')
+ const tree = parser.parse(code)
+ return treeToString(tree, code)
+ } catch (error: any) {
+ console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
+ process.exit(1)
+ }
+}
+
function showHelp() {
console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell.
@@ -39,6 +52,7 @@ ${colors.bright}Usage:${colors.reset} shrimp [...args]
${colors.bright}Commands:${colors.reset}
${colors.cyan}run ${colors.yellow}./my-file.sh${colors.reset} Execute a file with Shrimp
+ ${colors.cyan}parse ${colors.yellow}./my-file.sh${colors.reset} Print parse tree for Shrimp file
${colors.cyan}bytecode ${colors.yellow}./my-file.sh${colors.reset} Print bytecode for Shrimp file
${colors.cyan}eval ${colors.yellow}'some code'${colors.reset} Evaluate a line of Shrimp code
${colors.cyan}repl${colors.reset} Start REPL
@@ -102,6 +116,16 @@ async function main() {
return
}
+ if (['parse', '-parse', '--parse', '-p'].includes(command)) {
+ const file = args[1]
+ if (!file) {
+ console.log(`${colors.bright}usage: shrimp parse ${colors.reset}`)
+ process.exit(1)
+ }
+ console.log(await parseFile(file))
+ return
+ }
+
if (['run', '-run', '--run', '-r'].includes(command)) {
const file = args[1]
if (!file) {
--
2.50.1
From 7bcd582dc68e3daf698559123d42d7b7bbac21db Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sat, 1 Nov 2025 16:31:18 -0700
Subject: [PATCH 08/12] what have i done
---
src/compiler/compiler.ts | 45 +++
src/compiler/tests/function-blocks.test.ts | 136 +++++++++
src/parser/shrimp.grammar | 5 +
src/parser/shrimp.terms.ts | 17 +-
src/parser/shrimp.ts | 18 +-
src/parser/tests/function-blocks.test.ts | 303 +++++++++++++++++++++
6 files changed, 507 insertions(+), 17 deletions(-)
create mode 100644 src/compiler/tests/function-blocks.test.ts
create mode 100644 src/parser/tests/function-blocks.test.ts
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index 86cb600..be939cf 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -404,6 +404,51 @@ export class Compiler {
return instructions
}
+ case terms.FunctionCallWithBlock: {
+ const [fn, _colon, ...block] = getAllChildren(node)
+ let instructions: ProgramItem[] = []
+
+ const fnLabel: Label = `.func_${this.fnLabelCount++}`
+ const afterLabel: Label = `.after_${fnLabel}`
+
+ instructions.push(['JUMP', afterLabel])
+ instructions.push([`${fnLabel}:`])
+ instructions.push(
+ ...block.filter(x => x.type.name !== 'keyword')
+ .map(x => this.#compileNode(x!, input))
+ .flat()
+ )
+ instructions.push(['RETURN'])
+ instructions.push([`${afterLabel}:`])
+
+ if (fn?.type.id === terms.FunctionCallOrIdentifier) {
+ instructions.push(['LOAD', input.slice(fn!.from, fn!.to)])
+ instructions.push(['MAKE_FUNCTION', [], fnLabel])
+ instructions.push(['PUSH', 1])
+ instructions.push(['PUSH', 0])
+ instructions.push(['CALL'])
+ } else if (fn?.type.id === terms.FunctionCall) {
+ let body = this.#compileNode(fn!, input)
+ const namedArgCount = (body[body.length - 2]![1] as number) * 2
+ const startSlice = body.length - namedArgCount - 3
+
+ body = [
+ ...body.slice(0, startSlice),
+ ['MAKE_FUNCTION', [], fnLabel],
+ ...body.slice(startSlice)
+ ]
+
+ // @ts-ignore
+ body[body.length - 3]![1] += 1
+ instructions.push(...body)
+
+ } else {
+ throw new Error(`FunctionCallWithBlock: Expected FunctionCallOrIdentifier or FunctionCall`)
+ }
+
+ return instructions
+ }
+
case terms.TryExpr: {
const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input)
diff --git a/src/compiler/tests/function-blocks.test.ts b/src/compiler/tests/function-blocks.test.ts
new file mode 100644
index 0000000..056affe
--- /dev/null
+++ b/src/compiler/tests/function-blocks.test.ts
@@ -0,0 +1,136 @@
+import { expect, describe, test } from 'bun:test'
+
+describe('single line function blocks', () => {
+ test('work with no args', () => {
+ expect(`trap = do x: x end; trap: true end`).toEvaluateTo(true)
+ })
+
+ test('work with one arg', () => {
+ expect(`trap = do x y: [ x (y) ] end; trap EXIT: true end`).toEvaluateTo(['EXIT', true])
+ })
+
+ test('work with named args', () => {
+ expect(`attach = do signal fn: [ signal (fn) ] end; attach signal='exit': true end`).toEvaluateTo(['exit', true])
+ })
+
+
+ test('work with dot-get', () => {
+ expect(`signals = [trap=do x y: [x (y)] end]; signals.trap 'EXIT': true end`).toEvaluateTo(['EXIT', true])
+ })
+})
+
+describe('multi line function blocks', () => {
+ test('work with no args', () => {
+ expect(`
+trap = do x: x end
+trap:
+ true
+end`).toEvaluateTo(true)
+ })
+
+ test('work with one arg', () => {
+ expect(`
+trap = do x y: [ x (y) ] end
+trap EXIT:
+ true
+end`).toEvaluateTo(['EXIT', true])
+ })
+
+ test('work with named args', () => {
+ expect(`
+attach = do signal fn: [ signal (fn) ] end
+attach signal='exit':
+ true
+end`).toEvaluateTo(['exit', true])
+ })
+
+
+ test('work with dot-get', () => {
+ expect(`
+signals = [trap=do x y: [x (y)] end]
+signals.trap 'EXIT':
+ true
+end`).toEvaluateTo(['EXIT', true])
+ })
+})
+
+describe('ribbit', () => {
+ test('head tag', () => {
+ expect(`
+ head:
+ title What up
+ meta charSet=UTF-8
+ meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover'
+ end`).toEvaluateTo(`head`, {
+ head: () => 'head'
+ })
+ })
+
+ test('li', () => {
+ expect(`
+ list:
+ li border-bottom='1px solid black' one
+ li two
+ li three
+ end`).toEvaluateTo(`list`, {
+ list: () => 'list'
+ })
+ })
+
+ test('inline expressions', () => {
+ const buffer: string[] = []
+
+ const tagBlock = async (tagName: string, props = {}, fn: Function) => {
+ const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`)
+ const space = attrs.length ? ' ' : ''
+
+ buffer.push(`<${tagName}${space}${attrs.join(' ')}>`)
+ await fn()
+ buffer.push(`${tagName}>`)
+ }
+
+ const tagCall = (tagName: string, atNamed: {}, ...args: any[]) => {
+ const attrs = Object.entries(atNamed).map(([key, value]) => `${key}="${value}"`)
+ const space = attrs.length ? ' ' : ''
+ const children = args
+ .reverse()
+ .map(a => a === null ? buffer.pop() : a)
+ .reverse().join(' ')
+ .replaceAll(' !!ribbit-nospace!! ', '')
+
+ buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}${tagName}>`)
+ }
+
+ const tag = async (tagName: string, atNamed: {}, ...args: any[]) => {
+ if (typeof args[0] === 'function')
+ await tagBlock(tagName, atNamed, args[0])
+ else
+ tagCall(tagName, atNamed, ...args)
+ }
+
+ expect(`
+ ribbit:
+ p class=container:
+ h1 class=bright style='font-family: helvetica' Heya
+ h2 man that is (b wild) (nospace) !
+ p Double the fun.
+ end
+ end`).toEvaluateTo(
+ `
+
Heya
+man that is wild!
+Double the fun.
+
`, {
+ ribbit: async (cb: Function) => {
+ await cb()
+ return buffer.join("\n")
+ },
+ p: (atNamed: {}, ...args: any[]) => tag('p', atNamed, ...args),
+ h1: (atNamed: {}, ...args: any[]) => tag('h1', atNamed, ...args),
+ h2: (atNamed: {}, ...args: any[]) => tag('h2', atNamed, ...args),
+ b: (atNamed: {}, ...args: any[]) => tag('b', atNamed, ...args),
+ nospace: () => '!!ribbit-nospace!!',
+ join: (...args: string[]) => args.join(''),
+ })
+ })
+})
\ No newline at end of file
diff --git a/src/parser/shrimp.grammar b/src/parser/shrimp.grammar
index 4ed609a..01f95b6 100644
--- a/src/parser/shrimp.grammar
+++ b/src/parser/shrimp.grammar
@@ -59,6 +59,7 @@ item {
consumeToTerminator {
PipeExpr |
WhileExpr |
+ FunctionCallWithBlock |
ambiguousFunctionCall |
TryExpr |
Throw |
@@ -87,6 +88,10 @@ Block {
consumeToTerminator | newlineOrSemicolon block
}
+FunctionCallWithBlock {
+ ambiguousFunctionCall colon Block CatchExpr? FinallyExpr? end
+}
+
FunctionCallOrIdentifier {
DotGet | Identifier
}
diff --git a/src/parser/shrimp.terms.ts b/src/parser/shrimp.terms.ts
index 498ae00..05f3d5a 100644
--- a/src/parser/shrimp.terms.ts
+++ b/src/parser/shrimp.terms.ts
@@ -47,7 +47,7 @@ export const
Null = 45,
colon = 46,
CatchExpr = 47,
- keyword = 67,
+ keyword = 68,
Block = 49,
FinallyExpr = 50,
Underscore = 53,
@@ -55,10 +55,11 @@ export const
ConditionalOp = 55,
PositionalArg = 56,
WhileExpr = 58,
- TryExpr = 60,
- Throw = 62,
- IfExpr = 64,
- ElseIfExpr = 66,
- ElseExpr = 68,
- CompoundAssign = 69,
- Assign = 70
+ FunctionCallWithBlock = 60,
+ TryExpr = 61,
+ Throw = 63,
+ IfExpr = 65,
+ ElseIfExpr = 67,
+ ElseExpr = 69,
+ CompoundAssign = 70,
+ Assign = 71
diff --git a/src/parser/shrimp.ts b/src/parser/shrimp.ts
index c25351b..5afc5d8 100644
--- a/src/parser/shrimp.ts
+++ b/src/parser/shrimp.ts
@@ -4,14 +4,14 @@ 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:122, throw:126, if:130, else:134}
+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: "7|QYQbOOO#zQcO'#C{O$wOSO'#C}OOQa'#DT'#DTO&QQbO'#DdO'fQcO'#E[OOQa'#E['#E[O(iQcO'#E[O)kQcO'#EZO*RQRO'#C|O+bQcO'#EVO+rQcO'#EVO+|QbO'#CzO,tOpO'#CxOOQ`'#EW'#EWO,yQbO'#EVO-QQRO'#DsOOQ`'#EV'#EVO-fQQO'#EUOOQ`'#EU'#EUOOQ`'#Du'#DuQYQbOOO-nQbO'#DWO-yQbO'#DhO.nQQO'#DjO-yQbO'#DlO-yQbO'#DnO.sQbO'#DUOOQa'#EZ'#EZOOQ`'#Df'#DfOOQ`'#Ej'#EjOOQ`'#D}'#D}O.}QbO,59cO/tQbO'#DPO/|QWO'#DQOOOO'#E^'#E^OOOO'#Dv'#DvO0bOSO,59iOOQa,59i,59iOOQ`'#Dw'#DwO0pQbO,5:OO0wQQO,59oOOQa,5:O,5:OO1SQbO,5:OO1^QbO,5:`O-yQbO,59hO-yQbO,59hO-yQbO,59hO-yQbO,5:PO-yQbO,5:PO-yQbO,5:PO1qQRO,59fO1xQRO,59fO2ZQRO,59fO2UQQO,59fO2fQQO,59fO2nObO,59dO2yQbO'#EOO3UQbO,59bO1^QbO,5:_OOQ`,5:p,5:pOOQ`-E7s-E7sOOQ`'#Dx'#DxO3pQbO'#DXO3{QbO'#DYOOQO'#Dy'#DyO3sQQO'#DXO4ZQQO,59rO4zQRO,5:SO5RQRO,5:SO5^QbO,5:UO5tQcO,5:WO6pQcO,5:WO7QQcO,5:WO7[QRO,5:YO7cQRO,5:YOOQ`,59p,59pOOQ`-E7{-E7{OOOO,59k,59kOOOO,59l,59lOOOO-E7t-E7tOOQa1G/T1G/TOOQ`-E7u-E7uO7nQQO1G/ZOOQa1G/j1G/jO7yQbO1G/jOOQO'#D{'#D{O7nQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D|'#D|O7yQbO1G/jOOQ`1G/z1G/zOOQa1G/S1G/SO8uQcO1G/SO9PQcO1G/SO9ZQcO1G/SOOQa1G/k1G/kO;PQcO1G/kO;WQcO1G/kO;_QcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;fQbO'#CwOOQ`,5:j,5:jOOQ`-E7|-E7|OOQ`1G/y1G/yOOQ`-E7v-E7vO;sQQO,59sOOQO,59t,59tOOQO-E7w-E7wO;{QbO1G/^O5^QbO1G/nOOQ`'#D_'#D_OSQbO7+$xO>sQbO7+%YOOQ`'#Dz'#DzO>xQQO'#DzO>}QbO'#EgOOQ`,59y,59yO?qQbO'#D]O?vQQO'#D`OOQ`7+%[7+%[O?{QbO7+%[O@QQbO7+%[O@YQbO7+%`OOQa<OAN>OOAkQbOAN>OOApQbOAN>OO5^QbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`-E7}-E7}OOQ`AN>fAN>fOAxQbOAN>fO-yQbO,5:[O5^QbO,5:^OOQ`G23jG23jOA}QbOG23jOOQ`7+$}7+$}PAaQbO'#DpOOQ`G24QG24QOBSQRO1G/vOBZQRO1G/vOOQ`1G/x1G/xOOQ`LD)ULD)UO5^QbO7+%bOOQ`<RQbO7+%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`<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!vYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!wYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!wYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#R~~'aO#P~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#XQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#WQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#S~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[#TWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#UWrSOt#{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: 1540
+ tokenPrec: 1578
})
diff --git a/src/parser/tests/function-blocks.test.ts b/src/parser/tests/function-blocks.test.ts
new file mode 100644
index 0000000..80805a9
--- /dev/null
+++ b/src/parser/tests/function-blocks.test.ts
@@ -0,0 +1,303 @@
+import { expect, describe, test } from 'bun:test'
+
+import '../shrimp.grammar' // Importing this so changes cause it to retest!
+
+describe('single line function blocks', () => {
+ test('work with no args', () => {
+ expect(`trap: echo bye bye end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCallOrIdentifier
+ Identifier trap
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+ test('work with one arg', () => {
+ expect(`trap EXIT: echo bye bye end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCall
+ Identifier trap
+ PositionalArg
+ Word EXIT
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+ test('work with named args', () => {
+ expect(`attach signal='exit': echo bye bye end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCall
+ Identifier attach
+ NamedArg
+ NamedArgPrefix signal=
+ String
+ StringFragment exit
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+
+ test('work with dot-get', () => {
+ expect(`signals = [=]; signals.trap 'EXIT': echo bye bye end`).toMatchTree(`
+ Assign
+ AssignableIdentifier signals
+ Eq =
+ Dict [=]
+ FunctionCallWithBlock
+ FunctionCall
+ DotGet
+ IdentifierBeforeDot signals
+ Identifier trap
+ PositionalArg
+ String
+ StringFragment EXIT
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+})
+
+describe('multi line function blocks', () => {
+ test('work with no args', () => {
+ expect(`
+trap:
+ echo bye bye
+end
+`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCallOrIdentifier
+ Identifier trap
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+ test('work with one arg', () => {
+ expect(`
+trap EXIT:
+ echo bye bye
+end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCall
+ Identifier trap
+ PositionalArg
+ Word EXIT
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+ test('work with named args', () => {
+ expect(`
+attach signal='exit' code=1:
+ echo bye bye
+end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCall
+ Identifier attach
+ NamedArg
+ NamedArgPrefix signal=
+ String
+ StringFragment exit
+ NamedArg
+ NamedArgPrefix code=
+ Number 1
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+
+
+ test('work with dot-get', () => {
+ expect(`
+signals = [=]
+signals.trap 'EXIT':
+ echo bye bye
+end`).toMatchTree(`
+ Assign
+ AssignableIdentifier signals
+ Eq =
+ Dict [=]
+ FunctionCallWithBlock
+ FunctionCall
+ DotGet
+ IdentifierBeforeDot signals
+ Identifier trap
+ PositionalArg
+ String
+ StringFragment EXIT
+ colon :
+ Block
+ FunctionCall
+ Identifier echo
+ PositionalArg
+ Identifier bye
+ PositionalArg
+ Identifier bye
+ keyword end`
+ )
+ })
+})
+
+describe('ribbit', () => {
+ test('head tag', () => {
+ expect(`
+head:
+ title What up
+ meta charSet=UTF-8
+ meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'
+end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCallOrIdentifier
+ Identifier head
+ colon :
+ Block
+ FunctionCall
+ Identifier title
+ PositionalArg
+ Word What
+ PositionalArg
+ Identifier up
+ FunctionCall
+ Identifier meta
+ PositionalArg
+ Word charSet=UTF-8
+ FunctionCall
+ Identifier meta
+ NamedArg
+ NamedArgPrefix name=
+ String
+ StringFragment viewport
+ NamedArg
+ NamedArgPrefix content=
+ String
+ StringFragment width=device-width, initial-scale=1, viewport-fit=cover
+ keyword end
+ `)
+ })
+
+ test('li', () => {
+ expect(`
+list:
+ li border-bottom='1px solid black' one
+ li two
+ li three
+end`).toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCallOrIdentifier
+ Identifier list
+ colon :
+ Block
+ FunctionCall
+ Identifier li
+ NamedArg
+ NamedArgPrefix border-bottom=
+ String
+ StringFragment 1px solid black
+ PositionalArg
+ Identifier one
+ FunctionCall
+ Identifier li
+ PositionalArg
+ Identifier two
+ FunctionCall
+ Identifier li
+ PositionalArg
+ Identifier three
+ keyword end`)
+ })
+
+ test('inline expressions', () => {
+ expect(`
+p:
+ h1 class=bright style='font-family: helvetica' Heya
+ h2 man that is (b wild)!
+end`)
+ .toMatchTree(`
+ FunctionCallWithBlock
+ FunctionCallOrIdentifier
+ Identifier p
+ colon :
+ Block
+ FunctionCall
+ Identifier h1
+ NamedArg
+ NamedArgPrefix class=
+ Identifier bright
+ NamedArg
+ NamedArgPrefix style=
+ String
+ StringFragment font-family: helvetica
+ PositionalArg
+ Word Heya
+ FunctionCall
+ Identifier h2
+ PositionalArg
+ Identifier man
+ PositionalArg
+ Identifier that
+ PositionalArg
+ Identifier is
+ PositionalArg
+ ParenExpr
+ FunctionCall
+ Identifier b
+ PositionalArg
+ Identifier wild
+ PositionalArg
+ Word !
+ keyword end`)
+ })
+})
\ No newline at end of file
--
2.50.1
From 7756306e1d247b24a58711fb6838e01f2c06dbd3 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sat, 1 Nov 2025 22:40:25 -0700
Subject: [PATCH 09/12] insanity
---
src/compiler/tests/function-blocks.test.ts | 81 ---------------
src/compiler/tests/ribbit.test.ts | 113 +++++++++++++++++++++
2 files changed, 113 insertions(+), 81 deletions(-)
create mode 100644 src/compiler/tests/ribbit.test.ts
diff --git a/src/compiler/tests/function-blocks.test.ts b/src/compiler/tests/function-blocks.test.ts
index 056affe..41bf65d 100644
--- a/src/compiler/tests/function-blocks.test.ts
+++ b/src/compiler/tests/function-blocks.test.ts
@@ -53,84 +53,3 @@ signals.trap 'EXIT':
end`).toEvaluateTo(['EXIT', true])
})
})
-
-describe('ribbit', () => {
- test('head tag', () => {
- expect(`
- head:
- title What up
- meta charSet=UTF-8
- meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover'
- end`).toEvaluateTo(`head`, {
- head: () => 'head'
- })
- })
-
- test('li', () => {
- expect(`
- list:
- li border-bottom='1px solid black' one
- li two
- li three
- end`).toEvaluateTo(`list`, {
- list: () => 'list'
- })
- })
-
- test('inline expressions', () => {
- const buffer: string[] = []
-
- const tagBlock = async (tagName: string, props = {}, fn: Function) => {
- const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`)
- const space = attrs.length ? ' ' : ''
-
- buffer.push(`<${tagName}${space}${attrs.join(' ')}>`)
- await fn()
- buffer.push(`${tagName}>`)
- }
-
- const tagCall = (tagName: string, atNamed: {}, ...args: any[]) => {
- const attrs = Object.entries(atNamed).map(([key, value]) => `${key}="${value}"`)
- const space = attrs.length ? ' ' : ''
- const children = args
- .reverse()
- .map(a => a === null ? buffer.pop() : a)
- .reverse().join(' ')
- .replaceAll(' !!ribbit-nospace!! ', '')
-
- buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}${tagName}>`)
- }
-
- const tag = async (tagName: string, atNamed: {}, ...args: any[]) => {
- if (typeof args[0] === 'function')
- await tagBlock(tagName, atNamed, args[0])
- else
- tagCall(tagName, atNamed, ...args)
- }
-
- expect(`
- ribbit:
- p class=container:
- h1 class=bright style='font-family: helvetica' Heya
- h2 man that is (b wild) (nospace) !
- p Double the fun.
- end
- end`).toEvaluateTo(
- `
-
Heya
-man that is wild!
-Double the fun.
-`, {
- ribbit: async (cb: Function) => {
- await cb()
- return buffer.join("\n")
- },
- p: (atNamed: {}, ...args: any[]) => tag('p', atNamed, ...args),
- h1: (atNamed: {}, ...args: any[]) => tag('h1', atNamed, ...args),
- h2: (atNamed: {}, ...args: any[]) => tag('h2', atNamed, ...args),
- b: (atNamed: {}, ...args: any[]) => tag('b', atNamed, ...args),
- nospace: () => '!!ribbit-nospace!!',
- join: (...args: string[]) => args.join(''),
- })
- })
-})
\ No newline at end of file
diff --git a/src/compiler/tests/ribbit.test.ts b/src/compiler/tests/ribbit.test.ts
new file mode 100644
index 0000000..0fe397a
--- /dev/null
+++ b/src/compiler/tests/ribbit.test.ts
@@ -0,0 +1,113 @@
+import { expect, describe, test, beforeEach } from 'bun:test'
+import { type Value } from 'reefvm'
+
+const buffer: string[] = []
+
+const ribbitGlobals = {
+ ribbit: async (cb: Function) => {
+ await cb()
+ return buffer.join("\n")
+ },
+ tag: (tagName: string, atDefaults = {}) => {
+ return (atNamed = {}, ...args: any[]) => tag(tagName, Object.assign({}, atDefaults, atNamed), ...args)
+ },
+ head: (atNamed: {}, ...args: any[]) => tag('head', atNamed, ...args),
+ title: (atNamed: {}, ...args: any[]) => tag('title', atNamed, ...args),
+ meta: (atNamed: {}, ...args: any[]) => tag('meta', atNamed, ...args),
+ p: (atNamed: {}, ...args: any[]) => tag('p', atNamed, ...args),
+ h1: (atNamed: {}, ...args: any[]) => tag('h1', atNamed, ...args),
+ h2: (atNamed: {}, ...args: any[]) => tag('h2', atNamed, ...args),
+ b: (atNamed: {}, ...args: any[]) => tag('b', atNamed, ...args),
+ ul: (atNamed: {}, ...args: any[]) => tag('ul', atNamed, ...args),
+ li: (atNamed: {}, ...args: any[]) => tag('li', atNamed, ...args),
+ nospace: () => NOSPACE_TOKEN,
+ echo: (...args: any[]) => console.log(...args)
+}
+
+function raw(fn: Function) { (fn as any).raw = true }
+
+const tagBlock = async (tagName: string, props = {}, fn: Function) => {
+ const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`)
+ const space = attrs.length ? ' ' : ''
+
+ buffer.push(`<${tagName}${space}${attrs.join(' ')}>`)
+ await fn()
+ buffer.push(`${tagName}>`)
+}
+
+const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => {
+ const attrs = Object.entries(atNamed).map(([key, value]) => `${key}="${value}"`)
+ const space = attrs.length ? ' ' : ''
+ const children = args
+ .reverse()
+ .map(a => a === null ? buffer.pop() : a)
+ .reverse().join(' ')
+ .replaceAll(` ${NOSPACE_TOKEN} `, '')
+
+ if (SELF_CLOSING.includes(tagName))
+ buffer.push(`<${tagName}${space}${attrs.join(' ')} />`)
+ else
+ buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}${tagName}>`)
+}
+
+const tag = async (tagName: string, atNamed = {}, ...args: any[]) => {
+ if (typeof args[0] === 'function')
+ await tagBlock(tagName, atNamed, args[0])
+ else
+ tagCall(tagName, atNamed, ...args)
+}
+
+const NOSPACE_TOKEN = '!!ribbit-nospace!!'
+const SELF_CLOSING = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]
+
+describe('ribbit', () => {
+ beforeEach(() => buffer.length = 0)
+
+ test('head tag', () => {
+ expect(`
+ribbit:
+ head:
+ title What up
+ meta charset=UTF-8
+ meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover'
+ end
+end
+ `).toEvaluateTo(`
+What up
+
+
+`, ribbitGlobals)
+ })
+
+ test('custom tags', () => {
+ expect(`
+list = tag 'ul' class=list
+ribbit:
+ list:
+ li border-bottom='1px solid black' one
+ li two
+ li three
+ end
+end`).toEvaluateTo(``, ribbitGlobals)
+ })
+
+ test('inline expressions', () => {
+ expect(`
+ ribbit:
+ p class=container:
+ h1 class=bright style='font-family: helvetica' Heya
+ h2 man that is (b wild) (nospace) !
+ p Double the fun.
+ end
+ end`).toEvaluateTo(
+ `
+
Heya
+man that is wild!
+Double the fun.
+`, ribbitGlobals)
+ })
+})
\ No newline at end of file
--
2.50.1
From 70ac5544a9bbab35cef7c164e7d5afb6700a859f Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sat, 1 Nov 2025 22:55:29 -0700
Subject: [PATCH 10/12] old syntax
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 17c6f73..e25581d 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Go to http://localhost:3000 to try out the playground.
tail log.txt lines=50
name = "Shrimp"
- greet = fn person: echo "Hello" person
+ greet = do person: echo "Hello" person
result = tail log.txt lines=10
@@ -33,7 +33,7 @@ Go to http://localhost:3000 to try out the playground.
## Architecture
**parser/** - Lezer grammar and tokenizers that parse Shrimp code into syntax trees
-**editor/** - CodeMirror integration with syntax highlighting and language support
+**editor/** - CodeMirror integration with syntax highlighting and language support
**compiler/** - Transforms syntax trees into ReefVM bytecode for execution
The flow: Shrimp source → parser (CST) → compiler (bytecode) → ReefVM (execution)
--
2.50.1
From 24e0b49679953c27ddb083e007fc6928791fca83 Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sat, 1 Nov 2025 23:11:52 -0700
Subject: [PATCH 11/12] it's alive
---
bun.lock | 2 +-
src/compiler/tests/ribbit.test.ts | 7 +++----
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/bun.lock b/bun.lock
index afb8aaa..559b57e 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#c69b172c78853756ec8acba5bc33d93eb6a571c6", { "peerDependencies": { "typescript": "^5" } }, "c69b172c78853756ec8acba5bc33d93eb6a571c6"],
+ "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#0f39e9401eb7a0a7c906e150127f9829458a79b6", { "peerDependencies": { "typescript": "^5" } }, "0f39e9401eb7a0a7c906e150127f9829458a79b6"],
"style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="],
diff --git a/src/compiler/tests/ribbit.test.ts b/src/compiler/tests/ribbit.test.ts
index 0fe397a..9651adc 100644
--- a/src/compiler/tests/ribbit.test.ts
+++ b/src/compiler/tests/ribbit.test.ts
@@ -1,5 +1,4 @@
import { expect, describe, test, beforeEach } from 'bun:test'
-import { type Value } from 'reefvm'
const buffer: string[] = []
@@ -8,8 +7,8 @@ const ribbitGlobals = {
await cb()
return buffer.join("\n")
},
- tag: (tagName: string, atDefaults = {}) => {
- return (atNamed = {}, ...args: any[]) => tag(tagName, Object.assign({}, atDefaults, atNamed), ...args)
+ tag: async (tagFn: Function, atDefaults = {}) => {
+ return (atNamed = {}, ...args: any[]) => tagFn(Object.assign({}, atDefaults, atNamed), ...args)
},
head: (atNamed: {}, ...args: any[]) => tag('head', atNamed, ...args),
title: (atNamed: {}, ...args: any[]) => tag('title', atNamed, ...args),
@@ -81,7 +80,7 @@ end
test('custom tags', () => {
expect(`
-list = tag 'ul' class=list
+list = tag ul class=list
ribbit:
list:
li border-bottom='1px solid black' one
--
2.50.1
From 67e0db090b75e77836869304ebdaf8a81d61b01f Mon Sep 17 00:00:00 2001
From: Chris Wanstrath
Date: Sun, 2 Nov 2025 13:09:16 -0800
Subject: [PATCH 12/12] maybe better
---
src/compiler/tests/ribbit.test.ts | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/compiler/tests/ribbit.test.ts b/src/compiler/tests/ribbit.test.ts
index 9651adc..e2bb6c2 100644
--- a/src/compiler/tests/ribbit.test.ts
+++ b/src/compiler/tests/ribbit.test.ts
@@ -39,7 +39,7 @@ const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => {
const space = attrs.length ? ' ' : ''
const children = args
.reverse()
- .map(a => a === null ? buffer.pop() : a)
+ .map(a => a === TAG_TOKEN ? buffer.pop() : a)
.reverse().join(' ')
.replaceAll(` ${NOSPACE_TOKEN} `, '')
@@ -50,13 +50,16 @@ const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => {
}
const tag = async (tagName: string, atNamed = {}, ...args: any[]) => {
- if (typeof args[0] === 'function')
+ if (typeof args[0] === 'function') {
await tagBlock(tagName, atNamed, args[0])
- else
+ } else {
tagCall(tagName, atNamed, ...args)
+ return TAG_TOKEN
+ }
}
const NOSPACE_TOKEN = '!!ribbit-nospace!!'
+const TAG_TOKEN = '!!ribbit-tag!!'
const SELF_CLOSING = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]
describe('ribbit', () => {
--
2.50.1