[a=1 b=2 c=3] and [=] (empty dict)

This commit is contained in:
Chris Wanstrath 2025-10-28 21:09:15 -07:00
parent 34c1177636
commit 982054eb54
8 changed files with 433 additions and 53 deletions

View File

@ -470,11 +470,31 @@ export class Compiler {
case terms.Array: {
const children = getAllChildren(node)
// todo: [ = ]
const instructions: ProgramItem[] = children.map((x) => this.#compileNode(x, input)).flat()
instructions.push(['MAKE_ARRAY', children.length])
return instructions
}
case terms.Dict: {
const children = getAllChildren(node)
const instructions: ProgramItem[] = []
children.forEach((node) => {
const keyNode = node.firstChild
const valueNode = node.firstChild.nextSibling
// name= -> name
const key = input.slice(keyNode.from, keyNode.to).slice(0, -1)
instructions.push(['PUSH', key])
instructions.push(...this.#compileNode(valueNode, input))
})
instructions.push(['MAKE_DICT', children.length])
return instructions
}
default:
throw new CompilerError(
`Compiler doesn't know how to handle a "${node.type.name}" node.`,

View File

@ -76,7 +76,11 @@ describe('array literals', () => {
[1 2]
[3 4]
[5 6]
]`).toEvaluateTo([[1, 2], [3, 4], [5, 6]])
]`).toEvaluateTo([
[1, 2],
[3, 4],
[5, 6],
])
})
test('boolean and null literals', () => {
@ -94,3 +98,60 @@ describe('array literals', () => {
]`).toEvaluateTo([1, 2])
})
})
describe('dict literals', () => {
test('work with numbers', () => {
expect('[a=1 b=2 c=3]').toEvaluateTo({ a: 1, b: 2, c: 3 })
})
test('work with strings', () => {
expect("[a='one' b='two' c='three']").toEvaluateTo({ a: 'one', b: 'two', c: 'three' })
})
test('work with identifiers', () => {
expect('[a=one b=two c=three]').toEvaluateTo({ a: 'one', b: 'two', c: 'three' })
})
test('can be nested', () => {
expect('[a=one b=[two [c=three]]]').toEvaluateTo({ a: 'one', b: ['two', { c: 'three' }] })
})
test('can span multiple lines', () => {
expect(`[
a=1
b=2
c=3
]`).toEvaluateTo({ a: 1, b: 2, c: 3 })
})
test('empty dict', () => {
expect('[=]').toEvaluateTo({})
expect('[ = ]').toEvaluateTo({})
test('mixed types', () => {
expect("[a=1 b='two' c=three d=true e=null]").toEvaluateTo({
a: 1,
b: 'two',
c: 'three',
d: true,
e: null,
})
test('semicolons as separators', () => {
expect('[a=1; b=2; c=3]').toEvaluateTo({ a: 1, b: 2, c: 3 })
})
test('expressions in dicts', () => {
expect('[a=(1 + 2) b=(3 * 4)]').toEvaluateTo({ a: 3, b: 12 })
})
test('empty lines within dicts', () => {
expect(`[a=1
b=2
c=3]`).toEvaluateTo({ a: 1, b: 2, c: 3 })
})
})
})
})

View File

@ -191,8 +191,13 @@ EscapeSeq {
"\\" ("$" | "n" | "t" | "r" | "\\" | "'")
}
Dict {
"[=]" |
"[" newlineOrSemicolon* NamedArg (newlineOrSemicolon | NamedArg)* "]"
}
Array {
"[" (newlineOrSemicolon | expression)* "]"
"[" newlineOrSemicolon* (expression (newlineOrSemicolon | expression)*)? "]"
}
// We need expressionWithoutIdentifier to avoid conflicts in consumeToTerminator.
@ -204,7 +209,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 | Array | @specialize[@name=Null]<Identifier, "null">
ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | @specialize[@name=Null]<Identifier, "null">
}
block {

View File

@ -31,20 +31,21 @@ export const
EscapeSeq = 29,
Boolean = 30,
Regex = 31,
Array = 32,
Null = 33,
ConditionalOp = 34,
Dict = 32,
NamedArg = 33,
NamedArgPrefix = 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
keyword = 52,
Underscore = 39,
Array = 40,
Null = 41,
ConditionalOp = 42,
PositionalArg = 43,
IfExpr = 45,
SingleLineThenBlock = 47,
ThenBlock = 48,
ElseIfExpr = 49,
ElseExpr = 51,
Assign = 53

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:66, end:76, if:90, elseif:98, else:102}
const spec_Identifier = {__proto__:null,end:76, null:82, if:92, elseif:100, else:104}
export const parser = LRParser.deserialize({
version: 14,
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,
states: "2YQYQbOOO!ZOpO'#CqO#mQcO'#CtO$gOSO'#CvO$uQbO'#ETOOQ`'#DP'#DPOOQa'#C|'#C|O%xQbO'#DUO&}QcO'#DxOOQa'#Dx'#DxO(TQcO'#DwO(lQRO'#CuO(zQcO'#DsO)cQbO'#CsOOQ`'#Dt'#DtO*ZQbO'#DsO*iQbO'#EZOOQ`'#DZ'#DZO+^QRO'#DcOOQ`'#Ds'#DsO+cQQO'#DrOOQ`'#Dr'#DrOOQ`'#Dd'#DdQYQbOOO+kObO,59]O+sQbO'#C}OOQa'#Dw'#DwOOQ`'#DX'#DXOOQ`'#EY'#EYOOQ`'#Dk'#DkO+}QbO,59[O,bQbO'#CxO,jQWO'#CyOOOO'#Dz'#DzOOOO'#De'#DeO-OOSO,59bOOQa,59b,59bOOQ`'#Dg'#DgO-^QbO'#DQO-fQQO,5:oOOQ`'#Df'#DfO-kQbO,59pO-rQQO,59hOOQa,59p,59pO-}QbO,59pO*iQbO,59aO*iQbO,59aO.XQRO,59_O/nQRO'#CuO0OQRO,59_O0[QQO,59_O0aQQO,59_O0iQbO'#DlO0tQbO,59ZO1VQRO,5:uO1^QQO,5:uO1cQbO,59}OOQ`,5:^,5:^OOQ`-E7b-E7bOOQa1G.w1G.wOOQ`,59i,59iOOQ`-E7i-E7iOOOO,59d,59dOOOO,59e,59eOOOO-E7c-E7cOOQa1G.|1G.|OOQ`-E7e-E7eO1mQbO1G0ZOOQ`-E7d-E7dO1zQQO1G/SOOQa1G/[1G/[O2VQbO1G/[OOQO'#Di'#DiO1zQQO1G/SOOQa1G/S1G/SOOQ`'#Dj'#DjO2VQbO1G/[OOQa1G.{1G.{O2aQcO1G.{OOQa1G.y1G.yO*iQbO,59rO*iQbO,59rO!`QbO'#CtO&PQbO'#CpOOQ`,5:W,5:WOOQ`-E7j-E7jO2{QbO1G0aOOQ`1G/i1G/iO3YQbO7+%uO3_QbO7+%vO3oQQO7+$nOOQa7+$n7+$nO3zQbO7+$vOOQa7+$v7+$vOOQO-E7g-E7gOOQ`-E7h-E7hOOQO1G/^1G/^O4UQRO1G/^OOQ`'#D]'#D]O4`QbO7+%{O4eQbO7+%|OOQ`<<Ia<<IaOOQ`'#Dh'#DhO4{QQO'#DhO5QQbO'#EVO5hQbO<<IbOOQa<<HY<<HYOOQa<<Hb<<HbOOQ`<<Ig<<IgOOQ`'#D^'#D^O5mQbO<<IhOOQ`,5:S,5:SOOQ`-E7f-E7fOOQ`AN>|AN>|O*iQbO'#D_OOQ`'#Dm'#DmO5xQbOAN?SO6TQQO'#DaOOQ`AN?SAN?SO6YQbOAN?SO6_QRO,59yO6fQQO,59yOOQ`-E7k-E7kOOQ`G24nG24nO6kQbOG24nO6pQQO,59{O6uQQO1G/eOOQ`LD*YLD*YO3_QbO1G/gO4eQbO7+%POOQ`7+%R7+%ROOQ`<<Hk<<Hk",
stateData: "6}~O!dOS!eOS~O]QO^bO_XO`POaSOfXOnXOoXOyXO!O`O!j]O!mRO!tUO!uVO!veO~O!ihO~O]jO_XO`POaSOfXOnXOoXOriOwkOyXO!j]O!mRO!tUO!uVO|hX!vhX#PhX!{hXvhX~OP!kXQ!kXR!kXS!kXT!kXU!kXV!kXW!kXX!kXY!kXZ!kX[!kX~P!`OkqO!mtO!ooO!ppO~O]uOutP~O]jO_XO`POfXOnXOoXOriOyXO!j]O!mRO!tUO!uVO!vxO~O!z{O~P$}O]jO_XO`POaSOfXOnXOoXOriOwkOyXO!j]O!mRO!tUO!uVO~OP!lXQ!lXR!lXS!lX!v!lX#P!lXT!lXU!lXV!lXW!lXX!lXY!lXZ!lX[!lX!{!lXv!lX~P&POP!kXQ!kXR!kXS!kX!v!gX#P!gXv!gX~OP}OQ}OR!OOS!OO~OP}OQ}OR!OOS!OO!v!gX#P!gXv!gX~O]QO_XO`POaSOfXOnXOoXOyXO!j]O!mRO!tUO!uVO~O|!UO!v!gX#P!gXv!gX~O]jO_XO`POfXOnXOoXOyXO!j]O!mRO!tUO!uVO~OV!YO~O!v!ZO#P!ZO~O]!]Of!]O~OaSOw!^O~P*iO|da!vda#Pda!{davda~P&PO]!`O!j]O~O!m!aO!o!aO!p!aO!q!aO!r!aO!s!aO~OkqO!m!cO!ooO!ppO~O]uOutX~Ou!eO~O!z!hO~P$}OriO!v!jO!z!lO~O!v!mO!z!hO~P*iO!{!qOP!kXQ!kXR!kXS!kXT!kXU!kXV!kXW!kXX!kXY!kXZ!kX[!kX~OT!sOU!sOV!rOW!rOX!rOY!rOZ!rO[!rO~OP}OQ}OR!OOS!OO~P/SOP}OQ}OR!OOS!OO!{!qO~O|!UO!{!qO~O]!tO`PO!j]O~O|!UO!vca#Pca!{cavca~Ou!xO~P/SOu!xO~O^bO!O`O~P)cO^bO!O`O!v!{O~P)cOriO!v!jO!z!}O~O!v!mO!z#PO~P*iOP}OQ}ORiiSii!vii#Pii!{iivii~O^bO!O`O!v#WO~P)cOv#XO~O^bO!O`O!v#YOv!yP~P)cOriO!v!jO!z#^O~O!v!mO!z#_O~P*iO!{ziuzi~P/SOv#`O~O^bO!O`O!v#YOv!yP!S!yP!U!yP~P)cO!v#cO~O^bO!O`O!v#YOv!yX!S!yX!U!yX~P)cOv#eO~Ov#jO!S#fO!U#iO~Ov#oO!S#fO!U#iO~Ou#qO~Ov#oO~Ou#rO~P/SOu#rO~Ov#sO~O!v#tO~O!v#uO~Ofo~",
goto: ".]#PPPPPPPPPPPPPPPPPPPP#Q#a#oP$e#a%^%sP&d&dPP%s&hP&{'fPPP%sP'i'uP'|P(Y(](fP(jP'|(p(v(|)S)Y)c)m)w*Q*XPPPP*_*c*wPP+Z,dP-XPPPPPPPP-]-]-pPP-x.P.PdcOg!Y!e!x!{#W#[#t#uR!S]i^O]g!U!Y!e!x!{#W#[#t#ufQO]g!Y!e!x!{#W#[#t#utjQVW`iny|}!O!i!n!r!s!t!u#O#fR!t!UfWO]g!Y!e!x!{#W#[#t#utXQVW`iny|}!O!i!n!r!s!t!u#O#fQ!`oR!u!Ud[Og!Y!e!x!{#W#[#t#uQ!R]Q!o}R!p!O!]XOQVW]`giny|}!O!Y!e!i!n!r!s!t!u!x!{#O#W#[#f#t#uTqRsYlQWn!t!uQzVQ!gyX!jz!g!k!|dcOg!Y!e!x!{#W#[#t#uYkQWn!t!uQ!S]R!^iRwSQ!S]Q!X`Q#S!sR#m#fZlQWn!t!uecOg!Y!e!x!{#W#[#t#uR#V!xQ#b#WQ#v#tR#w#uT#g#b#hQ#k#bR#p#hQgOR![gQsRR!bsQyVR!fyQvSR!dvW#[!{#W#t#uR#d#[Q!kzQ!|!gT#Q!k!|Q!n|Q#O!iT#R!n#OWnQW!t!uR!_nS!V_!TR!w!VQ#h#bR#n#hTfOgSdOgQ!y!YQ!z!eQ#U!xZ#Z!{#W#[#t#ud_Og!Y!e!x!{#W#[#t#uQ!T]R!v!UdZOg!Y!e!x!{#W#[#t#uYkQWn!t!uQ|VQ!Q]Q!W`Q!^iQ!iyW!m|!i!n#OQ!o}Q!p!OQ#S!rQ#T!sR#l#fdYOg!Y!e!x!{#W#[#t#utjQVW`iny|}!O!i!n!r!s!t!u#O#fR!P]TrRssTOQW]gin!Y!e!t!u!x!{#W#[#t#uQ#]!{V#a#W#t#uZmQWn!t!ueaOg!Y!e!x!{#W#[#t#u",
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 Dict NamedArg NamedArgPrefix FunctionDef Params colon keyword Underscore Array Null ConditionalOp PositionalArg operator IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
maxTerm: 93,
context: trackScope,
nodeProps: [
["closedBy", 37,"end"]
],
propSources: [highlighting],
skippedNodes: [0],
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)],
repeatNodeCount: 10,
tokenData: "AO~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#P6T#P#Q6Y#Q#R#{#R#S6s#S#T#{#T#Y7^#Y#Z8l#Z#b7^#b#c<z#c#f7^#f#g=q#g#h7^#h#i>h#i#o7^#o#p#{#p#q@`#q;'S#{;'S;=`$d<%l~#{~O#{~~@yS$QUkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUkS!dYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UkS!vQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZkS!eYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!eYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!o~~'aO!m~U'hUkS!jQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUkS!{QOt#{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#{U4fW!uQkSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVkSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU!tQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO!p~U6aU!zQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6zUkSwQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7cYkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{U8YUrQkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U8qZkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#U9d#U#o7^#o;'S#{;'S;=`$d<%lO#{U9i[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#`7^#`#a:_#a#o7^#o;'S#{;'S;=`$d<%lO#{U:d[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#g7^#g#h;Y#h#o7^#o;'S#{;'S;=`$d<%lO#{U;_[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#X7^#X#Y<T#Y#o7^#o;'S#{;'S;=`$d<%lO#{U<[YnQkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=RY!qWkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=xY!sWkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^>o[!rWkSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#f7^#f#g?e#g#o7^#o;'S#{;'S;=`$d<%lO#{U?j[kSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#i7^#i#j;Y#j#o7^#o;'S#{;'S;=`$d<%lO#{U@gU|QkSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#P~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!i~~", 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: 924
tokenPrec: 1008
})

View File

@ -201,3 +201,292 @@ describe('array literals', () => {
`)
})
})
describe('dict literals', () => {
test('work with numbers', () => {
expect('[a=1 b=2 c=3]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('work with strings', () => {
expect("[a='one' b='two' c='three']").toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
String
StringFragment one
NamedArg
NamedArgPrefix b=
String
StringFragment two
NamedArg
NamedArgPrefix c=
String
StringFragment three
`)
})
test('work with identifiers', () => {
expect('[a=one b=two c=three]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Identifier one
NamedArg
NamedArgPrefix b=
Identifier two
NamedArg
NamedArgPrefix c=
Identifier three
`)
})
test('can be nested', () => {
expect('[a=one b=[two [c=three]]]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Identifier one
NamedArg
NamedArgPrefix b=
Array
Identifier two
Dict
NamedArg
NamedArgPrefix c=
Identifier three
`)
})
test('can span multiple lines', () => {
expect(`[
a=1
b=2
c=3
]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('empty dict', () => {
expect('[=]').toMatchTree(`
Dict [=]
`)
expect('[ = ]').toMatchTree(`
Array
Word =
`)
})
test('mixed types', () => {
expect("[a=1 b='two' c=three d=true e=null]").toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
String
StringFragment two
NamedArg
NamedArgPrefix c=
Identifier three
NamedArg
NamedArgPrefix d=
Boolean true
NamedArg
NamedArgPrefix e=
Null null
`)
})
test('semicolons as separators', () => {
expect('[a=1; b=2; c=3]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('expressions in dicts', () => {
expect('[a=(1 + 2) b=(3 * 4)]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
ParenExpr
BinOp
Number 1
Plus +
Number 2
NamedArg
NamedArgPrefix b=
ParenExpr
BinOp
Number 3
Star *
Number 4
`)
})
test('mixed separators - spaces and newlines', () => {
expect(`[a=1 b=2
c=3]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('empty lines within dicts', () => {
expect(`[a=1
b=2
c=3]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('comments within dicts', () => {
expect(`[ # something...
a=1 # first
b=2 # second
c=3
]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
test('complex nested multiline', () => {
expect(`[
a=[a=1 b=2]
b=[b=3 c=4]
c=[c=5 d=6]
]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix b=
Dict
NamedArg
NamedArgPrefix b=
Number 3
NamedArg
NamedArgPrefix c=
Number 4
NamedArg
NamedArgPrefix c=
Dict
NamedArg
NamedArgPrefix c=
Number 5
NamedArg
NamedArgPrefix d=
Number 6
`)
})
test('boolean and null literals', () => {
expect('[a=true b=false c=null]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Boolean true
NamedArg
NamedArgPrefix b=
Boolean false
NamedArg
NamedArgPrefix c=
Null null
`)
})
test('regex literals', () => {
expect('[pattern=//[0-9]+//]').toMatchTree(`
Dict
NamedArg
NamedArgPrefix pattern=
Regex //[0-9]+//
`)
})
test('trailing newlines', () => {
expect(`[
a=1
b=2
c=3
]`).toMatchTree(`
Dict
NamedArg
NamedArgPrefix a=
Number 1
NamedArg
NamedArgPrefix b=
Number 2
NamedArg
NamedArgPrefix c=
Number 3
`)
})
})

View File

@ -109,7 +109,10 @@ expect.extend({
return { pass: true }
} else {
return {
message: () => `Expected evaluation to be ${expected}, but got ${value}`,
message: () =>
`Expected evaluation to be ${JSON.stringify(expected)}, but got ${JSON.stringify(
value
)}`,
pass: false,
}
}
@ -163,5 +166,27 @@ const trimWhitespace = (str: string): string => {
}
function isEqual(a: any, b: any): boolean {
return typeof a === 'object' ? JSON.stringify(a) === JSON.stringify(b) : a === b
if (a === null && b === null) return true
switch (typeof a) {
case 'string':
case 'number':
case 'boolean':
case 'undefined':
return a === b
default:
return JSON.stringify(sortKeys(a)) === JSON.stringify(sortKeys(b))
}
}
function sortKeys(o: any): any {
if (Array.isArray(o)) return o.map(sortKeys)
if (o && typeof o === 'object' && o.constructor === Object)
return Object.keys(o)
.sort()
.reduce((r, k) => {
r[k] = sortKeys(o[k])
return r
}, {} as any)
return o
}

View File

@ -1,6 +1,6 @@
import { Tree, TreeCursor } from '@lezer/common'
import { assertNever } from '#utils/utils'
import { type Value } from 'reefvm'
import { type Value, fromValue } from 'reefvm'
export const treeToString = (tree: Tree, input: string): string => {
const lines: string[] = []
@ -35,27 +35,6 @@ export const treeToString = (tree: Tree, input: string): string => {
}
export const VMResultToValue = (result: Value): unknown => {
if (
result.type === 'number' ||
result.type === 'boolean' ||
result.type === 'string' ||
result.type === 'regex'
) {
return result.value
} else if (result.type === 'null') {
return null
} else if (result.type === 'array') {
return result.value.map(VMResultToValue)
} else if (result.type === 'dict') {
const obj: Record<string, unknown> = {}
for (const [key, val] of Object.entries(result.value)) {
obj[key] = VMResultToValue(val)
}
return obj
} else if (result.type === 'function') {
return Function
} else {
assertNever(result)
}
if (result.type === 'function') return Function
else return fromValue(result)
}