Add ?? and ??= operators #42

Merged
defunkt merged 1 commits from null-check-operators into main 2025-11-09 00:12:35 +00:00
8 changed files with 308 additions and 65 deletions
Showing only changes of commit 019f7d84b1 - Show all commits

View File

@ -303,13 +303,31 @@ export class Compiler {
const { identifier, operator, right } = getCompoundAssignmentParts(node)
const identifierName = input.slice(identifier.from, identifier.to)
const instructions: ProgramItem[] = []
const opValue = input.slice(operator.from, operator.to)
// will throw if undefined
instructions.push(['LOAD', identifierName])
// Special handling for ??= since it needs conditional evaluation
if (opValue === '??=') {
instructions.push(['LOAD', identifierName])
const rightInstructions = this.#compileNode(right, input)
instructions.push(['DUP'])
instructions.push(['PUSH', null])
instructions.push(['NEQ'])
instructions.push(['JUMP_IF_TRUE', rightInstructions.length + 1])
instructions.push(['POP'])
instructions.push(...rightInstructions)
instructions.push(['DUP'])
instructions.push(['STORE', identifierName])
return instructions
}
// Standard compound assignments: evaluate both sides, then operate
instructions.push(['LOAD', identifierName]) // will throw if undefined
instructions.push(...this.#compileNode(right, input))
const opValue = input.slice(operator.from, operator.to)
switch (opValue) {
case '+=':
instructions.push(['ADD'])
@ -638,6 +656,18 @@ export class Compiler {
break
case '??':
// Nullish coalescing: return left if not null, else right
instructions.push(...leftInstructions)
instructions.push(['DUP'])
instructions.push(['PUSH', null])
instructions.push(['NEQ'])
instructions.push(['JUMP_IF_TRUE', rightInstructions.length + 1])
instructions.push(['POP'])
instructions.push(...rightInstructions)
break
default:
throw new CompilerError(`Unsupported conditional operator: ${opValue}`, op.from, op.to)
}

View File

@ -333,3 +333,118 @@ describe('default params', () => {
).toEvaluateTo({ name: 'Jon', age: 21 })
})
})
describe('Nullish coalescing operator (??)', () => {
test('returns left side when not null', () => {
expect('5 ?? 10').toEvaluateTo(5)
})
test('returns right side when left is null', () => {
expect('null ?? 10').toEvaluateTo(10)
})
test('returns left side when left is false', () => {
expect('false ?? 10').toEvaluateTo(false)
})
test('returns left side when left is 0', () => {
expect('0 ?? 10').toEvaluateTo(0)
})
test('returns left side when left is empty string', () => {
expect(`'' ?? 'default'`).toEvaluateTo('')
})
test('chains left to right', () => {
expect('null ?? null ?? 42').toEvaluateTo(42)
expect('null ?? 10 ?? 20').toEvaluateTo(10)
})
test('short-circuits evaluation', () => {
const throwError = () => { throw new Error('Should not evaluate') }
expect('5 ?? throw-error').toEvaluateTo(5, { 'throw-error': throwError })
})
test('works with variables', () => {
expect('x = null; x ?? 5').toEvaluateTo(5)
expect('y = 3; y ?? 5').toEvaluateTo(3)
})
test('works with function calls', () => {
const getValue = () => null
const getDefault = () => 42
// Note: identifiers without parentheses refer to the function, not call it
// Use explicit call syntax to invoke the function
expect('(get-value) ?? (get-default)').toEvaluateTo(42, {
'get-value': getValue,
'get-default': getDefault
})
})
})
describe('Nullish coalescing assignment (??=)', () => {
test('assigns when variable is null', () => {
expect('x = null; x ??= 5; x').toEvaluateTo(5)
})
test('does not assign when variable is not null', () => {
expect('x = 3; x ??= 10; x').toEvaluateTo(3)
})
test('does not assign when variable is false', () => {
expect('x = false; x ??= true; x').toEvaluateTo(false)
})
test('does not assign when variable is 0', () => {
expect('x = 0; x ??= 100; x').toEvaluateTo(0)
})
test('does not assign when variable is empty string', () => {
expect(`x = ''; x ??= 'default'; x`).toEvaluateTo('')
})
test('returns the final value', () => {
expect('x = null; x ??= 5').toEvaluateTo(5)
expect('y = 3; y ??= 10').toEvaluateTo(3)
})
test('short-circuits evaluation when not null', () => {
const throwError = () => { throw new Error('Should not evaluate') }
expect('x = 5; x ??= throw-error; x').toEvaluateTo(5, { 'throw-error': throwError })
})
test('works with expressions', () => {
expect('x = null; x ??= 2 + 3; x').toEvaluateTo(5)
})
test('works with function calls', () => {
const getDefault = () => 42
expect('x = null; x ??= (get-default); x').toEvaluateTo(42, { 'get-default': getDefault })
})
test('throws when variable is undefined', () => {
expect(() => expect('undefined-var ??= 5').toEvaluateTo(null)).toThrow()
})
})
describe('Compound assignment operators', () => {
test('+=', () => {
expect('x = 5; x += 3; x').toEvaluateTo(8)
})
test('-=', () => {
expect('x = 10; x -= 4; x').toEvaluateTo(6)
})
test('*=', () => {
expect('x = 3; x *= 4; x').toEvaluateTo(12)
})
test('/=', () => {
expect('x = 20; x /= 5; x').toEvaluateTo(4)
})
test('%=', () => {
expect('x = 10; x %= 3; x').toEvaluateTo(1)
})
})

View File

@ -17,12 +17,16 @@ const operators: Array<Operator> = [
{ str: '==', tokenName: 'EqEq' },
// Compound assignment operators (must come before single-char operators)
{ str: '??=', tokenName: 'NullishEq' },
{ str: '+=', tokenName: 'PlusEq' },
{ str: '-=', tokenName: 'MinusEq' },
{ str: '*=', tokenName: 'StarEq' },
{ str: '/=', tokenName: 'SlashEq' },
{ str: '%=', tokenName: 'ModuloEq' },
// Nullish coalescing (must come before it could be mistaken for other tokens)
{ str: '??', tokenName: 'NullishCoalesce' },
// Single-char operators
{ str: '*', tokenName: 'Star' },
{ str: '=', tokenName: 'Eq' },

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, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq, Band, Bor, Bxor, Shl, Shr, Ushr }
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq, Band, Bor, Bxor, Shl, Shr, Ushr, NullishCoalesce, NullishEq }
@tokens {
@precedence { Number Regex }
@ -48,6 +48,7 @@ null { @specialize[@name=Null]<Identifier, "null"> }
pipe @left,
or @left,
and @left,
nullish @left,
comparison @left,
multiplicative @left,
additive @left,
@ -166,7 +167,8 @@ ConditionalOp {
expression !comparison Gt expression |
expression !comparison Gte expression |
(expression | ConditionalOp) !and And (expression | ConditionalOp) |
(expression | ConditionalOp) !or Or (expression | ConditionalOp)
(expression | ConditionalOp) !or Or (expression | ConditionalOp) |
(expression | ConditionalOp) !nullish NullishCoalesce (expression | ConditionalOp)
}
Params {
@ -182,7 +184,7 @@ Assign {
}
CompoundAssign {
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq) consumeToTerminator
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq | NullishEq) consumeToTerminator
}
BinOp {

View File

@ -25,49 +25,51 @@ export const
Shl = 23,
Shr = 24,
Ushr = 25,
Identifier = 26,
AssignableIdentifier = 27,
Word = 28,
IdentifierBeforeDot = 29,
Do = 30,
Comment = 31,
Program = 32,
PipeExpr = 33,
WhileExpr = 35,
keyword = 77,
ConditionalOp = 37,
ParenExpr = 38,
FunctionCallWithNewlines = 39,
DotGet = 40,
Number = 41,
PositionalArg = 42,
FunctionDef = 43,
Params = 44,
NamedParam = 45,
NamedArgPrefix = 46,
String = 47,
StringFragment = 48,
Interpolation = 49,
EscapeSeq = 50,
Boolean = 51,
Null = 52,
colon = 53,
CatchExpr = 54,
Block = 56,
FinallyExpr = 57,
Underscore = 60,
NamedArg = 61,
IfExpr = 62,
FunctionCall = 64,
ElseIfExpr = 65,
ElseExpr = 67,
FunctionCallOrIdentifier = 68,
BinOp = 69,
Regex = 70,
Dict = 71,
Array = 72,
FunctionCallWithBlock = 73,
TryExpr = 74,
Throw = 76,
CompoundAssign = 78,
Assign = 79
NullishCoalesce = 26,
NullishEq = 27,
Identifier = 28,
AssignableIdentifier = 29,
Word = 30,
IdentifierBeforeDot = 31,
Do = 32,
Comment = 33,
Program = 34,
PipeExpr = 35,
WhileExpr = 37,
keyword = 79,
ConditionalOp = 39,
ParenExpr = 40,
FunctionCallWithNewlines = 41,
DotGet = 42,
Number = 43,
PositionalArg = 44,
FunctionDef = 45,
Params = 46,
NamedParam = 47,
NamedArgPrefix = 48,
String = 49,
StringFragment = 50,
Interpolation = 51,
EscapeSeq = 52,
Boolean = 53,
Null = 54,
colon = 55,
CatchExpr = 56,
Block = 58,
FinallyExpr = 59,
Underscore = 62,
NamedArg = 63,
IfExpr = 64,
FunctionCall = 66,
ElseIfExpr = 67,
ElseExpr = 69,
FunctionCallOrIdentifier = 70,
BinOp = 71,
Regex = 72,
Dict = 73,
Array = 74,
FunctionCallWithBlock = 75,
TryExpr = 76,
Throw = 78,
CompoundAssign = 80,
Assign = 81

View File

@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer"
import {tokenizer, specializeKeyword} from "./tokenizer"
import {trackScope} from "./parserScopeContext"
import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,while:72, null:104, catch:110, finally:116, end:118, if:126, else:132, try:150, throw:154}
const spec_Identifier = {__proto__:null,while:76, null:108, catch:114, finally:120, end:122, if:130, else:136, try:154, throw:158}
export const parser = LRParser.deserialize({
version: 14,
states: "<bQYQbOOO!dOpO'#DUO!iOSO'#D]O%ZQcO'#DrO(QQcO'#EbOOQ`'#Ep'#EpO(kQRO'#DsO*mQcO'#E`O+WQbO'#DSOOQa'#Du'#DuO-]QbO'#DvOOQa'#Eb'#EbO-dQcO'#EbO/_QcO'#EaO0dQcO'#E`O0nQRO'#D|OOQ`'#E`'#E`O1SQbO'#E`O1ZQQO'#E_OOQ`'#E_'#E_OOQ`'#EO'#EOQYQbOOO1fQbO'#DXO1qQbO'#DlO2fQbO'#DPO3ZQQO'#DxO2fQbO'#DzO3`ObO,59pO3kQbO'#D_O3sQWO'#D`OOOO'#Eh'#EhOOOO'#ET'#ETO4XOSO,59wOOQa,59w,59wOOQ`'#DW'#DWO4gQbO'#DkOOQ`'#Ef'#EfOOQ`'#EW'#EWO4qQbO,5:YOOQa'#Ea'#EaO2fQbO,5:_O2fQbO,5:_O2fQbO,5:_O2fQbO,5:_O2fQbO,59mO2fQbO,59mO2fQbO,59mOOQ`'#EQ'#EQO+WQbO,59nO5kQcO'#DrO5rQcO'#EbO5yQRO,59nO6TQQO,59nO6YQQO,59nO6bQQO,59nO6mQRO,59nO6tQRO,59nO7VQQO'#C}O7[QbO,5:bO7cQQO,5:aOOQa,5:b,5:bO7nQbO,5:bO7xQbO,5:iO7xQbO,5:hO9PQbO,5:cO9WQbO,59iOOQ`,5:y,5:yO7xQbO'#EPOOQ`-E7|-E7|OOQ`'#ER'#ERO9rQbO'#DYO9}QbO'#DZOOQO'#ES'#ESO9uQQO'#DYO:]QQO,59sO:bQcO'#EaO;[QRO'#EoO<RQRO'#EoOOQO'#Eo'#EoO<YQQO,5:WO<_QRO,59kO<fQRO,59kO9PQbO,5:dO<qQcO,5:fO>PQcO,5:fO>mQcO,5:fOOQa1G/[1G/[OOOO,59y,59yOOOO,59z,59zOOOO-E8R-E8ROOQa1G/c1G/cOOQ`,5:V,5:VOOQ`-E8U-E8UOOQa1G/y1G/yO@fQcO1G/yO@pQcO1G/yOBOQcO1G/yOBYQcO1G/yOBgQcO1G/yOOQa1G/X1G/XOCuQcO1G/XOC|QcO1G/XODTQcO1G/XOOQ`-E8O-E8OOD[QRO1G/YODfQQO1G/YODkQQO1G/YODsQQO1G/YOEOQRO1G/YOEVQRO1G/YOEhQbO,59oOErQQO1G/YOOQa1G/Y1G/YOEzQQO1G/{OOQa1G/|1G/|OFVQbO1G/|OOQO'#EY'#EYOEzQQO1G/{OOQa1G/{1G/{OOQ`'#EZ'#EZOFVQbO1G/|OFaQbO1G0TOF{QbO1G0SOGgQbO'#DfOGxQbO'#DfOH]QbO1G/}OOQ`-E7}-E7}OOQ`,5:k,5:kOOQ`-E8P-E8POHhQQO,59tOOQO,59u,59uOOQO-E8Q-E8QOHpQbO1G/_O9PQbO1G/rO9PQbO1G/VOHwQbO1G0OOISQQO7+$tOOQa7+$t7+$tOI[QQO1G/ZOIdQQO7+%gOOQa7+%g7+%gOIoQbO7+%hOOQa7+%h7+%hOOQO-E8W-E8WOOQ`-E8X-E8XOOQ`'#EU'#EUOIyQQO'#EUOJRQbO'#EnOOQ`,5:Q,5:QOJfQbO'#DdOJkQQO'#DgOOQ`7+%i7+%iOJpQbO7+%iOJuQbO7+%iOJ}QbO7+$yOK]QbO7+$yOKmQbO7+%^OKuQbO7+$qOOQ`7+%j7+%jOKzQbO7+%jOLPQbO7+%jOOQa<<H`<<H`OLXQbO7+$uOLfQQO7+$uOOQa<<IR<<IROOQa<<IS<<ISOOQ`,5:p,5:pOOQ`-E8S-E8SOLnQQO,5:OO9PQbO,5:ROOQ`<<IT<<ITOLsQbO<<ITOOQ`<<He<<HeOLxQbO<<HeOL}QbO<<HeOMVQbO<<HeOOQ`'#EX'#EXOMbQbO<<HxOMjQbO'#DqOOQ`<<Hx<<HxOMrQbO<<HxOOQ`<<H]<<H]OOQ`<<IU<<IUOMwQbO<<IUOOQO,5:q,5:qOM|QbO<<HaOOQO-E8T-E8TO9PQbO1G/jOOQ`1G/m1G/mOOQ`AN>oAN>oOOQ`AN>PAN>PONZQbOAN>PON`QbOAN>POOQ`-E8V-E8VOOQ`AN>dAN>dONhQbOAN>dO1qQbO,5:ZO9PQbO,5:]OOQ`AN>pAN>pPEhQbO'#EQOOQ`7+%U7+%UOOQ`G23kG23kONmQbOG23kPMmQbO'#DoOOQ`G24OG24OONrQQO1G/uOOQ`1G/w1G/wOOQ`LD)VLD)VO9PQbO7+%aOOQ`<<H{<<H{",
stateData: "Nz~O#QOSoOS~OjROk_OlZOmPOnfOthOyZO!TZO!UZO!agO!hZO!miO!ojO#VWO#WcO#ZQO#fXO#gYO~O#XkO~O!QnO#ZqO#]lO#^mO~OjwOlZOmPOnfOyZO!OsO!TZO!UZO!^rO!hZO#VWO#ZQO#fXO#gYOP#TXQ#TXR#TXS#TXT#TXU#TXW#TXX#TXY#TXZ#TX[#TX]#TX^#TXd#TXe#TXf#TXg#TXh#TXi#TXr!fX!V!fX#e!fX~O#W!fX#i!fX!X!fX![!fX!]!fX!d!fX~P!wOjwOlZOmPOnfOyZO!OsO!TZO!UZO!^rO!hZO#VWO#ZQO#fXO#gYOP#UXQ#UXR#UXS#UXT#UXU#UXW#UXX#UXY#UXZ#UX[#UX]#UX^#UXd#UXe#UXf#UXg#UXh#UXi#UXr#UX#e#UX~O#W#UX#i#UX!V#UX!X#UX![#UX!]#UX!d#UX~P%qOPyOQyORzOSzOT}OU!OOW|OX|OY|OZ|O[|O]|O^xOd{Oe{Of{Og{Oh{Oi{O~OPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{Or#SX~O#W#SX#i#SX!X#SX![#SX!]#SX#e#SX!d#SX~P)xOj!ROk_OlZOmPOnfOthOyZO!TZO!UZO!agO!hZO!miO!ojO#VWO#W!PO#ZQO#fXO#gYO~OjwOlZOmPOyZO!OsO!TZO!UZO!hZO#VWO#W!PO#ZQO#fXO#gYO~O#h!^O~P,bOV!`O#W#UX#i#UX!X#UX![#UX!]#UX!d#UX~P&mOP#TXQ#TXR#TXS#TXT#TXU#TXW#TXX#TXY#TXZ#TX[#TX]#TX^#TXd#TXe#TXf#TXg#TXh#TXi#TXr#SX~O#W#SX#i#SX!X#SX![#SX!]#SX#e#SX!d#SX~P-}Or#SX#W#SX#i#SX!X#SX![#SX!]#SX#e#SX!d#SX~OT}OU!OO~P/xOV!`O_!aO`!aOa!aOb!aOc!aO~O!V!bO~P/xOr!eO#W!dO#i!dO~Oj!gO!O!iO!V|P~Oj!mOlZOmPOyZO!TZO!UZO!hZO#VWO#ZQO#fXO#gYO~OjwOlZOmPOyZO!TZO!UZO!hZO#VWO#ZQO#fXO#gYO~O!V!tO~Oj!xOy!xO#VWO~Oj!yO#VWO~O#Z!zO#]!zO#^!zO#_!zO#`!zO#a!zO~O!QnO#Z!|O#]lO#^mO~OnfO!^!}O~P2fOnfO!OsO!^rOr!ba!V!ba#W!ba#i!ba#e!ba!X!ba![!ba!]!ba!d!ba~P2fO#W!PO~P!wO#W!PO~P%qO#W!PO#e#dO~P)xO#e#dO~O#e#dOr#SX~O!V!bO#e#dOr#SX~O#e#dO~P-}OT}OU!OO#W!PO#e#dOr#SX~Or!eO~O#h#fO~P,bO!OsO#W#hO#h#jO~O#W#kO#h#fO~P2fOjROk_OlZOmPOnfOthOyZO!TZO!UZO!agO!hZO!miO!ojO#VWO#ZQO#fXO#gYO~O#W#pO~P7xOr!eO#Wqa#iqa#eqa!Xqa![qa!]qa!dqa~Oj!gO!O!iO!V|X~Oy#vO!T#vO!U#vO#ZQO~O!V#xO~OnfO!OsO!^rOT#TXU#TXW#TXX#TXY#TXZ#TX[#TX]#TX!V#TX~P2fOT}OU!OO!V#cX~OT}OU!OOW|OX|OY|OZ|O[|O]|O~O!V#cX~P;gO!V#yO~O!V#zO~P;gOT}OU!OO!V#zO~Or!na#W!na#i!na!X!na![!na!]!na#e!na!d!na~P(kOPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{O~Or!na#W!na#i!na!X!na![!na!]!na#e!na!d!na~P=_OT}OU!OOr!na#W!na#i!na!X!na![!na!]!na#e!na!d!na~O^xOR!giS!gid!gie!gif!gig!gih!gii!gir!gi#W!gi#i!gi#e!gi!X!gi![!gi!]!gi!d!gi~OP!giQ!gi~P?_OPyOQyO~P?_OPyOQyOd!gie!gif!gig!gih!gii!gir!gi#W!gi#i!gi#e!gi!X!gi![!gi!]!gi!d!gi~OR!giS!gi~P@zORzOSzO^xO~P@zORzOSzO~P@zOW|OX|OY|OZ|O[|O]|OTuirui#Wui#iui#eui!Vui!Xui![ui!]ui!dui~OU!OO~PBqOU!OO~PCTOUui~PBqO#W!PO#e#}O~P)xO#e#}O~O#e#}Or#SX~O!V!bO#e#}Or#SX~O#e#}O~P-}OT}OU!OO#W!PO#e#}Or#SX~OnfO!^rO~P,bO#W!PO#e#}O~O!OsO#W#hO#h$QO~O#W#kO#h$SO~P2fOr!eO#W!qi#i!qi!X!qi![!qi!]!qi#e!qi!d!qi~Or!eO#W!pi#i!pi!X!pi![!pi!]!pi#e!pi!d!pi~Or!eO!X!YX![!YX!]!YX!d!YX~O#W$VO!X#bP![#bP!]#bP!d#bP~P7xO!X$ZO![$[O!]$]O~O!O!iO!V|a~O#W$aO~P7xO!X$ZO![$[O!]$dO~O#W!PO#e$gO~O#W!PO#ewi~O!OsO#W#hO#h$jO~O#W#kO#h$kO~P2fOr!eO#W$lO~O#W$VO!X#bX![#bX!]#bX!d#bX~P7xOj$nO~O!V$oO~O!]$pO~O![$[O!]$pO~Or!eO!X$ZO![$[O!]$rO~O#W$VO!X#bP![#bP!]#bP~P7xO!]$yO!d$xO~O!]${O~O!]$|O~O![$[O!]$|O~OnfO!^rO#ewq~P,bO#W!PO#ewq~O!V%RO~O!]%TO~O!]%UO~O![$[O!]%UO~O!X$ZO![$[O!]%UO~O!]%YO!d$xO~O!V%]O!a%[O~O!]%YO~O!]%^O~OnfO!^rO#ewy~P,bO!]%aO~O![$[O!]%aO~O!]%dO~O!]%gO~O!V%hO~Oy!h~",
goto: "8W#ePPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#fP$PP$f%a&n&tP'}(Z)T)WP)^P*d*dPPP*hP*t+^PPP+t#fP,^,wP,{-R-hP._/b$P$PP$PP$P$P0g0m0y1m1s1}2T2[2b2l2r2|PPP3W3[4P5rPPP6{P7]PPPPP7a7g7mr`Oe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hQ!VWR#^!Qw`OWe!Q!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hr^Oe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hQ!YWS!ng%[Q!shQ!wjQ#V!OQ#X}R#a!QvSOeg!`!a!b!e!t#p#x#y#z$X$a$o%R%[%]%h!UZRSYhjsvxyz{|}!O!R!S![!_!m#b#g#l$R$h%P%_S!SW!QQ!xkR!ylQ!UWR#]!QrROe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%h!UwRSYhjsvxyz{|}!O!R!S![!_!m#b#g#l$R$h%P%_S!RW!QT!mg%[etRSv!R!S!m#b$h%P%_r`Oe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hdrRSv!R!S!m#b$h%P%_Q!VWQ!}sR#^!QR!lfX!jf!h!k#u#QZORSWYeghjsvxyz{|}!O!Q!R!S![!_!`!a!b!e!m!t#b#g#l#p#x#y#z$R$X$a$h$o%P%R%[%]%_%hR#v!iTnQpQ$_#qQ$f#{Q$t$`R%W$uQ#q!bQ#{!tQ$b#yQ$c#zQ%S$oQ%`%RQ%f%]R%i%hQ$^#qQ$e#{Q$q$_Q$s$`Q$}$fS%V$t$uR%b%WdtRSv!R!S!m#b$h%P%_Q!]YQ#e![X#h!]#e#i$PvTOWe!Q!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hT!pg%[T$v$b$wQ$z$bR%Z$wwTOWe!Q!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hrVOe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hQ!TWQ!vjQ#PyQ#SzQ#U{R#[!Q#RZORSWYeghjsvxyz{|}!O!Q!R!S![!_!`!a!b!e!m!t#b#g#l#p#x#y#z$R$X$a$h$o%P%R%[%]%_%h!YZRSYghjsvxyz{|}!O!R!S![!_!m#b#g#l$R$h%P%[%_w[OWe!Q!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hQeOR!fe^!cb!Z#m#n#o$W$`R#r!cQ!QWQ![Y`#Z!Q![#b#c#|$h%P%_S#b!R!SS#c!T!YS#|#[#aQ$h$OR%P$iQ!hfR#t!hQ!kfQ#u!hT#w!k#uQpQR!{pS$X#p$aR$m$XQ$i$OR%Q$iYvRS!R!S!mR#OvQ$w$bR%X$wQ#i!]Q$P#eT$T#i$PQ#l!_Q$R#gT$U#l$RTdOeSbOeS!ZW!QQ#m!`Q#n!a`#o!b!t#y#z$o%R%]%hQ#s!eU$W#p$X$aR$`#xvUOWe!Q!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hdrRSv!R!S!m#b$h%P%_Q!_YS!og%[Q!rhQ!ujQ!}sQ#PxQ#QyQ#RzQ#T{Q#V|Q#W}Q#Y!OQ#g![X#k!_#g#l$Rr]Oe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%h!YwRSYghjsvxyz{|}!O!R!S![!_!m#b#g#l$R$h%P%[%_Q!XWR#`!Q[uRSv!R!S!mQ$O#bV%O$h%P%_ToQpQ$Y#pR$u$aQ!qgR%e%[raOe!`!a!b!e!t#p#x#y#z$X$a$o%R%]%hQ!WWR#_!Q",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Band Bor Bxor Shl Shr Ushr Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg IfExpr keyword FunctionCall ElseIfExpr keyword ElseExpr FunctionCallOrIdentifier BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword CompoundAssign Assign",
maxTerm: 117,
states: "<tQYQbOOO!dOpO'#DWO!iOSO'#D_O%^QcO'#DtO(WQcO'#EdOOQ`'#Er'#ErO(qQRO'#DuO*vQcO'#EbO+aQbO'#DUOOQa'#Dw'#DwO-fQbO'#DxOOQa'#Ed'#EdO-mQcO'#EdO/kQcO'#EcO0pQcO'#EbO0}QRO'#EOOOQ`'#Eb'#EbO1fQbO'#EbO1mQQO'#EaOOQ`'#Ea'#EaOOQ`'#EQ'#EQQYQbOOO1xQbO'#DZO2TQbO'#DnO2xQbO'#DRO3mQQO'#DzO2xQbO'#D|O3rObO,59rO3}QbO'#DaO4VQWO'#DbOOOO'#Ej'#EjOOOO'#EV'#EVO4kOSO,59yOOQa,59y,59yOOQ`'#DY'#DYO4yQbO'#DmOOQ`'#Eh'#EhOOQ`'#EY'#EYO5TQbO,5:[OOQa'#Ec'#EcO2xQbO,5:aO2xQbO,5:aO2xQbO,5:aO2xQbO,5:aO2xQbO,59oO2xQbO,59oO2xQbO,59oO2xQbO,59oOOQ`'#ES'#ESO+aQbO,59pO5}QcO'#DtO6UQcO'#EdO6]QRO,59pO6gQQO,59pO6lQQO,59pO6tQQO,59pO7PQRO,59pO7iQRO,59pO7pQQO'#DPO7uQbO,5:dO7|QQO,5:cOOQa,5:d,5:dO8XQbO,5:dO8cQbO,5:kO8cQbO,5:jO9jQbO,5:eO9qQbO,59kOOQ`,5:{,5:{O8cQbO'#EROOQ`-E8O-E8OOOQ`'#ET'#ETO:]QbO'#D[O:hQbO'#D]OOQO'#EU'#EUO:`QQO'#D[O:vQQO,59uO:{QcO'#EcO;xQRO'#EqO<uQRO'#EqOOQO'#Eq'#EqO<|QQO,5:YO=RQRO,59mO=YQRO,59mO9jQbO,5:fO=hQcO,5:hO>vQcO,5:hO?dQcO,5:hOOQa1G/^1G/^OOOO,59{,59{OOOO,59|,59|OOOO-E8T-E8TOOQa1G/e1G/eOOQ`,5:X,5:XOOQ`-E8W-E8WOOQa1G/{1G/{OA`QcO1G/{OAjQcO1G/{OBxQcO1G/{OCSQcO1G/{OCaQcO1G/{OOQa1G/Z1G/ZODrQcO1G/ZODyQcO1G/ZOEQQcO1G/ZOFPQcO1G/ZOEXQcO1G/ZOOQ`-E8Q-E8QOFgQRO1G/[OFqQQO1G/[OFvQQO1G/[OGOQQO1G/[OGZQRO1G/[OGbQRO1G/[OGiQbO,59qOGsQQO1G/[OOQa1G/[1G/[OG{QQO1G/}OOQa1G0O1G0OOHWQbO1G0OOOQO'#E['#E[OG{QQO1G/}OOQa1G/}1G/}OOQ`'#E]'#E]OHWQbO1G0OOHbQbO1G0VOH|QbO1G0UOIhQbO'#DhOIyQbO'#DhOJ^QbO1G0POOQ`-E8P-E8POOQ`,5:m,5:mOOQ`-E8R-E8ROJiQQO,59vOOQO,59w,59wOOQO-E8S-E8SOJqQbO1G/aO9jQbO1G/tO9jQbO1G/XOJxQbO1G0QOKTQQO7+$vOOQa7+$v7+$vOK]QQO1G/]OKeQQO7+%iOOQa7+%i7+%iOKpQbO7+%jOOQa7+%j7+%jOOQO-E8Y-E8YOOQ`-E8Z-E8ZOOQ`'#EW'#EWOKzQQO'#EWOLSQbO'#EpOOQ`,5:S,5:SOLgQbO'#DfOLlQQO'#DiOOQ`7+%k7+%kOLqQbO7+%kOLvQbO7+%kOMOQbO7+${OM^QbO7+${OMnQbO7+%`OMvQbO7+$sOOQ`7+%l7+%lOM{QbO7+%lONQQbO7+%lOOQa<<Hb<<HbONYQbO7+$wONgQQO7+$wOOQa<<IT<<ITOOQa<<IU<<IUOOQ`,5:r,5:rOOQ`-E8U-E8UONoQQO,5:QO9jQbO,5:TOOQ`<<IV<<IVONtQbO<<IVOOQ`<<Hg<<HgONyQbO<<HgO! OQbO<<HgO! WQbO<<HgOOQ`'#EZ'#EZO! cQbO<<HzO! kQbO'#DsOOQ`<<Hz<<HzO! sQbO<<HzOOQ`<<H_<<H_OOQ`<<IW<<IWO! xQbO<<IWOOQO,5:s,5:sO! }QbO<<HcOOQO-E8V-E8VO9jQbO1G/lOOQ`1G/o1G/oOOQ`AN>qAN>qOOQ`AN>RAN>RO!![QbOAN>RO!!aQbOAN>ROOQ`-E8X-E8XOOQ`AN>fAN>fO!!iQbOAN>fO2TQbO,5:]O9jQbO,5:_OOQ`AN>rAN>rPGiQbO'#ESOOQ`7+%W7+%WOOQ`G23mG23mO!!nQbOG23mP! nQbO'#DqOOQ`G24QG24QO!!sQQO1G/wOOQ`1G/y1G/yOOQ`LD)XLD)XO9jQbO7+%cOOQ`<<H}<<H}",
stateData: "!!{~O#SOSqOS~OlROm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#YcO#]QO#hXO#iYO~O#ZkO~O!SnO#]qO#_lO#`mO~OlwOnZOoPOpfO{ZO!QsO!VZO!WZO!`rO!jZO#XWO#]QO#hXO#iYOP#VXQ#VXR#VXS#VXT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VX^#VXd#VXe#VXf#VXg#VXh#VXi#VXj#VXt!hX!X!hX#g!hX~O#Y!hX#k!hX!Z!hX!^!hX!_!hX!f!hX~P!wOlwOnZOoPOpfO{ZO!QsO!VZO!WZO!`rO!jZO#XWO#]QO#hXO#iYOP#WXQ#WXR#WXS#WXT#WXU#WXW#WXX#WXY#WXZ#WX[#WX]#WX^#WXd#WXe#WXf#WXg#WXh#WXi#WXj#WXt#WX#g#WX~O#Y#WX#k#WX!X#WX!Z#WX!^#WX!_#WX!f#WX~P%tOPyOQyORzOSzOT}OU!OOW|OX|OY|OZ|O[|O]|O^xOd{Oe{Of{Og{Oh{Oi{Oj!PO~OPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{Ot#UX~O#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~P*ROl!SOm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#Y!QO#]QO#hXO#iYO~OlwOnZOoPO{ZO!QsO!VZO!WZO!jZO#XWO#Y!QO#]QO#hXO#iYO~O#j!_O~P,kOV!aO#Y#WX#k#WX!Z#WX!^#WX!_#WX!f#WX~P&pOP#VXQ#VXR#VXS#VXT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VX^#VXd#VXe#VXf#VXg#VXh#VXi#VXj#VXt#UX~O#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~P.WOt#UX#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~OT}OU!OOj!PO~P0UOV!aO_!bO`!bOa!bOb!bOc!bOk!bO~O!X!cO~P0UOt!fO#Y!eO#k!eO~Ol!hO!Q!jO!X!OP~Ol!nOnZOoPO{ZO!VZO!WZO!jZO#XWO#]QO#hXO#iYO~OlwOnZOoPO{ZO!VZO!WZO!jZO#XWO#]QO#hXO#iYO~O!X!uO~Ol!yO{!yO#XWO~Ol!zO#XWO~O#]!{O#_!{O#`!{O#a!{O#b!{O#c!{O~O!SnO#]!}O#_lO#`mO~OpfO!`#OO~P2xOpfO!QsO!`rOt!da!X!da#Y!da#k!da#g!da!Z!da!^!da!_!da!f!da~P2xO#Y!QO~P!wO#Y!QO~P%tO#Y!QO#g#gO~P*RO#g#gO~O#g#gOt#UX~O!X!cO#g#gOt#UX~O#g#gO~P.WOT}OU!OOj!PO#Y!QOt#UX~O#g#gO~P7WOt!fO~O#j#iO~P,kO!QsO#Y#kO#j#mO~O#Y#nO#j#iO~P2xOlROm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#]QO#hXO#iYO~O#Y#sO~P8cOt!fO#Ysa#ksa#gsa!Zsa!^sa!_sa!fsa~Ol!hO!Q!jO!X!OX~O{#yO!V#yO!W#yO#]QO~O!X#{O~OpfO!QsO!`rOT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VXj#VX!X#VX~P2xOT}OU!OOj!PO!X#eX~OT}OU!OOW|OX|OY|OZ|O[|O]|Oj!PO~O!X#eX~P<WO!X#|O~O!X#}O~P<WOT}OU!OOj!PO!X#}O~Ot!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~P(qOPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{O~Ot!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~P>UOT}OU!OOj!POt!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~O^xOR!iiS!iid!iie!iif!iig!iih!iii!iit!ii#Y!ii#k!ii#g!ii!Z!ii!^!ii!_!ii!f!ii~OP!iiQ!ii~P@XOPyOQyO~P@XOPyOQyOd!iie!iif!iig!iih!iii!iit!ii#Y!ii#k!ii#g!ii!Z!ii!^!ii!_!ii!f!ii~OR!iiS!ii~PAtORzOSzO^xO~PAtORzOSzO~PAtOW|OX|OY|OZ|O[|O]|OTwijwitwi#Ywi#kwi#gwi!Xwi!Zwi!^wi!_wi!fwi~OU!OO~PCkOU!OO~PC}OUwi~PCkOT}OU!OOjwitwi#Ywi#kwi#gwi!Xwi!Zwi!^wi!_wi!fwi~OW|OX|OY|OZ|O[|O]|O~PEXO#Y!QO#g$QO~P*RO#g$QO~O#g$QOt#UX~O!X!cO#g$QOt#UX~O#g$QO~P.WO#g$QO~P7WOpfO!`rO~P,kO#Y!QO#g$QO~O!QsO#Y#kO#j$TO~O#Y#nO#j$VO~P2xOt!fO#Y!si#k!si!Z!si!^!si!_!si#g!si!f!si~Ot!fO#Y!ri#k!ri!Z!ri!^!ri!_!ri#g!ri!f!ri~Ot!fO!Z![X!^![X!_![X!f![X~O#Y$YO!Z#dP!^#dP!_#dP!f#dP~P8cO!Z$^O!^$_O!_$`O~O!Q!jO!X!Oa~O#Y$dO~P8cO!Z$^O!^$_O!_$gO~O#Y!QO#g$jO~O#Y!QO#gyi~O!QsO#Y#kO#j$mO~O#Y#nO#j$nO~P2xOt!fO#Y$oO~O#Y$YO!Z#dX!^#dX!_#dX!f#dX~P8cOl$qO~O!X$rO~O!_$sO~O!^$_O!_$sO~Ot!fO!Z$^O!^$_O!_$uO~O#Y$YO!Z#dP!^#dP!_#dP~P8cO!_$|O!f${O~O!_%OO~O!_%PO~O!^$_O!_%PO~OpfO!`rO#gyq~P,kO#Y!QO#gyq~O!X%UO~O!_%WO~O!_%XO~O!^$_O!_%XO~O!Z$^O!^$_O!_%XO~O!_%]O!f${O~O!X%`O!c%_O~O!_%]O~O!_%aO~OpfO!`rO#gyy~P,kO!_%dO~O!^$_O!_%dO~O!_%gO~O!_%jO~O!X%kO~O{!j~",
goto: "8f#gPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#hP$RP$h%f&t&zP(U(b)[)_P)eP*l*lPPP*pP*|+fPPP+|#hP,f-PP-T-Z-pP.g/k$R$RP$RP$R$R0q0w1T1w1}2X2_2f2l2v2|3WPPP3b3f4Z6PPPP7ZP7kPPPPP7o7u7{r`Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!WWR#a!Rw`OWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kr^Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!ZWS!og%_Q!thQ!xjQ#W!OQ#Y}Q#]!PR#d!RvSOeg!a!b!c!f!u#s#{#|#}$[$d$r%U%_%`%k!WZRSYhjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%bS!TW!RQ!ykR!zlQ!VWR#`!RrROe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%k!WwRSYhjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%bS!SW!RT!ng%_etRSv!S!T!n#e$k%S%br`Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kdrRSv!S!T!n#e$k%S%bQ!WWQ#OsR#a!RR!mfX!kf!i!l#x#SZORSWYeghjsvxyz{|}!O!P!R!S!T!]!`!a!b!c!f!n!u#e#j#o#s#{#|#}$U$[$d$k$r%S%U%_%`%b%kR#y!jTnQpQ$b#tQ$i$OQ$w$cR%Z$xQ#t!cQ$O!uQ$e#|Q$f#}Q%V$rQ%c%UQ%i%`R%l%kQ$a#tQ$h$OQ$t$bQ$v$cQ%Q$iS%Y$w$xR%e%ZdtRSv!S!T!n#e$k%S%bQ!^YQ#h!]X#k!^#h#l$SvTOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kT!qg%_T$y$e$zQ$}$eR%^$zwTOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%krVOe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!UWQ!wjQ#QyQ#TzQ#V{R#_!R#TZORSWYeghjsvxyz{|}!O!P!R!S!T!]!`!a!b!c!f!n!u#e#j#o#s#{#|#}$U$[$d$k$r%S%U%_%`%b%k![ZRSYghjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%_%bw[OWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQeOR!ge^!db![#p#q#r$Z$cR#u!dQ!RWQ!]Y`#^!R!]#e#f$P$k%S%bS#e!S!TS#f!U!ZS$P#_#dQ$k$RR%S$lQ!ifR#w!iQ!lfQ#x!iT#z!l#xQpQR!|pS$[#s$dR$p$[Q$l$RR%T$lYvRS!S!T!nR#PvQ$z$eR%[$zQ#l!^Q$S#hT$W#l$SQ#o!`Q$U#jT$X#o$UTdOeSbOeS![W!RQ#p!aQ#q!b`#r!c!u#|#}$r%U%`%kQ#v!fU$Z#s$[$dR$c#{vUOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kdrRSv!S!T!n#e$k%S%bQ!`YS!pg%_Q!shQ!vjQ#OsQ#QxQ#RyQ#SzQ#U{Q#W|Q#X}Q#Z!OQ#[!PQ#j!]X#n!`#j#o$Ur]Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%k![wRSYghjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%_%bQ!YWR#c!R[uRSv!S!T!nQ$R#eV%R$k%S%bToQpQ$]#sR$x$dQ!rgR%h%_raOe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!XWR#b!R",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Band Bor Bxor Shl Shr Ushr NullishCoalesce NullishEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg IfExpr keyword FunctionCall ElseIfExpr keyword ElseExpr FunctionCallOrIdentifier BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword CompoundAssign Assign",
maxTerm: 119,
context: trackScope,
nodeProps: [
["closedBy", 53,"end"]
["closedBy", 55,"end"]
],
propSources: [highlighting],
skippedNodes: [0,31],
skippedNodes: [0,33],
repeatNodeCount: 12,
tokenData: "IS~R}OX$OXY$mYZ%WZp$Opq$mqs$Ost%qtu'Yuw$Owx'_xy'dyz'}z{$O{|(h|}$O}!O(h!O!P$O!P!Q0o!Q!R)Y!R![+w![!]9[!]!^%W!^!}$O!}#O9u#O#P;k#P#Q;p#Q#R$O#R#S<Z#S#T$O#T#Y<t#Y#Z>`#Z#b<t#b#cC|#c#f<t#f#gEP#g#h<t#h#iFS#i#o<t#o#p$O#p#qHd#q;'S$O;'S;=`$g<%l~$O~O$O~~H}S$TU!QSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OS$jP;=`<%l$O^$tU!QS#QYOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU%_U!QS#WQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O^%xZoY!QSOY%qYZ$OZt%qtu&kuw%qwx&kx#O%q#O#P&k#P;'S%q;'S;=`'S<%lO%qY&pSoYOY&kZ;'S&k;'S;=`&|<%lO&kY'PP;=`<%l&k^'VP;=`<%l%q~'_O#]~~'dO#Z~U'kU!QS#VQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(UU!QS#eQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(mX!QSOt$Ouw$Ox!Q$O!Q!R)Y!R![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU)a`!QSyQOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S#U$O#U#V-c#V#l$O#l#m.w#m;'S$O;'S;=`$g<%lO$OU*hW!QSOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P;'S$O;'S;=`$g<%lO$OU+XY!QSyQOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P#R$O#R#S*c#S;'S$O;'S;=`$g<%lO$OU,O[!QSyQOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S;'S$O;'S;=`$g<%lO$OU,yW!QSOt$Ouw$Ox!Q$O!Q![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU-hX!QSOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.[X!QSyQOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.|[!QSOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU/y[!QSyQOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU0tW!QSOt$Ouw$Ox!P$O!P!Q1^!Q#O$O#P;'S$O;'S;=`$g<%lO$OU1c^!QSOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q$O!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_U2f^!QS!hQOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q6V!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_Q3gX!hQOY3bZ!P3b!P!Q4S!Q!}3b!}#O4q#O#P5p#P;'S3b;'S;=`6P<%lO3bQ4VP!P!Q4YQ4_U!hQ#Z#[4Y#]#^4Y#a#b4Y#g#h4Y#i#j4Y#m#n4YQ4tVOY4qZ#O4q#O#P5Z#P#Q3b#Q;'S4q;'S;=`5j<%lO4qQ5^SOY4qZ;'S4q;'S;=`5j<%lO4qQ5mP;=`<%l4qQ5sSOY3bZ;'S3b;'S;=`6P<%lO3bQ6SP;=`<%l3bU6[W!QSOt$Ouw$Ox!P$O!P!Q6t!Q#O$O#P;'S$O;'S;=`$g<%lO$OU6{b!QS!hQOt$Ouw$Ox#O$O#P#Z$O#Z#[6t#[#]$O#]#^6t#^#a$O#a#b6t#b#g$O#g#h6t#h#i$O#i#j6t#j#m$O#m#n6t#n;'S$O;'S;=`$g<%lO$OU8Y[!QSOY8TYZ$OZt8Ttu4quw8Twx4qx#O8T#O#P5Z#P#Q2_#Q;'S8T;'S;=`9O<%lO8TU9RP;=`<%l8TU9XP;=`<%l2_U9cU!QS!VQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU9|W#gQ!QSOt$Ouw$Ox!_$O!_!`:f!`#O$O#P;'S$O;'S;=`$g<%lO$OU:kV!QSOt$Ouw$Ox#O$O#P#Q;Q#Q;'S$O;'S;=`$g<%lO$OU;XU#fQ!QSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~;pO#^~U;wU#hQ!QSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<bU!QS!^QOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<y^!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$OU=|U!OQ!QSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU>e_!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#U?d#U#o<t#o;'S$O;'S;=`$g<%lO$OU?i`!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#`<t#`#a@k#a#o<t#o;'S$O;'S;=`$g<%lO$OU@p`!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#g<t#g#hAr#h#o<t#o;'S$O;'S;=`$g<%lO$OUAw`!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#X<t#X#YBy#Y#o<t#o;'S$O;'S;=`$g<%lO$OUCQ^!TQ!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^DT^#_W!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^EW^#aW!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^FZ`#`W!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#f<t#f#gG]#g#o<t#o;'S$O;'S;=`$g<%lO$OUGb`!QSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#i<t#i#jAr#j#o<t#o;'S$O;'S;=`$g<%lO$OUHkUrQ!QSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~ISO#i~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#X~~", 11)],
topRules: {"Program":[0,32]},
specialized: [{term: 26, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 26, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 2109
tokenData: "IS~R}OX$OXY$mYZ%WZp$Opq$mqs$Ost%qtu'Yuw$Owx'_xy'dyz'}z{$O{|(h|}$O}!O(h!O!P$O!P!Q0o!Q!R)Y!R![+w![!]9[!]!^%W!^!}$O!}#O9u#O#P;k#P#Q;p#Q#R$O#R#S<Z#S#T$O#T#Y<t#Y#Z>`#Z#b<t#b#cC|#c#f<t#f#gEP#g#h<t#h#iFS#i#o<t#o#p$O#p#qHd#q;'S$O;'S;=`$g<%l~$O~O$O~~H}S$TU!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OS$jP;=`<%l$O^$tU!SS#SYOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU%_U!SS#YQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O^%xZqY!SSOY%qYZ$OZt%qtu&kuw%qwx&kx#O%q#O#P&k#P;'S%q;'S;=`'S<%lO%qY&pSqYOY&kZ;'S&k;'S;=`&|<%lO&kY'PP;=`<%l&k^'VP;=`<%l%q~'_O#_~~'dO#]~U'kU!SS#XQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(UU!SS#gQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(mX!SSOt$Ouw$Ox!Q$O!Q!R)Y!R![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU)a`!SS{QOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S#U$O#U#V-c#V#l$O#l#m.w#m;'S$O;'S;=`$g<%lO$OU*hW!SSOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P;'S$O;'S;=`$g<%lO$OU+XY!SS{QOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P#R$O#R#S*c#S;'S$O;'S;=`$g<%lO$OU,O[!SS{QOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S;'S$O;'S;=`$g<%lO$OU,yW!SSOt$Ouw$Ox!Q$O!Q![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU-hX!SSOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.[X!SS{QOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.|[!SSOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU/y[!SS{QOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU0tW!SSOt$Ouw$Ox!P$O!P!Q1^!Q#O$O#P;'S$O;'S;=`$g<%lO$OU1c^!SSOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q$O!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_U2f^!SS!jQOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q6V!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_Q3gX!jQOY3bZ!P3b!P!Q4S!Q!}3b!}#O4q#O#P5p#P;'S3b;'S;=`6P<%lO3bQ4VP!P!Q4YQ4_U!jQ#Z#[4Y#]#^4Y#a#b4Y#g#h4Y#i#j4Y#m#n4YQ4tVOY4qZ#O4q#O#P5Z#P#Q3b#Q;'S4q;'S;=`5j<%lO4qQ5^SOY4qZ;'S4q;'S;=`5j<%lO4qQ5mP;=`<%l4qQ5sSOY3bZ;'S3b;'S;=`6P<%lO3bQ6SP;=`<%l3bU6[W!SSOt$Ouw$Ox!P$O!P!Q6t!Q#O$O#P;'S$O;'S;=`$g<%lO$OU6{b!SS!jQOt$Ouw$Ox#O$O#P#Z$O#Z#[6t#[#]$O#]#^6t#^#a$O#a#b6t#b#g$O#g#h6t#h#i$O#i#j6t#j#m$O#m#n6t#n;'S$O;'S;=`$g<%lO$OU8Y[!SSOY8TYZ$OZt8Ttu4quw8Twx4qx#O8T#O#P5Z#P#Q2_#Q;'S8T;'S;=`9O<%lO8TU9RP;=`<%l8TU9XP;=`<%l2_U9cU!SS!XQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU9|W#iQ!SSOt$Ouw$Ox!_$O!_!`:f!`#O$O#P;'S$O;'S;=`$g<%lO$OU:kV!SSOt$Ouw$Ox#O$O#P#Q;Q#Q;'S$O;'S;=`$g<%lO$OU;XU#hQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~;pO#`~U;wU#jQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<bU!SS!`QOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<y^!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$OU=|U!QQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU>e_!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#U?d#U#o<t#o;'S$O;'S;=`$g<%lO$OU?i`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#`<t#`#a@k#a#o<t#o;'S$O;'S;=`$g<%lO$OU@p`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#g<t#g#hAr#h#o<t#o;'S$O;'S;=`$g<%lO$OUAw`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#X<t#X#YBy#Y#o<t#o;'S$O;'S;=`$g<%lO$OUCQ^!VQ!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^DT^#aW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^EW^#cW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^FZ`#bW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#f<t#f#gG]#g#o<t#o;'S$O;'S;=`$g<%lO$OUGb`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#i<t#i#jAr#j#o<t#o;'S$O;'S;=`$g<%lO$OUHkUtQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~ISO#k~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#Z~~", 11)],
topRules: {"Program":[0,34]},
specialized: [{term: 28, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 28, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 2202
})

View File

@ -707,6 +707,87 @@ describe('CompoundAssign', () => {
PositionalArg
Number 3`)
})
test('parses ??= operator', () => {
expect('x ??= 5').toMatchTree(`
CompoundAssign
AssignableIdentifier x
NullishEq ??=
Number 5`)
})
test('parses ??= with expression', () => {
expect('config ??= get-default').toMatchTree(`
CompoundAssign
AssignableIdentifier config
NullishEq ??=
FunctionCallOrIdentifier
Identifier get-default`)
})
})
describe('Nullish coalescing operator', () => {
test('? can still end an identifier', () => {
expect('what?').toMatchTree(`
FunctionCallOrIdentifier
Identifier what?`)
})
test('?? can still end an identifier', () => {
expect('what??').toMatchTree(`
FunctionCallOrIdentifier
Identifier what??`)
})
test('?? can still be in a word', () => {
expect('what??the').toMatchTree(`
FunctionCallOrIdentifier
Identifier what??the`)
})
test('?? can still start a word', () => {
expect('??what??the').toMatchTree(`
Word ??what??the`)
})
test('parses ?? operator', () => {
expect('x ?? 5').toMatchTree(`
ConditionalOp
Identifier x
NullishCoalesce ??
Number 5`)
})
test('parses chained ?? operators', () => {
expect('a ?? b ?? c').toMatchTree(`
ConditionalOp
ConditionalOp
Identifier a
NullishCoalesce ??
Identifier b
NullishCoalesce ??
Identifier c`)
})
test('parses ?? with expressions', () => {
expect('get-value ?? default-value').toMatchTree(`
ConditionalOp
Identifier get-value
NullishCoalesce ??
Identifier default-value`)
})
test('parses ?? with parenthesized function call', () => {
expect('get-value ?? (default 10)').toMatchTree(`
ConditionalOp
Identifier get-value
NullishCoalesce ??
ParenExpr
FunctionCall
Identifier default
PositionalArg
Number 10`)
})
})
describe('DotGet whitespace sensitivity', () => {

View File

@ -193,6 +193,15 @@ const chooseIdentifierToken = (input: InputStream, stack: Stack): number => {
const nextCh = getFullCodePoint(input, peekPos)
const nextCh2 = getFullCodePoint(input, peekPos + 1)
const nextCh3 = getFullCodePoint(input, peekPos + 2)
// Check for ??= (three-character compound operator)
if (nextCh === 63 /* ? */ && nextCh2 === 63 /* ? */ && nextCh3 === 61 /* = */) {
const charAfterOp = getFullCodePoint(input, peekPos + 3)
if (isWhiteSpace(charAfterOp) || charAfterOp === -1 /* EOF */) {
return AssignableIdentifier
}
}
// Check for compound assignment operators: +=, -=, *=, /=, %=
if (