Compare commits

...

3 Commits

Author SHA1 Message Date
34c1177636 more tests 2025-10-28 17:03:41 -07:00
339c09eb8c compile array literals 2025-10-28 16:47:33 -07:00
7da4c14962 parse arrays 2025-10-28 16:30:45 -07:00
9 changed files with 353 additions and 35 deletions

1
packages/ReefVM Submodule

@ -0,0 +1 @@
Subproject commit 97b6722a113417398a1c47d583bfe07a906f87a0

View File

@ -468,6 +468,13 @@ export class Compiler {
return instructions
}
case terms.Array: {
const children = getAllChildren(node)
const instructions: ProgramItem[] = children.map((x) => this.#compileNode(x, input)).flat()
instructions.push(['MAKE_ARRAY', children.length])
return instructions
}
default:
throw new CompilerError(
`Compiler doesn't know how to handle a "${node.type.name}" node.`,

View File

@ -0,0 +1,96 @@
import { describe } from 'bun:test'
import { expect, test } from 'bun:test'
describe('array literals', () => {
test('work with numbers', () => {
expect('[1 2 3]').toEvaluateTo([1, 2, 3])
})
test('work with strings', () => {
expect("['one' 'two' 'three']").toEvaluateTo(['one', 'two', 'three'])
})
test('work with identifiers', () => {
expect('[one two three]').toEvaluateTo(['one', 'two', 'three'])
})
test('can be nested', () => {
expect('[one [two [three]]]').toEvaluateTo(['one', ['two', ['three']]])
})
test('can span multiple lines', () => {
expect(`[
1
2
3
]`).toEvaluateTo([1, 2, 3])
})
test('can span multiple w/o calling functions', () => {
expect(`[
one
two
three
]`).toEvaluateTo(['one', 'two', 'three'])
})
test('empty arrays', () => {
expect('[]').toEvaluateTo([])
})
test('mixed types', () => {
expect("[1 'two' three true null]").toEvaluateTo([1, 'two', 'three', true, null])
})
test('semicolons as separators', () => {
expect('[1; 2; 3]').toEvaluateTo([1, 2, 3])
})
test('expressions in arrays', () => {
expect('[(1 + 2) (3 * 4)]').toEvaluateTo([3, 12])
})
test('mixed separators - spaces and newlines', () => {
expect(`[1 2
3 4]`).toEvaluateTo([1, 2, 3, 4])
})
test('mixed separators - spaces and semicolons', () => {
expect('[1 2; 3 4]').toEvaluateTo([1, 2, 3, 4])
})
test('empty lines within arrays', () => {
expect(`[1
2]`).toEvaluateTo([1, 2])
})
test('comments within arrays', () => {
expect(`[1 # first
2 # second
]`).toEvaluateTo([1, 2])
})
test('complex nested multiline', () => {
expect(`[
[1 2]
[3 4]
[5 6]
]`).toEvaluateTo([[1, 2], [3, 4], [5, 6]])
})
test('boolean and null literals', () => {
expect('[true false null]').toEvaluateTo([true, false, null])
})
test('regex literals', () => {
expect('[//[0-9]+//]').toEvaluateTo([/[0-9]+/])
})
test('trailing newlines', () => {
expect(`[
1
2
]`).toEvaluateTo([1, 2])
})
})

View File

@ -191,6 +191,10 @@ EscapeSeq {
"\\" ("$" | "n" | "t" | "r" | "\\" | "'")
}
Array {
"[" (newlineOrSemicolon | expression)* "]"
}
// We need expressionWithoutIdentifier to avoid conflicts in consumeToTerminator.
// Without this, when parsing "my-var" at statement level, the parser can't decide:
// - ambiguousFunctionCall → FunctionCallOrIdentifier → Identifier
@ -200,7 +204,7 @@ EscapeSeq {
// 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 | @specialize[@name=Null]<Identifier, "null">
ParenExpr | Word | String | Number | Boolean | Regex | Array | @specialize[@name=Null]<Identifier, "null">
}
block {

View File

@ -31,19 +31,20 @@ export const
EscapeSeq = 29,
Boolean = 30,
Regex = 31,
Null = 32,
ConditionalOp = 33,
FunctionDef = 34,
Params = 35,
colon = 36,
keyword = 50,
PositionalArg = 38,
Underscore = 39,
NamedArg = 40,
NamedArgPrefix = 41,
IfExpr = 43,
SingleLineThenBlock = 45,
ThenBlock = 46,
ElseIfExpr = 47,
ElseExpr = 49,
Assign = 51
Array = 32,
Null = 33,
ConditionalOp = 34,
FunctionDef = 35,
Params = 36,
colon = 37,
keyword = 51,
PositionalArg = 39,
Underscore = 40,
NamedArg = 41,
NamedArgPrefix = 42,
IfExpr = 44,
SingleLineThenBlock = 46,
ThenBlock = 47,
ElseIfExpr = 48,
ElseExpr = 50,
Assign = 52

View File

@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer"
import {tokenizer, specializeKeyword} from "./tokenizer"
import {trackScope} from "./scopeTracker"
import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,null:64, end:74, if:88, elseif:96, else:100}
const spec_Identifier = {__proto__:null,null:66, end:76, if:90, elseif:98, else:102}
export const parser = LRParser.deserialize({
version: 14,
states: "/SQYQbOOO!TOpO'#CqO#aQcO'#CtO$ZOSO'#CvO%aQcO'#DsOOQa'#Ds'#DsO&gQcO'#DrO'OQRO'#CuO'^QcO'#DnO'uQbO'#D{OOQ`'#DO'#DOO'}QbO'#CsOOQ`'#Do'#DoO(oQbO'#DnO(}QbO'#EROOQ`'#DX'#DXO)lQRO'#DaOOQ`'#Dn'#DnO)qQQO'#DmOOQ`'#Dm'#DmOOQ`'#Db'#DbQYQbOOO)yObO,59]OOQa'#Dr'#DrOOQ`'#DS'#DSO*RQbO'#DUOOQ`'#EQ'#EQOOQ`'#Df'#DfO*]QbO,59[O*pQbO'#CxO*xQWO'#CyOOOO'#Du'#DuOOOO'#Dc'#DcO+^OSO,59bOOQa,59b,59bO(}QbO,59aO(}QbO,59aOOQ`'#Dd'#DdO+lQbO'#DPO+tQQO,5:gO+yQRO,59_O-`QRO'#CuO-pQRO,59_O-|QQO,59_O.RQQO,59_O.ZQbO'#DgO.fQbO,59ZO.wQRO,5:mO/OQQO,5:mO/TQbO,59{OOQ`,5:X,5:XOOQ`-E7`-E7`OOQa1G.w1G.wOOQ`,59p,59pOOQ`-E7d-E7dOOOO,59d,59dOOOO,59e,59eOOOO-E7a-E7aOOQa1G.|1G.|OOQa1G.{1G.{O/_QcO1G.{OOQ`-E7b-E7bO/yQbO1G0ROOQa1G.y1G.yO(}QbO,59iO(}QbO,59iO!YQbO'#CtO$iQbO'#CpOOQ`,5:R,5:ROOQ`-E7e-E7eO0WQbO1G0XOOQ`1G/g1G/gO0eQbO7+%mO0jQbO7+%nOOQO1G/T1G/TO0zQRO1G/TOOQ`'#DZ'#DZO1UQbO7+%sO1ZQbO7+%tOOQ`<<IX<<IXOOQ`'#De'#DeO1qQQO'#DeO1vQbO'#EOO2^QbO<<IYOOQ`<<I_<<I_OOQ`'#D['#D[O2cQbO<<I`OOQ`,5:P,5:POOQ`-E7c-E7cOOQ`AN>tAN>tO(}QbO'#D]OOQ`'#Dh'#DhO2nQbOAN>zO2yQQO'#D_OOQ`AN>zAN>zO3OQbOAN>zO3TQRO,59wO3[QQO,59wOOQ`-E7f-E7fOOQ`G24fG24fO3aQbOG24fO3fQQO,59yO3kQQO1G/cOOQ`LD*QLD*QO0jQbO1G/eO1ZQbO7+$}OOQ`7+%P7+%POOQ`<<Hi<<Hi",
stateData: "3s~O!_OS!`OS~O]QO^`O_TO`POaXOfTOnTOoTOpTO|^O!eZO!hRO!qcO~O!dfO~O]gO_TO`POaXOfTOnTOoTOpTOwhOyiO!eZO!hROzhX!qhX!whX!shXuhX~OP!fXQ!fXR!fXS!fXT!fXU!fXV!fXW!fXX!fXY!fXZ!fX[!fX~P!YOkoO!hrO!jmO!knO~O]gO_TO`POaXOfTOnTOoTOpTOwhOyiO!eZO!hRO~OP!gXQ!gXR!gXS!gX!q!gX!w!gXT!gXU!gXV!gXW!gXX!gXY!gXZ!gX[!gX!s!gXu!gX~P$iOP!fXQ!fXR!fXS!fX!q!bX!w!bXu!bX~OPsOQsORtOStO~OPsOQsORtOStO!q!bX!w!bXu!bX~O]uOtsP~O]QO_TO`POaXOfTOnTOoTOpTO!eZO!hRO~Oz}O!q!bX!w!bXu!bX~O]gO_TO`POfTOnTOoTOpTO!eZO!hRO~OV!RO~O!q!SO!w!SO~O]!UOf!UO~OaXOw!VO~P(}Ozda!qda!wda!sdauda~P$iO]!XO!eZO~O!h!YO!j!YO!k!YO!l!YO!m!YO!n!YO~OkoO!h![O!jmO!knO~O]uOtsX~Ot!`O~O!s!aOP!fXQ!fXR!fXS!fXT!fXU!fXV!fXW!fXX!fXY!fXZ!fX[!fX~OT!cOU!cOV!bOW!bOX!bOY!bOZ!bO[!bO~OPsOQsORtOStO~P,tOPsOQsORtOStO!s!aO~Oz}O!s!aO~O]!dO`PO!eZO~Oz}O!qca!wca!scauca~Ot!hO~P,tOt!hO~O^`O|^O~P'}OPsOQsORiiSii!qii!wii!siiuii~O^`O|^O!q!kO~P'}O^`O|^O!q!pO~P'}Ou!qO~O^`O|^O!q!rOu!rP~P'}O!sqitqi~P,tOu!vO~O^`O|^O!q!rOu!rP!Q!rP!S!rP~P'}O!q!yO~O^`O|^O!q!rOu!rX!Q!rX!S!rX~P'}Ou!{O~Ou#QO!Q!|O!S#PO~Ou#VO!Q!|O!S#PO~Ot#XO~Ou#VO~Ot#YO~P,tOt#YO~Ou#ZO~O!q#[O~O!q#]O~Ofo~",
goto: ",`!wPPPPPPPPPPPPPPPPPPP!x#X#gP$V#X$x%_P%x%xPPP%|&Y&sPP&vP&vPP&}P'Z'^'gP'kP&}'q'w'}(T(^(g(nPPPP(t(x)^PP)p*mP+[PPPPP+`+`P+sP+{,S,SdaOe!R!`!h!k!p!t#[#]R{Zi[OZe}!R!`!h!k!p!t#[#]fQOZe!R!`!h!k!p!t#[#]hgQS^ilst!b!c!d!e!|R!d}fSOZe!R!`!h!k!p!t#[#]hTQS^ilst!b!c!d!e!|Q!XmR!e}dWOe!R!`!h!k!p!t#[#]QzZQ!]sR!^t!PTOQSZ^eilst!R!`!b!c!d!e!h!k!p!t!|#[#]ToRqQ{ZQ!Q^Q!l!cR#T!|daOe!R!`!h!k!p!t#[#]YhQSl!d!eQ{ZR!ViRwXZjQSl!d!eeaOe!R!`!h!k!p!t#[#]R!o!hQ!x!pQ#^#[R#_#]T!}!x#OQ#R!xR#W#OQeOR!TeQqRR!ZqQvXR!_vW!t!k!p#[#]R!z!tWlQS!d!eR!WlS!O]|R!g!OQ#O!xR#U#OTdOeSbOeQ!i!RQ!j!`Q!n!hZ!s!k!p!t#[#]d]Oe!R!`!h!k!p!t#[#]Q|ZR!f}dVOe!R!`!h!k!p!t#[#]YhQSl!d!eQyZQ!P^Q!ViQ!]sQ!^tQ!l!bQ!m!cR#S!|dUOe!R!`!h!k!p!t#[#]hgQS^ilst!b!c!d!e!|RxZTpRqsYOQSZeil!R!`!d!e!h!k!p!t#[#]Q!u!kV!w!p#[#]ZkQSl!d!ee_Oe!R!`!h!k!p!t#[#]",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq Neq Lt Lte Gt Gte Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Null ConditionalOp FunctionDef Params colon keyword PositionalArg Underscore NamedArg NamedArgPrefix operator IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
maxTerm: 85,
states: "/xQYQbOOO!WOpO'#CqO#gQcO'#CtO$aOSO'#CvO%dQbO'#C|O&fQcO'#DuOOQa'#Du'#DuO'lQcO'#DtO(TQRO'#CuO(cQcO'#DpO(zQbO'#EQOOQ`'#DP'#DPO)SQbO'#CsOOQ`'#Dq'#DqO)wQbO'#DpO*VQbO'#EVOOQ`'#DY'#DYO*wQRO'#DbOOQ`'#Dp'#DpO*|QQO'#DoOOQ`'#Do'#DoOOQ`'#Dc'#DcQYQbOOO+UObO,59]OOQa'#Dt'#DtOOQ`'#DT'#DTO+^QbO'#DVOOQ`'#EU'#EUOOQ`'#Dh'#DhO+hQbO,59[O+{QbO'#CxO,TQWO'#CyOOOO'#Dw'#DwOOOO'#Dd'#DdO,iOSO,59bOOQa,59b,59bOOQ`'#De'#DeO,wQbO,59hOOQa,59h,59hO*VQbO,59aO*VQbO,59aOOQ`'#Df'#DfO-OQbO'#DQO-WQQO,5:lO-]QRO,59_O.rQRO'#CuO/SQRO,59_O/`QQO,59_O/eQQO,59_O/mQbO'#DiO/xQbO,59ZO0ZQRO,5:qO0bQQO,5:qO0gQbO,59|OOQ`,5:Z,5:ZOOQ`-E7a-E7aOOQa1G.w1G.wOOQ`,59q,59qOOQ`-E7f-E7fOOOO,59d,59dOOOO,59e,59eOOOO-E7b-E7bOOQa1G.|1G.|OOQ`-E7c-E7cOOQa1G/S1G/SOOQa1G.{1G.{O0qQcO1G.{OOQ`-E7d-E7dO1]QbO1G0WOOQa1G.y1G.yO*VQbO,59jO*VQbO,59jO!]QbO'#CtO%kQbO'#CpOOQ`,5:T,5:TOOQ`-E7g-E7gO1jQbO1G0]OOQ`1G/h1G/hO1wQbO7+%rO1|QbO7+%sOOQO1G/U1G/UO2^QRO1G/UOOQ`'#D['#D[O2hQbO7+%wO2mQbO7+%xOOQ`<<I^<<I^OOQ`'#Dg'#DgO3TQQO'#DgO3YQbO'#ESO3pQbO<<I_OOQ`<<Ic<<IcOOQ`'#D]'#D]O3uQbO<<IdOOQ`,5:R,5:ROOQ`-E7e-E7eOOQ`AN>yAN>yO*VQbO'#D^OOQ`'#Dj'#DjO4QQbOAN?OO4]QQO'#D`OOQ`AN?OAN?OO4bQbOAN?OO4gQRO,59xO4nQQO,59xOOQ`-E7h-E7hOOQ`G24jG24jO4sQbOG24jO4xQQO,59zO4}QQO1G/dOOQ`LD*ULD*UO1|QbO1G/fO2mQbO7+%OOOQ`7+%Q7+%QOOQ`<<Hj<<Hj",
stateData: "5V~O!aOS!bOS~O]QO^aO_UO`POaYOfUOnUOoUOqUO}_O!g[O!jRO!qSO!rdO~O!fgO~O]hO_UO`POaYOfUOnUOoUOqUOxiOzjO!g[O!jRO!qSO{hX!rhX!{hX!whXvhX~OP!hXQ!hXR!hXS!hXT!hXU!hXV!hXW!hXX!hXY!hXZ!hX[!hX~P!]OkpO!jsO!lnO!moO~O]hO_UO`POfUOnUOoUOqUO!g[O!jRO!qSO!rtO~O!svO~P$oO]hO_UO`POaYOfUOnUOoUOqUOxiOzjO!g[O!jRO!qSO~OP!iXQ!iXR!iXS!iX!r!iX!{!iXT!iXU!iXV!iXW!iXX!iXY!iXZ!iX[!iX!w!iXv!iX~P%kOP!hXQ!hXR!hXS!hX!r!dX!{!dXv!dX~OPwOQwORxOSxO~OPwOQwORxOSxO!r!dX!{!dXv!dX~O]yOutP~O]QO_UO`POaYOfUOnUOoUOqUO!g[O!jRO!qSO~O{!RO!r!dX!{!dXv!dX~O]hO_UO`POfUOnUOoUOqUO!g[O!jRO!qSO~OV!VO~O!r!WO!{!WO~O]!YOf!YO~OaYOx!ZO~P*VO{da!rda!{da!wdavda~P%kO]!]O!g[O~O!j!^O!l!^O!m!^O!n!^O!o!^O!p!^O~OkpO!j!`O!lnO!moO~O!s!bO~P$oO]yOutX~Ou!fO~O!w!gOP!hXQ!hXR!hXS!hXT!hXU!hXV!hXW!hXX!hXY!hXZ!hX[!hX~OT!iOU!iOV!hOW!hOX!hOY!hOZ!hO[!hO~OPwOQwORxOSxO~P.WOPwOQwORxOSxO!w!gO~O{!RO!w!gO~O]!jO`PO!g[O~O{!RO!rca!{ca!wcavca~Ou!nO~P.WOu!nO~O^aO}_O~P)SOPwOQwORiiSii!rii!{ii!wiivii~O^aO}_O!r!qO~P)SO^aO}_O!r!vO~P)SOv!wO~O^aO}_O!r!xOv!vP~P)SO!wriuri~P.WOv!|O~O^aO}_O!r!xOv!vP!R!vP!T!vP~P)SO!r#PO~O^aO}_O!r!xOv!vX!R!vX!T!vX~P)SOv#RO~Ov#WO!R#SO!T#VO~Ov#]O!R#SO!T#VO~Ou#_O~Ov#]O~Ou#`O~P.WOu#`O~Ov#aO~O!r#bO~O!r#cO~Ofo~",
goto: ",v!{PPPPPPPPPPPPPPPPPPP!|#]#kP$]#]%Q%gP&S&SPP%gP&W&d&}PP'QP'QPP'XP'e'h'qP'uP'X'{(R(X(_(e(n(w)OPPPP)U)Y)nPP*Q+RP+rPPPPPPPP+v+v,ZP,c,j,jdbOf!V!f!n!q!v!z#b#cR!P[i]O[f!R!V!f!n!q!v!z#b#cfQO[f!V!f!n!q!v!z#b#clhQST_jmuwx!h!i!j!k#SR!j!RfTO[f!V!f!n!q!v!z#b#clUQST_jmuwx!h!i!j!k#SQ!]nR!k!RdXOf!V!f!n!q!v!z#b#cQ!O[Q!cwR!dx!TUOQST[_fjmuwx!V!f!h!i!j!k!n!q!v!z#S#b#cTpRrQ!P[Q!U_Q!r!iR#Z#SdbOf!V!f!n!q!v!z#b#cYiQTm!j!kQ!P[R!ZjR{YZkQTm!j!kebOf!V!f!n!q!v!z#b#cR!u!nQ#O!vQ#d#bR#e#cT#T#O#UQ#X#OR#^#UQfOR!XfQrRR!_rQuSR!auQzYR!ezW!z!q!v#b#cR#Q!zWmQT!j!kR![mS!S^!QR!m!SQ#U#OR#[#UTeOfScOfQ!o!VQ!p!fQ!t!nZ!y!q!v!z#b#cd^Of!V!f!n!q!v!z#b#cQ!Q[R!l!RdWOf!V!f!n!q!v!z#b#cYiQTm!j!kStSuQ}[Q!T_Q!ZjQ!cwQ!dxQ!r!hQ!s!iR#Y#SdVOf!V!f!n!q!v!z#b#clhQST_jmuwx!h!i!j!k#SR|[TqRrsZOQT[fjm!V!f!j!k!n!q!v!z#b#cQ!{!qV!}!v#b#cZlQTm!j!ke`Of!V!f!n!q!v!z#b#c",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq Neq Lt Lte Gt Gte Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Array Null ConditionalOp FunctionDef Params colon keyword PositionalArg Underscore NamedArg NamedArgPrefix operator IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
maxTerm: 89,
context: trackScope,
nodeProps: [
["closedBy", 36,"end"]
["closedBy", 37,"end"]
],
propSources: [highlighting],
skippedNodes: [0],
repeatNodeCount: 7,
tokenData: ">i~RzOX#uXY$dYZ$}Zp#upq$dqs#ust%htu'Puw#uwx'Uxy'Zyz'tz{#u{|(_|}#u}!O(_!O!P#u!P!Q+R!Q![(|![!]3n!]!^$}!^#O#u#O#P4X#P#R#u#R#S4^#S#T#u#T#Y4w#Y#Z6V#Z#b4w#b#c:e#c#f4w#f#g;[#g#h4w#h#i<R#i#o4w#o#p#u#p#q=y#q;'S#u;'S;=`$^<%l~#u~O#u~~>dS#zUkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uS$aP;=`<%l#u^$kUkS!_YOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU%UUkS!qQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u^%oZkS!`YOY%hYZ#uZt%htu&buw%hwx&bx#O%h#O#P&b#P;'S%h;'S;=`&y<%lO%hY&gS!`YOY&bZ;'S&b;'S;=`&s<%lO&bY&vP;=`<%l&b^&|P;=`<%l%h~'UO!j~~'ZO!h~U'bUkS!eQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU'{UkS!sQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU(dWkSOt#uuw#ux!Q#u!Q![(|![#O#u#P;'S#u;'S;=`$^<%lO#uU)TYkSfQOt#uuw#ux!O#u!O!P)s!P!Q#u!Q![(|![#O#u#P;'S#u;'S;=`$^<%lO#uU)xWkSOt#uuw#ux!Q#u!Q![*b![#O#u#P;'S#u;'S;=`$^<%lO#uU*iWkSfQOt#uuw#ux!Q#u!Q![*b![#O#u#P;'S#u;'S;=`$^<%lO#uU+WWkSOt#uuw#ux!P#u!P!Q+p!Q#O#u#P;'S#u;'S;=`$^<%lO#uU+u^kSOY,qYZ#uZt,qtu-tuw,qwx-tx!P,q!P!Q#u!Q!},q!}#O2g#O#P0S#P;'S,q;'S;=`3h<%lO,qU,x^kSoQOY,qYZ#uZt,qtu-tuw,qwx-tx!P,q!P!Q0i!Q!},q!}#O2g#O#P0S#P;'S,q;'S;=`3h<%lO,qQ-yXoQOY-tZ!P-t!P!Q.f!Q!}-t!}#O/T#O#P0S#P;'S-t;'S;=`0c<%lO-tQ.iP!P!Q.lQ.qUoQ#Z#[.l#]#^.l#a#b.l#g#h.l#i#j.l#m#n.lQ/WVOY/TZ#O/T#O#P/m#P#Q-t#Q;'S/T;'S;=`/|<%lO/TQ/pSOY/TZ;'S/T;'S;=`/|<%lO/TQ0PP;=`<%l/TQ0VSOY-tZ;'S-t;'S;=`0c<%lO-tQ0fP;=`<%l-tU0nWkSOt#uuw#ux!P#u!P!Q1W!Q#O#u#P;'S#u;'S;=`$^<%lO#uU1_bkSoQOt#uuw#ux#O#u#P#Z#u#Z#[1W#[#]#u#]#^1W#^#a#u#a#b1W#b#g#u#g#h1W#h#i#u#i#j1W#j#m#u#m#n1W#n;'S#u;'S;=`$^<%lO#uU2l[kSOY2gYZ#uZt2gtu/Tuw2gwx/Tx#O2g#O#P/m#P#Q,q#Q;'S2g;'S;=`3b<%lO2gU3eP;=`<%l2gU3kP;=`<%l,qU3uUkStQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u~4^O!k~U4eUkSwQOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU4|YkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#uU5sUyQkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#uU6[ZkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#U6}#U#o4w#o;'S#u;'S;=`$^<%lO#uU7S[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#`4w#`#a7x#a#o4w#o;'S#u;'S;=`$^<%lO#uU7}[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#g4w#g#h8s#h#o4w#o;'S#u;'S;=`$^<%lO#uU8x[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#X4w#X#Y9n#Y#o4w#o;'S#u;'S;=`$^<%lO#uU9uYnQkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^:lY!lWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^;cY!nWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#o4w#o;'S#u;'S;=`$^<%lO#u^<Y[!mWkSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#f4w#f#g=O#g#o4w#o;'S#u;'S;=`$^<%lO#uU=T[kSOt#uuw#ux!_#u!_!`5l!`#O#u#P#T#u#T#i4w#i#j8s#j#o4w#o;'S#u;'S;=`$^<%lO#uU>QUzQkSOt#uuw#ux#O#u#P;'S#u;'S;=`$^<%lO#u~>iO!w~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!d~~", 11)],
repeatNodeCount: 8,
tokenData: "?s~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O(e!O!P#{!P!Q+X!Q![)S![!]3t!]!^%T!^!}#{!}#O4_#O#P4x#P#Q4}#Q#R#{#R#S5h#S#T#{#T#Y6R#Y#Z7a#Z#b6R#b#c;o#c#f6R#f#g<f#g#h6R#h#i=]#i#o6R#o#p#{#p#q?T#q;'S#{;'S;=`$d<%l~#{~O#{~~?nS$QUkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUkS!aYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UkS!rQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZkS!bYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!bYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!l~~'aO!j~U'hUkS!gQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUkS!wQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWkSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYkSfQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWkSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWkSfQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^WkSOt#{uw#{x!P#{!P!Q+v!Q#O#{#P;'S#{;'S;=`$d<%lO#{U+{^kSOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q#{!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wU-O^kSoQOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q0o!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wQ.PXoQOY-zZ!P-z!P!Q.l!Q!}-z!}#O/Z#O#P0Y#P;'S-z;'S;=`0i<%lO-zQ.oP!P!Q.rQ.wUoQ#Z#[.r#]#^.r#a#b.r#g#h.r#i#j.r#m#n.rQ/^VOY/ZZ#O/Z#O#P/s#P#Q-z#Q;'S/Z;'S;=`0S<%lO/ZQ/vSOY/ZZ;'S/Z;'S;=`0S<%lO/ZQ0VP;=`<%l/ZQ0]SOY-zZ;'S-z;'S;=`0i<%lO-zQ0lP;=`<%l-zU0tWkSOt#{uw#{x!P#{!P!Q1^!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1ebkSoQOt#{uw#{x#O#{#P#Z#{#Z#[1^#[#]#{#]#^1^#^#a#{#a#b1^#b#g#{#g#h1^#h#i#{#i#j1^#j#m#{#m#n1^#n;'S#{;'S;=`$d<%lO#{U2r[kSOY2mYZ#{Zt2mtu/Zuw2mwx/Zx#O2m#O#P/s#P#Q,w#Q;'S2m;'S;=`3h<%lO2mU3kP;=`<%l2mU3qP;=`<%l,wU3{UkSuQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4fU!qQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~4}O!m~U5UU!sQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U5oUkSxQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6WYkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#o6R#o;'S#{;'S;=`$d<%lO#{U6}UzQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7fZkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#U8X#U#o6R#o;'S#{;'S;=`$d<%lO#{U8^[kSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#`6R#`#a9S#a#o6R#o;'S#{;'S;=`$d<%lO#{U9X[kSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#g6R#g#h9}#h#o6R#o;'S#{;'S;=`$d<%lO#{U:S[kSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#X6R#X#Y:x#Y#o6R#o;'S#{;'S;=`$d<%lO#{U;PYnQkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#o6R#o;'S#{;'S;=`$d<%lO#{^;vY!nWkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#o6R#o;'S#{;'S;=`$d<%lO#{^<mY!pWkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#o6R#o;'S#{;'S;=`$d<%lO#{^=d[!oWkSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#f6R#f#g>Y#g#o6R#o;'S#{;'S;=`$d<%lO#{U>_[kSOt#{uw#{x!_#{!_!`6v!`#O#{#P#T#{#T#i6R#i#j9}#j#o6R#o;'S#{;'S;=`$d<%lO#{U?[U{QkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~?sO!{~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!f~~", 11)],
topRules: {"Program":[0,18]},
specialized: [{term: 13, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 13, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 860
tokenPrec: 924
})

View File

@ -0,0 +1,203 @@
import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('array literals', () => {
test('work with numbers', () => {
expect('[1 2 3]').toMatchTree(`
Array
Number 1
Number 2
Number 3
`)
})
test('work with strings', () => {
expect("['one' 'two' 'three']").toMatchTree(`
Array
String
StringFragment one
String
StringFragment two
String
StringFragment three
`)
})
test('work with identifiers', () => {
expect('[one two three]').toMatchTree(`
Array
Identifier one
Identifier two
Identifier three
`)
})
test('can be nested', () => {
expect('[one [two [three]]]').toMatchTree(`
Array
Identifier one
Array
Identifier two
Array
Identifier three
`)
})
test('can span multiple lines', () => {
expect(`[
1
2
3
]`).toMatchTree(`
Array
Number 1
Number 2
Number 3
`)
})
test('can span multiple w/o calling functions', () => {
expect(`[
one
two
three
]`).toMatchTree(`
Array
Identifier one
Identifier two
Identifier three
`)
})
test('empty arrays', () => {
expect('[]').toMatchTree(`
Array []
`)
})
test('mixed types', () => {
expect("[1 'two' three true null]").toMatchTree(`
Array
Number 1
String
StringFragment two
Identifier three
Boolean true
Null null
`)
})
test('semicolons as separators', () => {
expect('[1; 2; 3]').toMatchTree(`
Array
Number 1
Number 2
Number 3
`)
})
test('expressions in arrays', () => {
expect('[(1 + 2) (3 * 4)]').toMatchTree(`
Array
ParenExpr
BinOp
Number 1
Plus +
Number 2
ParenExpr
BinOp
Number 3
Star *
Number 4
`)
})
test('mixed separators - spaces and newlines', () => {
expect(`[1 2
3 4]`).toMatchTree(`
Array
Number 1
Number 2
Number 3
Number 4
`)
})
test('mixed separators - spaces and semicolons', () => {
expect('[1 2; 3 4]').toMatchTree(`
Array
Number 1
Number 2
Number 3
Number 4
`)
})
test('empty lines within arrays', () => {
expect(`[1
2]`).toMatchTree(`
Array
Number 1
Number 2
`)
})
test('comments within arrays', () => {
expect(`[ # something...
1 # first
2 # second
]`).toMatchTree(`
Array
Number 1
Number 2
`)
})
test('complex nested multiline', () => {
expect(`[
[1 2]
[3 4]
[5 6]
]`).toMatchTree(`
Array
Array
Number 1
Number 2
Array
Number 3
Number 4
Array
Number 5
Number 6
`)
})
test('boolean and null literals', () => {
expect('[true false null]').toMatchTree(`
Array
Boolean true
Boolean false
Null null
`)
})
test('regex literals', () => {
expect('[//[0-9]+//]').toMatchTree(`
Array
Regex //[0-9]+//
`)
})
test('trailing newlines', () => {
expect(`[
1
2
]`).toMatchTree(`
Array
Number 1
Number 2
`)
})
})

View File

@ -195,7 +195,13 @@ const isWhiteSpace = (ch: number): boolean => {
}
const isWordChar = (ch: number): boolean => {
return !isWhiteSpace(ch) && ch !== 10 /* \n */ && ch !== 41 /* ) */ && ch !== -1 /* EOF */
return (
!isWhiteSpace(ch) &&
ch !== 10 /* \n */ &&
ch !== 41 /* ) */ &&
ch !== 93 /* ] */ &&
ch !== -1 /* EOF */
)
}
const isLowercaseLetter = (ch: number): boolean => {

View File

@ -93,11 +93,7 @@ expect.extend({
}
},
async toEvaluateTo(
received: unknown,
expected: unknown,
globals: Record<string, any> = {}
) {
async toEvaluateTo(received: unknown, expected: unknown, globals: Record<string, any> = {}) {
assert(typeof received === 'string', 'toEvaluateTo can only be used with string values')
try {
@ -109,7 +105,7 @@ expect.extend({
if (expected instanceof RegExp) expected = String(expected)
if (value instanceof RegExp) value = String(value)
if (value === expected) {
if (isEqual(value, expected)) {
return { pass: true }
} else {
return {
@ -165,3 +161,7 @@ const trimWhitespace = (str: string): string => {
})
.join('\n')
}
function isEqual(a: any, b: any): boolean {
return typeof a === 'object' ? JSON.stringify(a) === JSON.stringify(b) : a === b
}