Compare commits

...

1 Commits

Author SHA1 Message Date
a6261fce7f implicit function-level try blocks 2025-10-29 14:50:59 -07:00
7 changed files with 381 additions and 81 deletions

View File

@ -255,7 +255,10 @@ export class Compiler {
} }
case terms.FunctionDef: { case terms.FunctionDef: {
const { paramNames, bodyNodes } = getFunctionDefParts(node, input) const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } = getFunctionDefParts(
node,
input
)
const instructions: ProgramItem[] = [] const instructions: ProgramItem[] = []
const functionLabel: Label = `.func_${this.fnLabelCount++}` const functionLabel: Label = `.func_${this.fnLabelCount++}`
const afterLabel: Label = `.after_${functionLabel}` const afterLabel: Label = `.after_${functionLabel}`
@ -263,9 +266,28 @@ export class Compiler {
instructions.push(['JUMP', afterLabel]) instructions.push(['JUMP', afterLabel])
instructions.push([`${functionLabel}:`]) instructions.push([`${functionLabel}:`])
bodyNodes.forEach((bodyNode) => {
instructions.push(...this.#compileNode(bodyNode, input)) const compileFunctionBody = () => {
}) const bodyInstructions: ProgramItem[] = []
bodyNodes.forEach((bodyNode, index) => {
bodyInstructions.push(...this.#compileNode(bodyNode, input))
if (index < bodyNodes.length - 1) {
bodyInstructions.push(['POP'])
}
})
return bodyInstructions
}
// If function has catch or finally, wrap body in try/catch/finally
if (catchVariable || finallyBody) {
instructions.push(
...this.#compileTryCatchFinally(compileFunctionBody, catchVariable, catchBody, finallyBody, input)
)
} else {
// Normal function without catch/finally
instructions.push(...compileFunctionBody())
}
instructions.push(['RETURN']) instructions.push(['RETURN'])
instructions.push([`${afterLabel}:`]) instructions.push([`${afterLabel}:`])
@ -338,60 +360,21 @@ export class Compiler {
case terms.TryExpr: { case terms.TryExpr: {
const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input) const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input)
const instructions: ProgramItem[] = [] return this.#compileTryCatchFinally(
this.tryLabelCount++ () => this.#compileNode(tryBlock, input),
const catchLabel: Label = `.catch_${this.tryLabelCount}` catchVariable,
const finallyLabel: Label = finallyBody ? `.finally_${this.tryLabelCount}` : null as any catchBody,
const endLabel: Label = `.end_try_${this.tryLabelCount}` finallyBody,
input
instructions.push(['PUSH_TRY', catchLabel]) )
const tryInstructions = this.#compileNode(tryBlock, input)
instructions.push(...tryInstructions)
instructions.push(['POP_TRY'])
if (finallyBody) {
instructions.push(['JUMP', finallyLabel])
} else {
instructions.push(['JUMP', endLabel])
}
instructions.push([`${catchLabel}:`])
if (catchBody && catchVariable) {
// catch block
instructions.push(['STORE', catchVariable])
const catchInstructions = this.#compileNode(catchBody, input)
instructions.push(...catchInstructions)
instructions.push(['JUMP', finallyBody ? finallyLabel : endLabel])
} else {
// no catch block
if (finallyBody) {
instructions.push(['JUMP', finallyLabel])
} else {
instructions.push(['THROW'])
}
}
// finally block
if (finallyBody) {
instructions.push([`${finallyLabel}:`])
const finallyInstructions = this.#compileNode(finallyBody, input)
instructions.push(...finallyInstructions)
// finally doesn't return a value
instructions.push(['POP'])
}
instructions.push([`${endLabel}:`])
return instructions
} }
case terms.ThrowStatement: { case terms.Throw: {
const children = getAllChildren(node) const children = getAllChildren(node)
const [_throwKeyword, expression] = children const [_throwKeyword, expression] = children
if (!expression) { if (!expression) {
throw new CompilerError( throw new CompilerError(
`ThrowStatement expected expression, got ${children.length} children`, `Throw expected expression, got ${children.length} children`,
node.from, node.from,
node.to node.to
) )
@ -606,4 +589,52 @@ export class Compiler {
) )
} }
} }
#compileTryCatchFinally(
compileTryBody: () => ProgramItem[],
catchVariable: string | undefined,
catchBody: SyntaxNode | undefined,
finallyBody: SyntaxNode | undefined,
input: string
): ProgramItem[] {
const instructions: ProgramItem[] = []
this.tryLabelCount++
const catchLabel: Label = `.catch_${this.tryLabelCount}`
const finallyLabel: Label = finallyBody ? `.finally_${this.tryLabelCount}` : (null as any)
const endLabel: Label = `.end_try_${this.tryLabelCount}`
instructions.push(['PUSH_TRY', catchLabel])
instructions.push(...compileTryBody())
instructions.push(['POP_TRY'])
instructions.push(['JUMP', finallyBody ? finallyLabel : endLabel])
// catch block
instructions.push([`${catchLabel}:`])
if (catchBody && catchVariable) {
instructions.push(['STORE', catchVariable])
const catchInstructions = this.#compileNode(catchBody, input)
instructions.push(...catchInstructions)
instructions.push(['JUMP', finallyBody ? finallyLabel : endLabel])
} else {
// no catch block
if (finallyBody) {
instructions.push(['JUMP', finallyLabel])
} else {
instructions.push(['THROW'])
}
}
// finally block
if (finallyBody) {
instructions.push([`${finallyLabel}:`])
const finallyInstructions = this.#compileNode(finallyBody, input)
instructions.push(...finallyInstructions)
// finally doesn't return a value
instructions.push(['POP'])
}
instructions.push([`${endLabel}:`])
return instructions
}
} }

View File

@ -181,3 +181,131 @@ describe('exception handling', () => {
`).toEvaluateTo(30) `).toEvaluateTo(30)
}) })
}) })
describe('function-level exception handling', () => {
test('function with catch - no error', () => {
expect(`
read-file = do path:
path
catch e:
'default'
end
read-file test.txt
`).toEvaluateTo('test.txt')
})
test('function with catch - error thrown', () => {
expect(`
read-file = do path:
throw 'file not found'
catch e:
'default'
end
read-file test.txt
`).toEvaluateTo('default')
})
test('function with catch - error variable binding', () => {
expect(`
safe-call = do:
throw 'operation failed'
catch err:
err
end
safe-call
`).toEvaluateTo('operation failed')
})
test('function with finally - always runs', () => {
expect(`
counter = 0
increment-task = do:
result = 42
result
finally:
counter = counter + 1
end
x = increment-task
y = increment-task
counter
`).toEvaluateTo(2)
})
test('function with finally - return value from body', () => {
expect(`
get-value = do:
100
finally:
999
end
get-value
`).toEvaluateTo(100)
})
test('function with catch and finally', () => {
expect(`
cleanup-count = 0
safe-op = do should-fail:
if should-fail:
throw 'failed'
end
'success'
catch e:
'caught'
finally:
cleanup-count = cleanup-count + 1
end
result1 = safe-op false
result2 = safe-op true
cleanup-count
`).toEvaluateTo(2)
})
test('function with catch and finally - catch return value', () => {
expect(`
safe-fail = do:
throw 'always fails'
catch e:
'error handled'
finally:
noop = 1
end
safe-fail
`).toEvaluateTo('error handled')
})
test('function without catch/finally still works', () => {
expect(`
regular = do x:
x + 10
end
regular 5
`).toEvaluateTo(15)
})
test('nested functions with catch', () => {
expect(`
inner = do:
throw 'inner error'
catch e:
'inner caught'
end
outer = do:
inner
catch e:
'outer caught'
end
outer
`).toEvaluateTo('inner caught')
})
})

View File

@ -59,11 +59,11 @@ export const getAssignmentParts = (node: SyntaxNode) => {
export const getFunctionDefParts = (node: SyntaxNode, input: string) => { export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
const children = getAllChildren(node) const children = getAllChildren(node)
const [fnKeyword, paramsNode, colon, ...bodyNodes] = children const [fnKeyword, paramsNode, colon, ...rest] = children
if (!fnKeyword || !paramsNode || !colon || !bodyNodes) { if (!fnKeyword || !paramsNode || !colon || !rest) {
throw new CompilerError( throw new CompilerError(
`FunctionDef expected 5 children, got ${children.length}`, `FunctionDef expected at least 4 children, got ${children.length}`,
node.from, node.from,
node.to node.to
) )
@ -80,8 +80,48 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
return input.slice(param.from, param.to) return input.slice(param.from, param.to)
}) })
const bodyWithoutEnd = bodyNodes.slice(0, -1) // Separate body nodes from catch/finally/end
return { paramNames, bodyNodes: bodyWithoutEnd } const bodyNodes: SyntaxNode[] = []
let catchExpr: SyntaxNode | undefined
let catchVariable: string | undefined
let catchBody: SyntaxNode | undefined
let finallyExpr: SyntaxNode | undefined
let finallyBody: SyntaxNode | undefined
for (const child of rest) {
if (child.type.id === terms.CatchExpr) {
catchExpr = child
const catchChildren = getAllChildren(child)
const [_catchKeyword, identifierNode, _colon, body] = catchChildren
if (!identifierNode || !body) {
throw new CompilerError(
`CatchExpr expected identifier and body, got ${catchChildren.length} children`,
child.from,
child.to
)
}
catchVariable = input.slice(identifierNode.from, identifierNode.to)
catchBody = body
} else if (child.type.id === terms.FinallyExpr) {
finallyExpr = child
const finallyChildren = getAllChildren(child)
const [_finallyKeyword, _colon, body] = finallyChildren
if (!body) {
throw new CompilerError(
`FinallyExpr expected body, got ${finallyChildren.length} children`,
child.from,
child.to
)
}
finallyBody = body
} else if (child.type.name === 'keyword' && input.slice(child.from, child.to) === 'end') {
// Skip the end keyword
} else {
bodyNodes.push(child)
}
}
return { paramNames, bodyNodes, catchVariable, catchBody, finallyBody }
} }
export const getFunctionCallParts = (node: SyntaxNode, input: string) => { export const getFunctionCallParts = (node: SyntaxNode, input: string) => {

View File

@ -52,7 +52,7 @@ consumeToTerminator {
PipeExpr | PipeExpr |
ambiguousFunctionCall | ambiguousFunctionCall |
TryExpr | TryExpr |
ThrowStatement | Throw |
IfExpr | IfExpr |
FunctionDef | FunctionDef |
Assign | Assign |
@ -99,11 +99,11 @@ FunctionDef {
} }
singleLineFunctionDef { singleLineFunctionDef {
Do Params colon consumeToTerminator @specialize[@name=keyword]<Identifier, "end"> Do Params colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
} }
multilineFunctionDef { multilineFunctionDef {
Do Params colon newlineOrSemicolon block @specialize[@name=keyword]<Identifier, "end"> Do Params colon newlineOrSemicolon block CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
} }
IfExpr { IfExpr {
@ -158,7 +158,7 @@ TryBlock {
block block
} }
ThrowStatement { Throw {
@specialize[@name=keyword]<Identifier, "throw"> expression @specialize[@name=keyword]<Identifier, "throw"> expression
} }

View File

@ -39,17 +39,17 @@ export const
FunctionDef = 37, FunctionDef = 37,
Params = 38, Params = 38,
colon = 39, colon = 39,
CatchExpr = 40,
keyword = 63, keyword = 63,
Underscore = 41, TryBlock = 42,
Array = 42, FinallyExpr = 43,
Null = 43, Underscore = 46,
ConditionalOp = 44, Array = 47,
PositionalArg = 45, Null = 48,
TryExpr = 47, ConditionalOp = 49,
CatchExpr = 49, PositionalArg = 50,
TryBlock = 51, TryExpr = 52,
FinallyExpr = 52, Throw = 54,
ThrowStatement = 54,
IfExpr = 56, IfExpr = 56,
SingleLineThenBlock = 58, SingleLineThenBlock = 58,
ThenBlock = 59, ThenBlock = 59,

View File

@ -4,13 +4,13 @@ import {operatorTokenizer} from "./operatorTokenizer"
import {tokenizer, specializeKeyword} from "./tokenizer" import {tokenizer, specializeKeyword} from "./tokenizer"
import {trackScope} from "./scopeTracker" import {trackScope} from "./scopeTracker"
import {highlighting} from "./highlight" import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,end:80, null:86, try:96, catch:100, finally:106, throw:110, if:114, elseif:122, else:126} const spec_Identifier = {__proto__:null,catch:82, finally:88, end:90, null:96, try:106, throw:110, if:114, elseif:122, else:126}
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: "7^QYQbOOO#tQcO'#CvO$qOSO'#CxO%PQbO'#E`OOQ`'#DR'#DROOQa'#DO'#DOO&SQbO'#DWO'XQcO'#ETOOQa'#ET'#ETO)cQcO'#ESO*bQRO'#CwO+WQcO'#EOO+hQcO'#EOO+rQbO'#CuO,jOpO'#CsOOQ`'#EP'#EPO,oQbO'#EOO,vQQO'#EfOOQ`'#D]'#D]O,{QbO'#DdO,{QbO'#EhOOQ`'#Df'#DfO-pQRO'#DnOOQ`'#EO'#EOO-uQQO'#D}OOQ`'#D}'#D}OOQ`'#Do'#DoQYQbOOO-}QbO'#DPOOQa'#ES'#ESOOQ`'#DZ'#DZOOQ`'#Ee'#EeOOQ`'#Dv'#DvO.XQbO,59^O.rQbO'#CzO.zQWO'#C{OOOO'#EV'#EVOOOO'#Dp'#DpO/`OSO,59dOOQa,59d,59dOOQ`'#Dr'#DrO/nQbO'#DSO/vQQO,5:zOOQ`'#Dq'#DqO/{QbO,59rO0SQQO,59jOOQa,59r,59rO0_QbO,59rO,{QbO,59cO,{QbO,59cO,{QbO,59cO,{QbO,59tO,{QbO,59tO,{QbO,59tO0iQRO,59aO0pQRO,59aO1RQRO,59aO0|QQO,59aO1^QQO,59aO1fObO,59_O1qQbO'#DwO1|QbO,59]O2eQbO,5;QOOQ`,5:O,5:OO2xQRO,5;SO3PQRO,5;SO3[QbO,5:YOOQ`,5:i,5:iOOQ`-E7m-E7mOOQ`,59k,59kOOQ`-E7t-E7tOOOO,59f,59fOOOO,59g,59gOOOO-E7n-E7nOOQa1G/O1G/OOOQ`-E7p-E7pO3lQbO1G0fOOQ`-E7o-E7oO4PQQO1G/UOOQa1G/^1G/^O4[QbO1G/^OOQO'#Dt'#DtO4PQQO1G/UOOQa1G/U1G/UOOQ`'#Du'#DuO4[QbO1G/^OOQa1G.}1G.}O5TQcO1G.}O5_QcO1G.}O5iQcO1G.}OOQa1G/`1G/`O7XQcO1G/`O7`QcO1G/`O7gQcO1G/`OOQa1G.{1G.{OOQa1G.y1G.yO!aQbO'#CvO&ZQbO'#CrOOQ`,5:c,5:cOOQ`-E7u-E7uO7nQbO1G0lO7yQbO1G0mO8gQbO1G0nOOQ`1G/t1G/tO8zQbO7+&QO9PQbO7+&RO9gQQO7+$pOOQa7+$p7+$pO9rQbO7+$xOOQa7+$x7+$xOOQO-E7r-E7rOOQ`-E7s-E7sO9|QbO'#D_O:RQQO'#DbOOQ`7+&W7+&WO:WQbO7+&WO:]QbO7+&WOOQ`'#Ds'#DsO:eQQO'#DsO:jQbO'#EbOOQ`'#Da'#DaO;^QbO7+&XOOQ`'#Dh'#DhO;iQbO7+&YO;nQbO7+&ZOOQ`<<Il<<IlO<[QbO<<ImOOQa<<H[<<H[OOQa<<Hd<<HdO<aQQO,59yO<fQbO,59|OOQ`<<Ir<<IrO<yQbO<<IrOOQ`,5:_,5:_OOQ`-E7q-E7qOOQ`<<Is<<IsO=OQbO<<IsO=TQbO<<IsOOQ`<<It<<ItOOQ`'#Di'#DiO=]QbO<<IuOOQ`AN?XAN?XO=hQbO1G/eO9PQbO1G/hOOQ`1G/h1G/hOOQ`AN?^AN?^OOQ`AN?_AN?_O={QbOAN?_O,{QbO'#DjOOQ`'#Dx'#DxO>QQbOAN?aO>]QQO'#DlOOQ`AN?aAN?aO>bQbOAN?aO>gQbO7+%POOQ`7+%P7+%POOQ`7+%S7+%SOOQ`G24yG24yO?QQRO,5:UO?XQRO,5:UOOQ`-E7v-E7vOOQ`G24{G24{O?dQbOG24{O?iQQO,5:WOOQ`<<Hk<<HkO?nQQO1G/pOOQ`LD*gLD*gO9PQbO1G/rO;nQbO7+%[OOQ`7+%^7+%^OOQ`<<Hv<<Hv", states: "8lQYQbOOO#tQcO'#CvO$qOSO'#CxO%PQbO'#E`OOQ`'#DR'#DROOQa'#DO'#DOO&SQbO'#D]O'XQcO'#ETOOQa'#ET'#ETO)cQcO'#ESO*bQRO'#CwO+WQcO'#EOO+hQcO'#EOO+rQbO'#CuO,jOpO'#CsOOQ`'#EP'#EPO,oQbO'#EOO,vQQO'#EfOOQ`'#Db'#DbO,{QbO'#DdO,{QbO'#EhOOQ`'#Df'#DfO-pQRO'#DnOOQ`'#EO'#EOO-uQQO'#D}OOQ`'#D}'#D}OOQ`'#Do'#DoQYQbOOO-}QbO'#DPOOQa'#ES'#ESOOQ`'#D`'#D`OOQ`'#Ee'#EeOOQ`'#Dv'#DvO.XQbO,59^O.rQbO'#CzO.zQWO'#C{OOOO'#EV'#EVOOOO'#Dp'#DpO/`OSO,59dOOQa,59d,59dOOQ`'#Dr'#DrO/nQbO'#DSO/vQQO,5:zOOQ`'#Dq'#DqO/{QbO,59wO0SQQO,59jOOQa,59w,59wO0_QbO,59wO,{QbO,59cO,{QbO,59cO,{QbO,59cO,{QbO,59yO,{QbO,59yO,{QbO,59yO0iQRO,59aO0pQRO,59aO1RQRO,59aO0|QQO,59aO1^QQO,59aO1fObO,59_O1qQbO'#DwO1|QbO,59]O2eQbO,5;QOOQ`,5:O,5:OO2xQRO,5;SO3PQRO,5;SO3[QbO,5:YOOQ`,5:i,5:iOOQ`-E7m-E7mOOQ`,59k,59kOOQ`-E7t-E7tOOOO,59f,59fOOOO,59g,59gOOOO-E7n-E7nOOQa1G/O1G/OOOQ`-E7p-E7pO3lQbO1G0fOOQ`-E7o-E7oO4PQQO1G/UOOQa1G/c1G/cO4[QbO1G/cOOQO'#Dt'#DtO4PQQO1G/UOOQa1G/U1G/UOOQ`'#Du'#DuO4[QbO1G/cOOQa1G.}1G.}O5TQcO1G.}O5_QcO1G.}O5iQcO1G.}OOQa1G/e1G/eO7XQcO1G/eO7`QcO1G/eO7gQcO1G/eOOQa1G.{1G.{OOQa1G.y1G.yO!aQbO'#CvO&ZQbO'#CrOOQ`,5:c,5:cOOQ`-E7u-E7uO7nQbO1G0lO7yQbO1G0mO8gQbO1G0nOOQ`1G/t1G/tO8zQbO7+&QO7yQbO7+&SO9VQQO7+$pOOQa7+$p7+$pO9bQbO7+$}OOQa7+$}7+$}OOQO-E7r-E7rOOQ`-E7s-E7sO9lQbO'#DUO9qQQO'#DXOOQ`7+&W7+&WO9vQbO7+&WO9{QbO7+&WOOQ`'#Ds'#DsO:TQQO'#DsO:YQbO'#EaOOQ`'#DW'#DWO:|QbO7+&XOOQ`'#Dh'#DhO;XQbO7+&YO;^QbO7+&ZOOQ`<<Il<<IlO;zQbO<<IlO<PQbO<<IlO<XQbO<<InOOQa<<H[<<H[OOQa<<Hi<<HiO<dQQO,59pO<iQbO,59sOOQ`<<Ir<<IrO<|QbO<<IrOOQ`,5:_,5:_OOQ`-E7q-E7qOOQ`<<Is<<IsO=RQbO<<IsO=WQbO<<IsOOQ`<<It<<ItOOQ`'#Di'#DiO=`QbO<<IuOOQ`AN?WAN?WO=kQbOAN?WOOQ`AN?YAN?YO=pQbOAN?YO=uQbOAN?YO=}QbO1G/[O>bQbO1G/_OOQ`1G/_1G/_OOQ`AN?^AN?^OOQ`AN?_AN?_O>xQbOAN?_O,{QbO'#DjOOQ`'#Dx'#DxO>}QbOAN?aO?YQQO'#DlOOQ`AN?aAN?aO?_QbOAN?aOOQ`G24rG24rOOQ`G24tG24tO?dQbOG24tO?iQbO7+$vOOQ`7+$v7+$vOOQ`7+$y7+$yOOQ`G24yG24yO@SQRO,5:UO@ZQRO,5:UOOQ`-E7v-E7vOOQ`G24{G24{O@fQbOG24{O@kQQO,5:WOOQ`LD*`LD*`OOQ`<<Hb<<HbO@pQQO1G/pOOQ`LD*gLD*gO>bQbO1G/rO;^QbO7+%[OOQ`7+%^7+%^OOQ`<<Hv<<Hv",
stateData: "?v~O!oOS!pOS~O_PO`fOaWOb^OcROhWOpWOqWO{WO!QaO!XcO!ZdO!u]O!xQO#PTO#QUO#RiO~O_mOaWOb^OcROhWOpWOqWOtlOynO{WO!u]O!xQO#PTO#QUO!OjX#RjX#^jX#WjXxjX!SjX!VjX~OP!vXQ!vXR!vXS!vXT!vXU!vXW!vXX!vXY!vXZ!vX[!vX]!vX^!vX~P!aOmtO!xwO!zrO!{sO~O_xOwvP~O_mOaWOb^OhWOpWOqWOtlO{WO!u]O!xQO#PTO#QUO#R{O~O#V!OO~P%XO_mOaWOb^OcROhWOpWOqWOtlOynO{WO!u]O!xQO#PTO#QUO~OP!wXQ!wXR!wXS!wXT!wXU!wXW!wXX!wXY!wXZ!wX[!wX]!wX^!wX#R!wX#^!wX#W!wXx!wX!S!wX!V!wX~P&ZOP!vXQ!vXR!vXS!vXT!vXU!vXW!vXX!vXY!vXZ!vX[!vX]!vX^!vX~O#R!rX#^!rXx!rX!S!rX!V!rX~P(hOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO~OP!ROQ!ROR!SOS!SO^!QO~P)vO#R!rX#^!rXx!rX!S!rX!V!rX~OP!ROQ!ROR!SOS!SO~P*uOT!UOU!VO~P*uO_POaWOb^OcROhWOpWOqWO{WO!u]O!xQO#PTO#QUO~O!t!]O~O!O!^O~P*uOw!`O~O_mOaWOb^OhWOpWOqWO{WO!u]O!xQO#PTO#QUO~OV!dO~O#R!eO#^!eO~OcROy!gO~P,{O!Ofa#Rfa#^fa#Wfaxfa!Sfa!Vfa~P&ZO_!iO!u]O~O!x!jO!z!jO!{!jO!|!jO!}!jO#O!jO~OmtO!x!lO!zrO!{sO~O_xOwvX~Ow!nO~O#V!qO~P%XOtlO#R!sO#V!uO~O#R!vO#V!qO~P,{O#W#QO~P(hOP!ROQ!ROR!SOS!SO#W#QO~OT!UOU!VO#W#QO~O!O!^O#W#QO~O_#ROh#RO!u]O~O_#SOb^O!u]O~O!O!^O#Rea#^ea#Weaxea!Sea!Vea~O`fO!QaO!XcO!ZdO#R#XO~P+rOw#YO~P)vOT!UOU!VOw#YO~O`fO!QaO!XcO!ZdO~P+rO`fO!QaO!XcO!ZdO#R#]O~P+rOtlO#R!sO#V#_O~O#R!vO#V#aO~P,{O^!QORkiSki#Rki#^ki#Wkixki!Ski!Vki~OPkiQki~P4fOP!ROQ!RO~P4fOP!ROQ!RORkiSki#Rki#^ki#Wkixki!Ski!Vki~OW!TOX!TOY!TOZ!TO[!TO]!TOT|i#R|i#^|i#W|iw|ix|i!S|i!V|i~OU!VO~P6ZOU!VO~P6mOU|i~P6ZOx#fO!S#dO!V#eO~O`fO!QaO!XcO!ZdO#R#iOx#UP!S#UP!V#UP~P+rO`fO!QaO!XcO!ZdO#R#pO~P+rOx#qO~O`fO!QaO!XcO!ZdO#R#iOx#UP~P+rOtlO#R!sO#V#sO~O#R!vO#V#tO~P,{O_#uO~Ow#vO~Ox#wO~Ox#wO!V#eO~O#R#yO~O`fO!QaO!XcO!ZdO#R#iOx#UX!S#UX!V#UX!_#UX!a#UX~P+rOx#{O!S#dO!V#eO~Ox$OO~O`fO!QaO!XcO!ZdO#R#iOx#UP!_#UP!a#UP~P+rOx$RO~Ow$SO~O`fO!QaO!XcO!ZdO#R$TO~P+rOx$VO~Ox$WO~Ox$WO!V#eO~Ox$^O!_$YO!a$]O~O`fO!QaO!XcO!ZdO#R$`O~P+rOx$cO~Ox$gO!_$YO!a$]O~Ow$iO~Ox$gO~O`fO!QaO!XcO!ZdO#R#iOx#UP!V#UP~P+rOw$kO~P)vOT!UOU!VOw$kO~Ox$lO~O#R$mO~O#R$nO~Ohq~", stateData: "@x~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(hOT!UOU!VOW!TOX!TOY!TOZ!TO[!TO]!TO~OP!ROQ!ROR!SOS!SO^!QO~P)vO#R!rX#^!rXy!rX|!rX}!rX~OP!ROQ!ROR!SOS!SO~P*uOT!UOU!VO~P*uO_POaWOb^OcROhWOpWOqWO!QWO!u]O!xQO#PTO#QUO~O!t!]O~O!T!^O~P*uOw!`O~O_mOaWOb^OhWOpWOqWO!QWO!u]O!xQO#PTO#QUO~OV!dO~O#R!eO#^!eO~OcRO!O!gO~P,{O!Tfa#Rfa#^fa#Wfayfa|fa}fa~P&ZO_!iO!u]O~O!x!jO!z!jO!{!jO!|!jO!}!jO#O!jO~OmtO!x!lO!zrO!{sO~O_xOwvX~Ow!nO~O#V!qO~P%XOtlO#R!sO#V!uO~O#R!vO#V!qO~P,{O#W#QO~P(hOP!ROQ!ROR!SOS!SO#W#QO~OT!UOU!VO#W#QO~O!T!^O#W#QO~O_#ROh#RO!u]O~O_#SOb^O!u]O~O!T!^O#Rea#^ea#Weayea|ea}ea~O`fO!VaO!XcO!ZdO#R#XO~P+rOw#YO~P)vOT!UOU!VOw#YO~O`fO!VaO!XcO!ZdO~P+rO`fO!VaO!XcO!ZdO#R#]O~P+rOtlO#R!sO#V#_O~O#R!vO#V#aO~P,{O^!QORkiSki#Rki#^ki#Wkiyki|ki}ki~OPkiQki~P4fOP!ROQ!RO~P4fOP!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~P6ZOU!VO~P6mOU!Ri~P6ZOy#dO|#eO}#fO~O`fO!VaO!XcO!ZdO#R#iOy#TP|#TP}#TP~P+rO`fO!VaO!XcO!ZdO#R#pO~P+rOy#dO|#eO}#qO~OtlO#R!sO#V#uO~O#R!vO#V#vO~P,{O_#wO~Ow#xO~O}#yO~O|#eO}#yO~O#R#{O~O`fO!VaO!XcO!ZdO#R#iOy#TX|#TX}#TX!_#TX!a#TX~P+rOy#dO|#eO}#}O~O}$QO~O`fO!VaO!XcO!ZdO#R#iO}#TP!_#TP!a#TP~P+rO}$TO~O|#eO}$TO~Oy#dO|#eO}$VO~Ow$YO~O`fO!VaO!XcO!ZdO#R$ZO~P+rO}$]O~O}$^O~O|#eO}$^O~O}$dO!_$`O!a$cO~O}$fO~O}$gO~O|#eO}$gO~O`fO!VaO!XcO!ZdO#R$iO~P+rO`fO!VaO!XcO!ZdO#R#iO}#TP~P+rO}$lO~O}$pO!_$`O!a$cO~Ow$rO~O}$pO~O}$sO~O`fO!VaO!XcO!ZdO#R#iO|#TP}#TP~P+rOw$uO~P)vOT!UOU!VOw$uO~O}$vO~O#R$wO~O#R$xO~Ohq~",
goto: "2l#^PPPPPPPPPPPPPPPPPPPPP#_#t$YP%X#t&^&yP's'sPP&y'wP([({PPP&yP)O)qP)xP*[P*b*kP)xP)xP*w*z+TP+XP)x+_+e+k+q+w,T,_,i,r,yPPPP-P-T-uPP._/uP0sPPPPPPPP0w0w1bPP1o1v1v2Y2YpgOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nR!Z]u_O]k!^!`!d!n#X#Y#]#k#p#v$S$T$`$m$nrPO]k!`!d!n#X#Y#]#k#p#v$S$T$`$m$nzmPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$YR#S!^rVO]k!`!d!n#X#Y#]#k#p#v$S$T$`$m$nzWPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$YQ!irQ#R!]R#T!^pZOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nQ!X]Q!x!RR!{!S!oWOPUV]cdklq|!P!Q!R!S!T!U!V!`!d!n!r!w#S#T#X#Y#]#`#k#p#v$S$T$Y$`$m$nTtQvYoPVq#S#TQ}UQ!p|X!s}!p!t#^pgOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nYnPVq#S#TQ!Z]R!glRzRp[Ok!`!d!n#X#Y#]#k#p#v$S$T$`$m$nQ!Y]Q!cdQ!|!VQ#O!UR$e$YZoPVq#S#TqgOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nQ#h#WR#}#mQ#m#XQ$b$TR$j$`Q#g#WQ#x#hQ#|#mR$X#}R#o#YQ$Q#pQ$o$mR$p$nT$Z$Q$[Q$_$QR$h$[QkOR!fkQvQR!kvQ|UR!o|QyRR!my^#k#X#]#p$T$`$m$nR#z#kQ!t}Q#^!pT#b!t#^Q!w!PQ#`!rT#c!w#`WqPV#S#TR!hqS!_`![R#V!_Q$[$QR$f$[TjOkShOkQ#W!`Q#Z!dQ#[!n`#j#X#]#k#p$T$`$m$nQ#n#YQ$U#vR$a$Sp`Ok!`!d!n#X#Y#]#k#p#v$S$T$`$m$nQ![]R#U!^rYO]k!`!d!n#X#Y#]#k#p#v$S$T$`$m$nYnPVq#S#TQ!PUQ!acQ!bdQ!glQ!r|W!v!P!r!w#`Q!x!QQ!y!RQ!z!SQ!|!TQ!}!UQ#P!VR$d$YpXOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nzmPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$YR!W]TuQv!PSOPV]klq!`!d!n#S#T#X#Y#]#k#p#v$S$T$`$m$nU#l#X$T$`Q#r#]V$P#p$m$nZpPVq#S#TqbOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$nqeOk!`!d!n#X#Y#]#k#p#v$S$T$`$m$n", goto: "3O#^PPPPPPPPPPPPPPPPPPPPP#_#t$YP%X#t&^&yP's'sPP&y'wP([({P)OP)[)ePPP&yP)}*pP*wP*wP*wP+Z+^+gP+kP*w+q+w+},T,Z,g,q,{-U-]PPPP-c-g.XPP.q0XP1VPPPPPPPP1Z1t1ZPP2R2Y2Y2l2lpgOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xR!Z]u_O]k!^!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xrPO]k!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xzmPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$`R#S!^rVO]k!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xzWPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$`Q!irQ#R!]R#T!^pZOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xQ!X]Q!x!RR!{!S!oWOPUV]cdklq|!P!Q!R!S!T!U!V!`!d!n!r!w#S#T#X#Y#]#`#k#p#x$Y$Z$`$i$w$xTtQvYoPVq#S#TQ}UQ!p|X!s}!p!t#^pgOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xYnPVq#S#TQ!Z]R!glRzRQ#h#WQ#s#[Q$P#mR$X#tQ#m#XQ$k$ZR$t$iQ#g#WQ#r#[Q#z#hQ$O#mQ$U#sQ$W#tQ$_$PR$h$Xp[Ok!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xQ!Y]Q!cdQ!|!VQ#O!UR$n$`ZoPVq#S#TqgOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xR#o#YQ$S#pQ$y$wR$z$xT$a$S$bQ$e$SR$q$bQkOR!fkQvQR!kvQ|UR!o|QyRR!my^#k#X#]#p$Z$i$w$xR#|#kQ!t}Q#^!pT#b!t#^Q!w!PQ#`!rT#c!w#`WqPV#S#TR!hqS!_`![R#V!_Q$b$SR$o$bTjOkShOkQ#W!`Q#Z!dQ#[!n`#j#X#]#k#p$Z$i$w$xQ#n#YQ$[#xR$j$Yp`Ok!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xQ![]R#U!^rYO]k!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xYnPVq#S#TQ!PUQ!acQ!bdQ!glQ!r|W!v!P!r!w#`Q!x!QQ!y!RQ!z!SQ!|!TQ!}!UQ#P!VR$m$`pXOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xzmPUVcdlq|!P!Q!R!S!T!U!V!r!w#S#T#`$`R!W]TuQv!PSOPV]klq!`!d!n#S#T#X#Y#]#k#p#x$Y$Z$i$w$xU#l#X$Z$iQ#t#]V$R#p$w$xZpPVq#S#TqbOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$xqeOk!`!d!n#X#Y#]#k#p#x$Y$Z$i$w$x",
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 keyword Underscore Array Null ConditionalOp PositionalArg operator TryExpr keyword CatchExpr keyword TryBlock FinallyExpr keyword ThrowStatement keyword IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign", 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, maxTerm: 106,
context: trackScope, context: trackScope,
nodeProps: [ nodeProps: [
@ -19,9 +19,9 @@ export const parser = LRParser.deserialize({
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 10, 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#{U6zUmSyQOt#{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!OQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~AOO#^~", 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)], tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!t~~", 11)],
topRules: {"Program":[0,20]}, 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}], 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: 1415 tokenPrec: 1463
}) })

View File

@ -127,7 +127,7 @@ describe('try/catch/finally/throw', () => {
test('parses throw statement with string', () => { test('parses throw statement with string', () => {
expect("throw 'error message'").toMatchTree(` expect("throw 'error message'").toMatchTree(`
ThrowStatement Throw
keyword throw keyword throw
String String
StringFragment error message StringFragment error message
@ -136,7 +136,7 @@ describe('try/catch/finally/throw', () => {
test('parses throw statement with identifier', () => { test('parses throw statement with identifier', () => {
expect('throw error-object').toMatchTree(` expect('throw error-object').toMatchTree(`
ThrowStatement Throw
keyword throw keyword throw
Identifier error-object Identifier error-object
`) `)
@ -144,7 +144,7 @@ describe('try/catch/finally/throw', () => {
test('parses throw statement with dict', () => { test('parses throw statement with dict', () => {
expect('throw [type=validation-error message=failed]').toMatchTree(` expect('throw [type=validation-error message=failed]').toMatchTree(`
ThrowStatement Throw
keyword throw keyword throw
Dict Dict
NamedArg NamedArg
@ -175,3 +175,104 @@ describe('try/catch/finally/throw', () => {
`) `)
}) })
}) })
describe('function-level exception handling', () => {
test('parses function with catch', () => {
expect(`read-file = do path:
read-data path
catch e:
empty-string
end`).toMatchTree(`
Assign
AssignableIdentifier read-file
Eq =
FunctionDef
Do do
Params
Identifier path
colon :
FunctionCall
Identifier read-data
PositionalArg
Identifier path
CatchExpr
keyword catch
Identifier e
colon :
TryBlock
FunctionCallOrIdentifier
Identifier empty-string
keyword end
`)
})
test('parses function with finally', () => {
expect(`cleanup-task = do x:
do-work x
finally:
close-resources
end`).toMatchTree(`
Assign
AssignableIdentifier cleanup-task
Eq =
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCall
Identifier do-work
PositionalArg
Identifier x
FinallyExpr
keyword finally
colon :
TryBlock
FunctionCallOrIdentifier
Identifier close-resources
keyword end
`)
})
test('parses function with catch and finally', () => {
expect(`safe-operation = do x:
risky-work x
catch err:
log err
default-value
finally:
cleanup
end`).toMatchTree(`
Assign
AssignableIdentifier safe-operation
Eq =
FunctionDef
Do do
Params
Identifier x
colon :
FunctionCall
Identifier risky-work
PositionalArg
Identifier x
CatchExpr
keyword catch
Identifier err
colon :
TryBlock
FunctionCall
Identifier log
PositionalArg
Identifier err
FunctionCallOrIdentifier
Identifier default-value
FinallyExpr
keyword finally
colon :
TryBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
`)
})
})