Add += and friends #16

Merged
probablycorey merged 3 commits from compound-assignment into main 2025-10-31 17:06:55 +00:00
9 changed files with 233 additions and 56 deletions

View File

@ -9,6 +9,7 @@ import {
checkTreeForErrors,
getAllChildren,
getAssignmentParts,
getCompoundAssignmentParts,
getBinaryParts,
getDotGetParts,
getFunctionCallParts,
@ -247,6 +248,34 @@ export class Compiler {
return instructions
}
case terms.CompoundAssign: {
const { identifier, operator, right } = getCompoundAssignmentParts(node)
const identifierName = input.slice(identifier.from, identifier.to)
const instructions: ProgramItem[] = []
// will throw if undefined
instructions.push(['LOAD', identifierName])
instructions.push(...this.#compileNode(right, input))
const opValue = input.slice(operator.from, operator.to)
switch (opValue) {
case '+=': instructions.push(['ADD']); break
case '-=': instructions.push(['SUB']); break
case '*=': instructions.push(['MUL']); break
case '/=': instructions.push(['DIV']); break
case '%=': instructions.push(['MOD']); break
default:
throw new CompilerError(`Unknown compound operator: ${opValue}`, operator.from, operator.to)
}
// DUP and store (same as regular assignment)
instructions.push(['DUP'])
instructions.push(['STORE', identifierName])
return instructions
}
case terms.ParenExpr: {
const child = node.firstChild
if (!child) return [] // I guess it is empty parentheses?

View File

@ -64,6 +64,38 @@ describe('compiler', () => {
expect('sum = 2 + 3; sum').toEvaluateTo(5)
})
test('compound assignment +=', () => {
expect('x = 10; x += 5; x').toEvaluateTo(15)
})
test('compound assignment -= ', () => {
expect('x = 10; x -= 3; x').toEvaluateTo(7)
})
test('compound assignment *=', () => {
expect('x = 5; x *= 3; x').toEvaluateTo(15)
})
test('compound assignment /=', () => {
expect('x = 20; x /= 4; x').toEvaluateTo(5)
})
test('compound assignment %=', () => {
expect('x = 17; x %= 5; x').toEvaluateTo(2)
})
test('compound assignment with expression', () => {
expect('x = 10; x += 2 + 3; x').toEvaluateTo(15)
})
test('compound assignment returns value', () => {
expect('x = 5; x += 10; x').toEvaluateTo(15)
})
test('compound assignment fails on undefined variable', () => {
expect('undefined-var += 5').toFailEvaluation()
})
test('parentheses', () => {
expect('(2 + 3) * 4').toEvaluateTo(20)
})

View File

@ -57,6 +57,27 @@ export const getAssignmentParts = (node: SyntaxNode) => {
return { identifier: left, right }
}
export const getCompoundAssignmentParts = (node: SyntaxNode) => {
const children = getAllChildren(node)
const [left, operator, right] = children
if (!left || left.type.id !== terms.AssignableIdentifier) {
throw new CompilerError(
`CompoundAssign left child must be an AssignableIdentifier, got ${left ? left.type.name : 'none'}`,
node.from,
node.to
)
} else if (!operator || !right) {
throw new CompilerError(
`CompoundAssign expected 3 children, got ${children.length}`,
node.from,
node.to
)
}
return { identifier: left, operator, right }
}
export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
const children = getAllChildren(node)
const [fnKeyword, paramsNode, colon, ...rest] = children

View File

@ -10,7 +10,14 @@ const operators: Array<Operator> = [
{ str: '!=', tokenName: 'Neq' },
{ str: '==', tokenName: 'EqEq' },
// // Single-char operators
// Compound assignment operators (must come before single-char operators)
{ str: '+=', tokenName: 'PlusEq' },
{ str: '-=', tokenName: 'MinusEq' },
{ str: '*=', tokenName: 'StarEq' },
{ str: '/=', tokenName: 'SlashEq' },
{ str: '%=', tokenName: 'ModuloEq' },
// Single-char operators
{ str: '*', tokenName: 'Star' },
{ str: '=', tokenName: 'Eq' },
{ str: '/', tokenName: 'Slash' },

View File

@ -6,7 +6,7 @@
@top Program { item* }
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo }
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq }
@tokens {
@precedence { Number Regex }
@ -55,6 +55,7 @@ consumeToTerminator {
Throw |
IfExpr |
FunctionDef |
CompoundAssign |
Assign |
BinOp |
ConditionalOp |
@ -181,6 +182,10 @@ Assign {
AssignableIdentifier Eq consumeToTerminator
}
CompoundAssign {
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq) consumeToTerminator
}
BinOp {
expression !multiplicative Modulo expression |
(expression | BinOp) !multiplicative Star (expression | BinOp) |

View File

@ -14,45 +14,51 @@ export const
Gt = 12,
Gte = 13,
Modulo = 14,
Identifier = 15,
AssignableIdentifier = 16,
Word = 17,
IdentifierBeforeDot = 18,
Do = 19,
Program = 20,
PipeExpr = 21,
FunctionCall = 22,
DotGet = 23,
Number = 24,
ParenExpr = 25,
FunctionCallOrIdentifier = 26,
BinOp = 27,
String = 28,
StringFragment = 29,
Interpolation = 30,
EscapeSeq = 31,
Boolean = 32,
Regex = 33,
Dict = 34,
NamedArg = 35,
NamedArgPrefix = 36,
FunctionDef = 37,
Params = 38,
colon = 39,
CatchExpr = 40,
keyword = 63,
TryBlock = 42,
FinallyExpr = 43,
Underscore = 46,
Array = 47,
Null = 48,
ConditionalOp = 49,
PositionalArg = 50,
TryExpr = 52,
Throw = 54,
IfExpr = 56,
SingleLineThenBlock = 58,
ThenBlock = 59,
ElseIfExpr = 60,
ElseExpr = 62,
Assign = 64
PlusEq = 15,
MinusEq = 16,
StarEq = 17,
SlashEq = 18,
ModuloEq = 19,
Identifier = 20,
AssignableIdentifier = 21,
Word = 22,
IdentifierBeforeDot = 23,
Do = 24,
Program = 25,
PipeExpr = 26,
FunctionCall = 27,
DotGet = 28,
Number = 29,
ParenExpr = 30,
FunctionCallOrIdentifier = 31,
BinOp = 32,
String = 33,
StringFragment = 34,
Interpolation = 35,
EscapeSeq = 36,
Boolean = 37,
Regex = 38,
Dict = 39,
NamedArg = 40,
NamedArgPrefix = 41,
FunctionDef = 42,
Params = 43,
colon = 44,
CatchExpr = 45,
keyword = 68,
TryBlock = 47,
FinallyExpr = 48,
Underscore = 51,
Array = 52,
Null = 53,
ConditionalOp = 54,
PositionalArg = 55,
TryExpr = 57,
Throw = 59,
IfExpr = 61,
SingleLineThenBlock = 63,
ThenBlock = 64,
ElseIfExpr = 65,
ElseExpr = 67,
CompoundAssign = 69,
Assign = 70

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,catch:82, finally:88, end:90, null:96, try:106, throw:110, if:114, elseif:122, else:126}
const spec_Identifier = {__proto__:null,catch:92, finally:98, end:100, null:106, try:116, throw:120, if:124, elseif:132, else:136}
export const parser = LRParser.deserialize({
version: 14,
states: "8xQYQbOOO#tQcO'#CvO$qOSO'#CxO%PQbO'#E`OOQ`'#DR'#DROOQa'#DO'#DOO&SQbO'#D]O'XQcO'#ETOOQa'#ET'#ETO)cQcO'#ESO)vQRO'#CwO+SQcO'#EOO+dQcO'#EOO+nQbO'#CuO,fOpO'#CsOOQ`'#EP'#EPO,kQbO'#EOO,rQQO'#EfOOQ`'#Db'#DbO,wQbO'#DdO,wQbO'#EhOOQ`'#Df'#DfO-lQRO'#DnOOQ`'#EO'#EOO-qQQO'#D}OOQ`'#D}'#D}OOQ`'#Do'#DoQYQbOOO-yQbO'#DPOOQa'#ES'#ESOOQ`'#D`'#D`OOQ`'#Ee'#EeOOQ`'#Dv'#DvO.TQbO,59^O.nQbO'#CzO.vQWO'#C{OOOO'#EV'#EVOOOO'#Dp'#DpO/[OSO,59dOOQa,59d,59dOOQ`'#Dr'#DrO/jQbO'#DSO/rQQO,5:zOOQ`'#Dq'#DqO/wQbO,59wO0OQQO,59jOOQa,59w,59wO0ZQbO,59wO,wQbO,59cO,wQbO,59cO,wQbO,59cO,wQbO,59yO,wQbO,59yO,wQbO,59yO0eQRO,59aO0lQRO,59aO0}QRO,59aO0xQQO,59aO1YQQO,59aO1bObO,59_O1mQbO'#DwO1xQbO,59]O2aQbO,5;QO2tQcO,5:OO3jQcO,5:OO3zQcO,5:OO4pQRO,5;SO4wQRO,5;SO5SQbO,5:YOOQ`,5:i,5:iOOQ`-E7m-E7mOOQ`,59k,59kOOQ`-E7t-E7tOOOO,59f,59fOOOO,59g,59gOOOO-E7n-E7nOOQa1G/O1G/OOOQ`-E7p-E7pO5dQbO1G0fOOQ`-E7o-E7oO5wQQO1G/UOOQa1G/c1G/cO6SQbO1G/cOOQO'#Dt'#DtO5wQQO1G/UOOQa1G/U1G/UOOQ`'#Du'#DuO6SQbO1G/cOOQa1G.}1G.}O6{QcO1G.}O7VQcO1G.}O7aQcO1G.}OOQa1G/e1G/eO9PQcO1G/eO9WQcO1G/eO9_QcO1G/eOOQa1G.{1G.{OOQa1G.y1G.yO!aQbO'#CvO&ZQbO'#CrOOQ`,5:c,5:cOOQ`-E7u-E7uO9fQbO1G0lO9qQbO1G0mO:_QbO1G0nOOQ`1G/t1G/tO:rQbO7+&QO9qQbO7+&SO:}QQO7+$pOOQa7+$p7+$pO;YQbO7+$}OOQa7+$}7+$}OOQO-E7r-E7rOOQ`-E7s-E7sO;dQbO'#DUO;iQQO'#DXOOQ`7+&W7+&WO;nQbO7+&WO;sQbO7+&WOOQ`'#Ds'#DsO;{QQO'#DsO<QQbO'#EaOOQ`'#DW'#DWO<tQbO7+&XOOQ`'#Dh'#DhO=PQbO7+&YO=UQbO7+&ZOOQ`<<Il<<IlO=rQbO<<IlO=wQbO<<IlO>PQbO<<InOOQa<<H[<<H[OOQa<<Hi<<HiO>[QQO,59pO>aQbO,59sOOQ`<<Ir<<IrO>tQbO<<IrOOQ`,5:_,5:_OOQ`-E7q-E7qOOQ`<<Is<<IsO>yQbO<<IsO?OQbO<<IsOOQ`<<It<<ItOOQ`'#Di'#DiO?WQbO<<IuOOQ`AN?WAN?WO?cQbOAN?WOOQ`AN?YAN?YO?hQbOAN?YO?mQbOAN?YO?uQbO1G/[O@YQbO1G/_OOQ`1G/_1G/_OOQ`AN?^AN?^OOQ`AN?_AN?_O@pQbOAN?_O,wQbO'#DjOOQ`'#Dx'#DxO@uQbOAN?aOAQQQO'#DlOOQ`AN?aAN?aOAVQbOAN?aOOQ`G24rG24rOOQ`G24tG24tOA[QbOG24tOAaQbO7+$vOOQ`7+$v7+$vOOQ`7+$y7+$yOOQ`G24yG24yOAzQRO,5:UOBRQRO,5:UOOQ`-E7v-E7vOOQ`G24{G24{OB^QbOG24{OBcQQO,5:WOOQ`LD*`LD*`OOQ`<<Hb<<HbOBhQQO1G/pOOQ`LD*gLD*gO@YQbO1G/rO=UQbO7+%[OOQ`7+%^7+%^OOQ`<<Hv<<Hv",
stateData: "Bp~O!oOS!pOS~O_PO`fOaWOb^OcROhWOpWOqWO!QWO!VaO!XcO!ZdO!u]O!xQO#PTO#QUO#RiO~O_mOaWOb^OcROhWOpWOqWOtlO!OnO!QWO!u]O!xQO#PTO#QUO!TjX#RjX#^jX#WjXyjX|jX}jX~OP!vXQ!vXR!vXS!vXT!vXU!vXW!vXX!vXY!vXZ!vX[!vX]!vX^!vX~P!aOmtO!xwO!zrO!{sO~O_xOwvP~O_mOaWOb^OhWOpWOqWOtlO!QWO!u]O!xQO#PTO#QUO#R{O~O#V!OO~P%XO_mOaWOb^OcROhWOpWOqWOtlO!OnO!QWO!u]O!xQO#PTO#QUO~OP!wXQ!wXR!wXS!wXT!wXU!wXW!wXX!wXY!wXZ!wX[!wX]!wX^!wX#R!wX#^!wX#W!wXy!wX|!wX}!wX~P&ZOP!vXQ!vXR!vXS!vXT!vXU!vXW!vXX!vXY!vXZ!vX[!vX]!vX^!vX~O#R!rX#^!rXy!rX|!rX}!rX~P(hOP!ROQ!ROR!SOS!SOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO^!QO~O#R!rX#^!rXy!rX|!rX}!rX~OP!ROQ!ROR!SOS!SO~P*qOT!UOU!VO~P*qO_POaWOb^OcROhWOpWOqWO!QWO!u]O!xQO#PTO#QUO~O!t!]O~O!T!^O~P*qOw!`O~O_mOaWOb^OhWOpWOqWO!QWO!u]O!xQO#PTO#QUO~OV!fO~O#R!gO#^!gO~OcRO!O!iO~P,wO!Tfa#Rfa#^fa#Wfayfa|fa}fa~P&ZO_!kO!u]O~O!x!lO!z!lO!{!lO!|!lO!}!lO#O!lO~OmtO!x!nO!zrO!{sO~O_xOwvX~Ow!pO~O#V!sO~P%XOtlO#R!uO#V!wO~O#R!xO#V!sO~P,wO#W#SO~P(hOP!ROQ!ROR!SOS!SO#W#SO~OT!UOU!VO#W#SO~O!T!^O#W#SO~O_#TOh#TO!u]O~O_#UOb^O!u]O~O!T!^O#Rea#^ea#Weayea|ea}ea~O`fO!VaO!XcO!ZdO#R#ZO~P+nO#R!Wa#^!Way!Wa|!Wa}!Wa~P)vO#R!Wa#^!Way!Wa|!Wa}!Wa~OP!ROQ!ROR!SOS!SO~P3XOT!UOU!VO~P3XOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO~Ow#[O~P4UOT!UOU!VOw#[O~O`fO!VaO!XcO!ZdO~P+nO`fO!VaO!XcO!ZdO#R#_O~P+nOtlO#R!uO#V#aO~O#R!xO#V#cO~P,wO^!QORkiSki#Rki#^ki#Wkiyki|ki}ki~OPkiQki~P6^OP!ROQ!RO~P6^OP!ROQ!RORkiSki#Rki#^ki#Wkiyki|ki}ki~OW!TOX!TOY!TOZ!TO[!TO]!TOT!Ri#R!Ri#^!Ri#W!Riw!Riy!Ri|!Ri}!Ri~OU!VO~P8ROU!VO~P8eOU!Ri~P8ROy#fO|#gO}#hO~O`fO!VaO!XcO!ZdO#R#kOy#TP|#TP}#TP~P+nO`fO!VaO!XcO!ZdO#R#rO~P+nOy#fO|#gO}#sO~OtlO#R!uO#V#wO~O#R!xO#V#xO~P,wO_#yO~Ow#zO~O}#{O~O|#gO}#{O~O#R#}O~O`fO!VaO!XcO!ZdO#R#kOy#TX|#TX}#TX!_#TX!a#TX~P+nOy#fO|#gO}$PO~O}$SO~O`fO!VaO!XcO!ZdO#R#kO}#TP!_#TP!a#TP~P+nO}$VO~O|#gO}$VO~Oy#fO|#gO}$XO~Ow$[O~O`fO!VaO!XcO!ZdO#R$]O~P+nO}$_O~O}$`O~O|#gO}$`O~O}$fO!_$bO!a$eO~O}$hO~O}$iO~O|#gO}$iO~O`fO!VaO!XcO!ZdO#R$kO~P+nO`fO!VaO!XcO!ZdO#R#kO}#TP~P+nO}$nO~O}$rO!_$bO!a$eO~Ow$tO~O}$rO~O}$uO~O`fO!VaO!XcO!ZdO#R#kO|#TP}#TP~P+nOw$wO~P4UOT!UOU!VOw$wO~O}$xO~O#R$yO~O#R$zO~Ohq~",
goto: "3U#^PPPPPPPPPPPPPPPPPPPPP#_#t$YP%X#t&^&|P'v'vPP&|'zP(_)OP)RP)_)hPPP&|P*Q*vP*}P*}P*}P+a+d+mP+qP*}+w+},T,Z,a,m,w-R-[-cPPPP-i-m._PP.w0_P1]PPPPPPPP1a1z1aPP2X2`2`2r2rpgOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zR!Z]u_O]k!^!`!f!p#Z#[#_#m#r#z$[$]$k$y$zrPO]k!`!f!p#Z#[#_#m#r#z$[$]$k$y$zzmPUVcdlq|!P!Q!R!S!T!U!V!t!y#U#V#b$bR#U!^rVO]k!`!f!p#Z#[#_#m#r#z$[$]$k$y$zzWPUVcdlq|!P!Q!R!S!T!U!V!t!y#U#V#b$bQ!krQ#T!]R#V!^pZOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zQ!X]Q!bcQ!z!RR!}!S!oWOPUV]cdklq|!P!Q!R!S!T!U!V!`!f!p!t!y#U#V#Z#[#_#b#m#r#z$[$]$b$k$y$zTtQvYoPVq#U#VQ}UQ!r|X!u}!r!v#`pgOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zYnPVq#U#VQ!Z]R!ilRzRQ#j#YQ#u#^Q$R#oR$Z#vQ#o#ZQ$m$]R$v$kQ#i#YQ#t#^Q#|#jQ$Q#oQ$W#uQ$Y#vQ$a$RR$j$Zp[Ok!`!f!p#Z#[#_#m#r#z$[$]$k$y$zQ!Y]Q!ccQ!edQ#O!VQ#Q!UR$p$bZoPVq#U#VqgOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zR#q#[Q$U#rQ${$yR$|$zT$c$U$dQ$g$UR$s$dQkOR!hkQvQR!mvQ|UR!q|QyRR!oy^#m#Z#_#r$]$k$y$zR$O#mQ!v}Q#`!rT#d!v#`Q!y!PQ#b!tT#e!y#bWqPV#U#VR!jqS!_`![R#X!_Q$d$UR$q$dTjOkShOkQ#Y!`Q#]!fQ#^!p`#l#Z#_#m#r$]$k$y$zQ#p#[Q$^#zR$l$[p`Ok!`!f!p#Z#[#_#m#r#z$[$]$k$y$zQ![]R#W!^rYO]k!`!f!p#Z#[#_#m#r#z$[$]$k$y$zYnPVq#U#VQ!PUQ!acQ!ddQ!ilQ!t|W!x!P!t!y#bQ!z!QQ!{!RQ!|!SQ#O!TQ#P!UQ#R!VR$o$bpXOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zzmPUVcdlq|!P!Q!R!S!T!U!V!t!y#U#V#b$bR!W]TuQv!PSOPV]klq!`!f!p#U#V#Z#[#_#m#r#z$[$]$k$y$zU#n#Z$]$kQ#v#_V$T#r$y$zZpPVq#U#VqbOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$zqeOk!`!f!p#Z#[#_#m#r#z$[$]$k$y$z",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo 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 CatchExpr keyword TryBlock FinallyExpr keyword keyword Underscore Array Null ConditionalOp PositionalArg operator TryExpr keyword Throw keyword IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
maxTerm: 106,
states: "9UQYQbOOO#tQcO'#C{O$qOSO'#C}O%PQbO'#EfOOQ`'#DW'#DWOOQa'#DT'#DTO&SQbO'#DbO'XQcO'#EZOOQa'#EZ'#EZO)cQcO'#EYO)vQRO'#C|O+SQcO'#EUO+dQcO'#EUO+nQbO'#CzO,fOpO'#CxOOQ`'#EV'#EVO,kQbO'#EUO,rQQO'#ElOOQ`'#Dg'#DgO,wQbO'#DiO,wQbO'#EnOOQ`'#Dk'#DkO-lQRO'#DsOOQ`'#EU'#EUO.QQQO'#ETOOQ`'#ET'#ETOOQ`'#Du'#DuQYQbOOO.YQbO'#DUOOQa'#EY'#EYOOQ`'#De'#DeOOQ`'#Ek'#EkOOQ`'#D|'#D|O.dQbO,59cO.}QbO'#DPO/VQWO'#DQOOOO'#E]'#E]OOOO'#Dv'#DvO/kOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO/yQbO'#DXO0RQQO,5;QOOQ`'#Dw'#DwO0WQbO,59|O0_QQO,59oOOQa,59|,59|O0jQbO,59|O,wQbO,59hO,wQbO,59hO,wQbO,59hO,wQbO,5:OO,wQbO,5:OO,wQbO,5:OO0tQRO,59fO0{QRO,59fO1^QRO,59fO1XQQO,59fO1iQQO,59fO1qObO,59dO1|QbO'#D}O2XQbO,59bO2pQbO,5;WO3TQcO,5:TO3yQcO,5:TO4ZQcO,5:TO5PQRO,5;YO5WQRO,5;YO5cQbO,5:_O5cQbO,5:`OOQ`,5:o,5:oOOQ`-E7s-E7sOOQ`,59p,59pOOQ`-E7z-E7zOOOO,59k,59kOOOO,59l,59lOOOO-E7t-E7tOOQa1G/T1G/TOOQ`-E7v-E7vO5sQbO1G0lOOQ`-E7u-E7uO6WQQO1G/ZOOQa1G/h1G/hO6cQbO1G/hOOQO'#Dz'#DzO6WQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D{'#D{O6cQbO1G/hOOQa1G/S1G/SO7[QcO1G/SO7fQcO1G/SO7pQcO1G/SOOQa1G/j1G/jO9`QcO1G/jO9gQcO1G/jO9nQcO1G/jOOQa1G/Q1G/QOOQa1G/O1G/OO!aQbO'#C{O&ZQbO'#CwOOQ`,5:i,5:iOOQ`-E7{-E7{O9uQbO1G0rO:QQbO1G0sO:nQbO1G0tOOQ`1G/y1G/yOOQ`1G/z1G/zO;RQbO7+&WO:QQbO7+&YO;^QQO7+$uOOQa7+$u7+$uO;iQbO7+%SOOQa7+%S7+%SOOQO-E7x-E7xOOQ`-E7y-E7yO;sQbO'#DZO;xQQO'#D^OOQ`7+&^7+&^O;}QbO7+&^O<SQbO7+&^OOQ`'#Dy'#DyO<[QQO'#DyO<aQbO'#EgOOQ`'#D]'#D]O=TQbO7+&_OOQ`'#Dm'#DmO=`QbO7+&`O=eQbO7+&aOOQ`<<Ir<<IrO>RQbO<<IrO>WQbO<<IrO>`QbO<<ItOOQa<<Ha<<HaOOQa<<Hn<<HnO>kQQO,59uO>pQbO,59xOOQ`<<Ix<<IxO?TQbO<<IxOOQ`,5:e,5:eOOQ`-E7w-E7wOOQ`<<Iy<<IyO?YQbO<<IyO?_QbO<<IyOOQ`<<Iz<<IzOOQ`'#Dn'#DnO?gQbO<<I{OOQ`AN?^AN?^O?rQbOAN?^OOQ`AN?`AN?`O?wQbOAN?`O?|QbOAN?`O@UQbO1G/aO@iQbO1G/dOOQ`1G/d1G/dOOQ`AN?dAN?dOOQ`AN?eAN?eOAPQbOAN?eO,wQbO'#DoOOQ`'#EO'#EOOAUQbOAN?gOAaQQO'#DqOOQ`AN?gAN?gOAfQbOAN?gOOQ`G24xG24xOOQ`G24zG24zOAkQbOG24zOApQbO7+${OOQ`7+${7+${OOQ`7+%O7+%OOOQ`G25PG25POBZQRO,5:ZOBbQRO,5:ZOOQ`-E7|-E7|OOQ`G25RG25ROBmQbOG25ROBrQQO,5:]OOQ`LD*fLD*fOOQ`<<Hg<<HgOBwQQO1G/uOOQ`LD*mLD*mO@iQbO1G/wO=eQbO7+%aOOQ`7+%c7+%cOOQ`<<H{<<H{",
stateData: "CP~O!uOS!vOS~OdPOefOfWOg^OhROmWOuWOvWO!VWO![aO!^cO!`dO!{]O#OQO#VTO#WUO#XiO~OdmOfWOg^OhROmWOuWOvWOylO!TnO!VWO!{]O#OQO#VTO#WUO!YoX#XoX#doX#^oX!OoX!RoX!SoX~OP!|XQ!|XR!|XS!|XT!|XU!|XW!|XX!|XY!|XZ!|X[!|X]!|X^!|X~P!aOrtO#OwO#QrO#RsO~OdxO|{P~OdmOfWOg^OmWOuWOvWOylO!VWO!{]O#OQO#VTO#WUO#X{O~O#]!OO~P%XOdmOfWOg^OhROmWOuWOvWOylO!TnO!VWO!{]O#OQO#VTO#WUO~OP!}XQ!}XR!}XS!}XT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X^!}X#X!}X#d!}X#^!}X!O!}X!R!}X!S!}X~P&ZOP!|XQ!|XR!|XS!|XT!|XU!|XW!|XX!|XY!|XZ!|X[!|X]!|X^!|X~O#X!xX#d!xX!O!xX!R!xX!S!xX~P(hOP!ROQ!ROR!SOS!SOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO^!QO~O#X!xX#d!xX!O!xX!R!xX!S!xX~OP!ROQ!ROR!SOS!SO~P*qOT!UOU!VO~P*qOdPOfWOg^OhROmWOuWOvWO!VWO!{]O#OQO#VTO#WUO~O!z!]O~O!Y!^O~P*qO|!`O~OdmOfWOg^OmWOuWOvWO!VWO!{]O#OQO#VTO#WUO~OV!gO_!fO`!fOa!fOb!fOc!fO~O#X!hO#d!hO~OhRO!T!jO~P,wO!Yka#Xka#dka#^ka!Oka!Rka!Ska~P&ZOd!lO!{]O~O#O!mO#Q!mO#R!mO#S!mO#T!mO#U!mO~OrtO#O!oO#QrO#RsO~OdxO|{X~O|!qO~O#]!tO~P%XOylO#X!vO#]!xO~O#X!yO#]!tO~P,wO#^#TO~P(hOP!ROQ!ROR!SOS!SO#^#TO~OT!UOU!VO#^#TO~O!Y!^O#^#TO~Od#UOm#UO!{]O~Od#VOg^O!{]O~O!Y!^O#Xja#dja#^ja!Oja!Rja!Sja~OefO![aO!^cO!`dO#X#[O~P+nO#X!]a#d!]a!O!]a!R!]a!S!]a~P)vO#X!]a#d!]a!O!]a!R!]a!S!]a~OP!ROQ!ROR!SOS!SO~P3hOT!UOU!VO~P3hOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO~O|#]O~P4eOT!UOU!VO|#]O~OefO![aO!^cO!`dO~P+nOefO![aO!^cO!`dO#X#aO~P+nOylO#X!vO#]#cO~O#X!yO#]#eO~P,wO^!QORpiSpi#Xpi#dpi#^pi!Opi!Rpi!Spi~OPpiQpi~P6mOP!ROQ!RO~P6mOP!ROQ!RORpiSpi#Xpi#dpi#^pi!Opi!Rpi!Spi~OW!TOX!TOY!TOZ!TO[!TO]!TOT!Wi#X!Wi#d!Wi#^!Wi|!Wi!O!Wi!R!Wi!S!Wi~OU!VO~P8bOU!VO~P8tOU!Wi~P8bO!O#hO!R#iO!S#jO~OefO![aO!^cO!`dO#X#mO!O#ZP!R#ZP!S#ZP~P+nOefO![aO!^cO!`dO#X#tO~P+nO!O#hO!R#iO!S#uO~OylO#X!vO#]#yO~O#X!yO#]#zO~P,wOd#{O~O|#|O~O!S#}O~O!R#iO!S#}O~O#X$PO~OefO![aO!^cO!`dO#X#mO!O#ZX!R#ZX!S#ZX!d#ZX!f#ZX~P+nO!O#hO!R#iO!S$RO~O!S$UO~OefO![aO!^cO!`dO#X#mO!S#ZP!d#ZP!f#ZP~P+nO!S$XO~O!R#iO!S$XO~O!O#hO!R#iO!S$ZO~O|$^O~OefO![aO!^cO!`dO#X$_O~P+nO!S$aO~O!S$bO~O!R#iO!S$bO~O!S$hO!d$dO!f$gO~O!S$jO~O!S$kO~O!R#iO!S$kO~OefO![aO!^cO!`dO#X$mO~P+nOefO![aO!^cO!`dO#X#mO!S#ZP~P+nO!S$pO~O!S$tO!d$dO!f$gO~O|$vO~O!S$tO~O!S$wO~OefO![aO!^cO!`dO#X#mO!R#ZP!S#ZP~P+nO|$yO~P4eOT!UOU!VO|$yO~O!S$zO~O#X${O~O#X$|O~Omv~",
goto: "3n#dPPPPPPPPPPPPPPPPPPPPPPPPPP#e#{$bP%b#{&h'XP(S(SPP'X(WP(k)]P)`P)l)uPPP'XP*_+UP+]P+]P+]P+p+s+|P,QP+]+],W,^,d,j,p,|-W-b-k-rPPPP-x-|.qPP/[0sP1rPPPPPPPP1v2b1vPP2o2v2v3Z3ZrgOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|R!Z]w_O]k!^!`!f!g!q#[#]#a#o#t#|$^$_$m${$|tPO]k!`!f!g!q#[#]#a#o#t#|$^$_$m${$|zmPUVcdlq|!P!Q!R!S!T!U!V!u!z#V#W#d$dR#V!^tVO]k!`!f!g!q#[#]#a#o#t#|$^$_$m${$|zWPUVcdlq|!P!Q!R!S!T!U!V!u!z#V#W#d$dQ!lrQ#U!]R#W!^rZOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|Q!X]Q!bcQ!{!RR#O!S!qWOPUV]cdklq|!P!Q!R!S!T!U!V!`!f!g!q!u!z#V#W#[#]#a#d#o#t#|$^$_$d$m${$|TtQvYoPVq#V#WQ}UQ!s|X!v}!s!w#brgOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|YnPVq#V#WQ!Z]R!jlRzRQ#l#ZQ#w#`Q$T#qR$]#xQ#q#[Q$o$_R$x$mQ#k#ZQ#v#`Q$O#lQ$S#qQ$Y#wQ$[#xQ$c$TR$l$]r[Ok!`!f!g!q#[#]#a#o#t#|$^$_$m${$|Q!Y]Q!ccQ!edQ#P!VQ#R!UR$r$dZoPVq#V#WsgOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|R#s#]Q$W#tQ$}${R%O$|T$e$W$fQ$i$WR$u$fQkOR!ikQvQR!nvQ|UR!r|QyRR!py^#o#[#a#t$_$m${$|R$Q#oQ!w}Q#b!sT#f!w#bQ!z!PQ#d!uT#g!z#dWqPV#V#WR!kqS!_`![R#Y!_Q$f$WR$s$fTjOkShOkQ#Z!`Q#^!fQ#_!gQ#`!q`#n#[#a#o#t$_$m${$|Q#r#]Q$`#|R$n$^r`Ok!`!f!g!q#[#]#a#o#t#|$^$_$m${$|Q![]R#X!^tYO]k!`!f!g!q#[#]#a#o#t#|$^$_$m${$|YnPVq#V#WQ!PUQ!acQ!ddQ!jlQ!u|W!y!P!u!z#dQ!{!QQ!|!RQ!}!SQ#P!TQ#Q!UQ#S!VR$q$drXOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|zmPUVcdlq|!P!Q!R!S!T!U!V!u!z#V#W#d$dR!W]TuQv!RSOPV]klq!`!f!g!q#V#W#[#]#a#o#t#|$^$_$m${$|U#p#[$_$mQ#x#aV$V#t${$|ZpPVq#V#WsbOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|seOk!`!f!g!q#[#]#a#o#t#|$^$_$m${$|",
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 colon CatchExpr keyword TryBlock FinallyExpr keyword keyword Underscore Array Null ConditionalOp PositionalArg operator TryExpr keyword Throw keyword IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword CompoundAssign Assign",
maxTerm: 112,
context: trackScope,
nodeProps: [
["closedBy", 39,"end"]
["closedBy", 44,"end"]
],
propSources: [highlighting],
skippedNodes: [0],
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$QUmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUmS!oYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UmS#RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZmS!pYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!pYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!z~~'aO!x~U'hUmS!uQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUmS#WQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWmSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYmShQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWmSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWmShQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^WmSOt#{uw#{x!P#{!P!Q+v!Q#O#{#P;'S#{;'S;=`$d<%lO#{U+{^mSOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q#{!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wU-O^mSqQOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q0o!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wQ.PXqQOY-zZ!P-z!P!Q.l!Q!}-z!}#O/Z#O#P0Y#P;'S-z;'S;=`0i<%lO-zQ.oP!P!Q.rQ.wUqQ#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-zU0tWmSOt#{uw#{x!P#{!P!Q1^!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1ebmSqQOt#{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[mSOY2mYZ#{Zt2mtu/Zuw2mwx/Zx#O2m#O#P/s#P#Q,w#Q;'S2m;'S;=`3h<%lO2mU3kP;=`<%l2mU3qP;=`<%l,wU3{UmSwQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4fW#QQmSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVmSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU#PQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO!{~U6aU#VQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6zUmS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7cYmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{U8YUtQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U8qZmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#U9d#U#o7^#o;'S#{;'S;=`$d<%lO#{U9i[mSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#`7^#`#a:_#a#o7^#o;'S#{;'S;=`$d<%lO#{U:d[mSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#g7^#g#h;Y#h#o7^#o;'S#{;'S;=`$d<%lO#{U;_[mSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#X7^#X#Y<T#Y#o7^#o;'S#{;'S;=`$d<%lO#{U<[YpQmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=RY!|WmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=xY#OWmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^>o[!}WmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#f7^#f#g?e#g#o7^#o;'S#{;'S;=`$d<%lO#{U?j[mSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#i7^#i#j;Y#j#o7^#o;'S#{;'S;=`$d<%lO#{U@gU!TQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#^~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!t~~", 11)],
topRules: {"Program":[0,20]},
specialized: [{term: 15, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 15, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 1547
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$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!uYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#XQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!vYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!vYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#Q~~'aO#O~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+^WrSOt#{uw#{x!P#{!P!Q+v!Q#O#{#P;'S#{;'S;=`$d<%lO#{U+{^rSOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q#{!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wU-O^rSvQOY,wYZ#{Zt,wtu-zuw,wwx-zx!P,w!P!Q0o!Q!},w!}#O2m#O#P0Y#P;'S,w;'S;=`3n<%lO,wQ.PXvQOY-zZ!P-z!P!Q.l!Q!}-z!}#O/Z#O#P0Y#P;'S-z;'S;=`0i<%lO-zQ.oP!P!Q.rQ.wUvQ#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-zU0tWrSOt#{uw#{x!P#{!P!Q1^!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1ebrSvQOt#{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[rSOY2mYZ#{Zt2mtu/Zuw2mwx/Zx#O2m#O#P/s#P#Q,w#Q;'S2m;'S;=`3h<%lO2mU3kP;=`<%l2mU3qP;=`<%l,wU3{UrS|QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4fW#WQrSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVrSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU#VQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO#R~U6aU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6zUrS!TQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7cYrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{U8YUyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U8qZrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#U9d#U#o7^#o;'S#{;'S;=`$d<%lO#{U9i[rSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#`7^#`#a:_#a#o7^#o;'S#{;'S;=`$d<%lO#{U:d[rSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#g7^#g#h;Y#h#o7^#o;'S#{;'S;=`$d<%lO#{U;_[rSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#X7^#X#Y<T#Y#o7^#o;'S#{;'S;=`$d<%lO#{U<[YuQrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=RY#SWrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=xY#UWrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^>o[#TWrSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#f7^#f#g?e#g#o7^#o;'S#{;'S;=`$d<%lO#{U?j[rSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#i7^#i#j;Y#j#o7^#o;'S#{;'S;=`$d<%lO#{U@gU!YQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#d~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!z~~", 11)],
topRules: {"Program":[0,25]},
specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 1562
})

View File

@ -532,6 +532,72 @@ describe('Assign', () => {
})
})
describe('CompoundAssign', () => {
test('parses += operator', () => {
expect('x += 5').toMatchTree(`
CompoundAssign
AssignableIdentifier x
PlusEq +=
Number 5`)
})
test('parses -= operator', () => {
expect('count -= 1').toMatchTree(`
CompoundAssign
AssignableIdentifier count
MinusEq -=
Number 1`)
})
test('parses *= operator', () => {
expect('total *= 2').toMatchTree(`
CompoundAssign
AssignableIdentifier total
StarEq *=
Number 2`)
})
test('parses /= operator', () => {
expect('value /= 10').toMatchTree(`
CompoundAssign
AssignableIdentifier value
SlashEq /=
Number 10`)
})
test('parses %= operator', () => {
expect('remainder %= 3').toMatchTree(`
CompoundAssign
AssignableIdentifier remainder
ModuloEq %=
Number 3`)
})
test('parses compound assignment with expression', () => {
expect('x += 1 + 2').toMatchTree(`
CompoundAssign
AssignableIdentifier x
PlusEq +=
BinOp
Number 1
Plus +
Number 2`)
})
test('parses compound assignment with function call', () => {
expect('total += add 5 3').toMatchTree(`
CompoundAssign
AssignableIdentifier total
PlusEq +=
FunctionCall
Identifier add
PositionalArg
Number 5
PositionalArg
Number 3`)
})
})
describe('DotGet whitespace sensitivity', () => {
test('no whitespace - DotGet works when identifier in scope', () => {
expect('basename = 5; basename.prop').toMatchTree(`

View File

@ -184,6 +184,17 @@ const chooseIdentifierToken = (input: InputStream, stack: Stack): number => {
}
const nextCh = getFullCodePoint(input, peekPos)
const nextCh2 = getFullCodePoint(input, peekPos + 1)
// Check for compound assignment operators: +=, -=, *=, /=, %=
if ([43/* + */, 45/* - */, 42/* * */, 47/* / */, 37/* % */].includes(nextCh) && nextCh2 === 61/* = */) {
// Found compound operator, check if it's followed by whitespace
const charAfterOp = getFullCodePoint(input, peekPos + 2)
if (isWhiteSpace(charAfterOp) || charAfterOp === -1 /* EOF */) {
return AssignableIdentifier
}
}
if (nextCh === 61 /* = */) {
// Found '=', but check if it's followed by whitespace
// If '=' is followed by non-whitespace (like '=cool*'), it won't be tokenized as Eq