Compare commits
4 Commits
a8fd79a990
...
a99080ba3f
| Author | SHA1 | Date | |
|---|---|---|---|
| a99080ba3f | |||
| c883854187 | |||
| 9bc514a782 | |||
| 701ca98401 |
|
|
@ -17,6 +17,7 @@ import {
|
|||
getNamedArgParts,
|
||||
getPipeExprParts,
|
||||
getStringParts,
|
||||
getTryExprParts,
|
||||
} from '#compiler/utils'
|
||||
|
||||
const DEBUG = false
|
||||
|
|
@ -51,6 +52,7 @@ export class Compiler {
|
|||
instructions: ProgramItem[] = []
|
||||
fnLabelCount = 0
|
||||
ifLabelCount = 0
|
||||
tryLabelCount = 0
|
||||
bytecode: Bytecode
|
||||
pipeCounter = 0
|
||||
|
||||
|
|
@ -253,7 +255,10 @@ export class Compiler {
|
|||
}
|
||||
|
||||
case terms.FunctionDef: {
|
||||
const { paramNames, bodyNodes } = getFunctionDefParts(node, input)
|
||||
const { paramNames, bodyNodes, catchVariable, catchBody, finallyBody } = getFunctionDefParts(
|
||||
node,
|
||||
input
|
||||
)
|
||||
const instructions: ProgramItem[] = []
|
||||
const functionLabel: Label = `.func_${this.fnLabelCount++}`
|
||||
const afterLabel: Label = `.after_${functionLabel}`
|
||||
|
|
@ -261,9 +266,27 @@ export class Compiler {
|
|||
instructions.push(['JUMP', afterLabel])
|
||||
|
||||
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 (catchVariable || finallyBody) {
|
||||
// If function has catch or finally, wrap body in try/catch/finally
|
||||
instructions.push(
|
||||
...this.#compileTryCatchFinally(compileFunctionBody, catchVariable, catchBody, finallyBody, input)
|
||||
)
|
||||
} else {
|
||||
instructions.push(...compileFunctionBody())
|
||||
}
|
||||
|
||||
instructions.push(['RETURN'])
|
||||
|
||||
instructions.push([`${afterLabel}:`])
|
||||
|
|
@ -317,10 +340,48 @@ export class Compiler {
|
|||
}
|
||||
|
||||
case terms.ThenBlock:
|
||||
case terms.SingleLineThenBlock: {
|
||||
const instructions = getAllChildren(node)
|
||||
.map((child) => this.#compileNode(child, input))
|
||||
.flat()
|
||||
case terms.SingleLineThenBlock:
|
||||
case terms.TryBlock: {
|
||||
const children = getAllChildren(node)
|
||||
const instructions: ProgramItem[] = []
|
||||
|
||||
children.forEach((child, index) => {
|
||||
instructions.push(...this.#compileNode(child, input))
|
||||
// keep only the last expression's value
|
||||
if (index < children.length - 1) {
|
||||
instructions.push(['POP'])
|
||||
}
|
||||
})
|
||||
|
||||
return instructions
|
||||
}
|
||||
|
||||
case terms.TryExpr: {
|
||||
const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input)
|
||||
|
||||
return this.#compileTryCatchFinally(
|
||||
() => this.#compileNode(tryBlock, input),
|
||||
catchVariable,
|
||||
catchBody,
|
||||
finallyBody,
|
||||
input
|
||||
)
|
||||
}
|
||||
|
||||
case terms.Throw: {
|
||||
const children = getAllChildren(node)
|
||||
const [_throwKeyword, expression] = children
|
||||
if (!expression) {
|
||||
throw new CompilerError(
|
||||
`Throw expected expression, got ${children.length} children`,
|
||||
node.from,
|
||||
node.to
|
||||
)
|
||||
}
|
||||
|
||||
const instructions: ProgramItem[] = []
|
||||
instructions.push(...this.#compileNode(expression, input))
|
||||
instructions.push(['THROW'])
|
||||
|
||||
return instructions
|
||||
}
|
||||
|
|
@ -527,4 +588,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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
311
src/compiler/tests/exceptions.test.ts
Normal file
311
src/compiler/tests/exceptions.test.ts
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
import { describe } from 'bun:test'
|
||||
import { expect, test } from 'bun:test'
|
||||
|
||||
describe('exception handling', () => {
|
||||
test('try with catch - no error thrown', () => {
|
||||
expect(`
|
||||
try:
|
||||
42
|
||||
catch err:
|
||||
99
|
||||
end
|
||||
`).toEvaluateTo(42)
|
||||
})
|
||||
|
||||
test('try with catch - error thrown', () => {
|
||||
expect(`
|
||||
try:
|
||||
throw 'something went wrong'
|
||||
99
|
||||
catch err:
|
||||
err
|
||||
end
|
||||
`).toEvaluateTo('something went wrong')
|
||||
})
|
||||
|
||||
test('try with catch - catch variable binding', () => {
|
||||
expect(`
|
||||
try:
|
||||
throw 100
|
||||
catch my-error:
|
||||
my-error + 50
|
||||
end
|
||||
`).toEvaluateTo(150)
|
||||
})
|
||||
|
||||
test('try with finally - no error', () => {
|
||||
expect(`
|
||||
x = 0
|
||||
result = try:
|
||||
x = 10
|
||||
42
|
||||
finally:
|
||||
x = x + 5
|
||||
end
|
||||
x
|
||||
`).toEvaluateTo(15)
|
||||
})
|
||||
|
||||
test('try with finally - return value from try', () => {
|
||||
expect(`
|
||||
x = 0
|
||||
result = try:
|
||||
x = 10
|
||||
42
|
||||
finally:
|
||||
x = x + 5
|
||||
999
|
||||
end
|
||||
result
|
||||
`).toEvaluateTo(42)
|
||||
})
|
||||
|
||||
test('try with catch and finally - no error', () => {
|
||||
expect(`
|
||||
x = 0
|
||||
try:
|
||||
x = 10
|
||||
42
|
||||
catch err:
|
||||
x = 999
|
||||
0
|
||||
finally:
|
||||
x = x + 5
|
||||
end
|
||||
x
|
||||
`).toEvaluateTo(15)
|
||||
})
|
||||
|
||||
test('try with catch and finally - error thrown', () => {
|
||||
expect(`
|
||||
x = 0
|
||||
result = try:
|
||||
x = 10
|
||||
throw 'error'
|
||||
99
|
||||
catch err:
|
||||
x = 20
|
||||
err
|
||||
finally:
|
||||
x = x + 5
|
||||
end
|
||||
x
|
||||
`).toEvaluateTo(25)
|
||||
})
|
||||
|
||||
test('try with catch and finally - return value from catch', () => {
|
||||
expect(`
|
||||
result = try:
|
||||
throw 'oops'
|
||||
catch err:
|
||||
'caught'
|
||||
finally:
|
||||
'finally'
|
||||
end
|
||||
result
|
||||
`).toEvaluateTo('caught')
|
||||
})
|
||||
|
||||
test('throw statement with string', () => {
|
||||
expect(`
|
||||
try:
|
||||
throw 'error message'
|
||||
catch err:
|
||||
err
|
||||
end
|
||||
`).toEvaluateTo('error message')
|
||||
})
|
||||
|
||||
test('throw statement with number', () => {
|
||||
expect(`
|
||||
try:
|
||||
throw 404
|
||||
catch err:
|
||||
err
|
||||
end
|
||||
`).toEvaluateTo(404)
|
||||
})
|
||||
|
||||
test('throw statement with dict', () => {
|
||||
expect(`
|
||||
try:
|
||||
throw [code=500 message=failed]
|
||||
catch e:
|
||||
e
|
||||
end
|
||||
`).toEvaluateTo({ code: 500, message: 'failed' })
|
||||
})
|
||||
|
||||
test('uncaught exception fails', () => {
|
||||
expect(`throw 'uncaught error'`).toFailEvaluation()
|
||||
})
|
||||
|
||||
test('single-line try catch', () => {
|
||||
expect(`result = try: throw 'err' catch e: 'handled' end; result`).toEvaluateTo('handled')
|
||||
})
|
||||
|
||||
test('nested try blocks - inner catches', () => {
|
||||
expect(`
|
||||
try:
|
||||
result = try:
|
||||
throw 'inner error'
|
||||
catch err:
|
||||
err
|
||||
end
|
||||
result
|
||||
catch outer:
|
||||
'outer'
|
||||
end
|
||||
`).toEvaluateTo('inner error')
|
||||
})
|
||||
|
||||
test('nested try blocks - outer catches', () => {
|
||||
expect(`
|
||||
try:
|
||||
try:
|
||||
throw 'inner error'
|
||||
catch err:
|
||||
throw 'outer error'
|
||||
end
|
||||
catch outer:
|
||||
outer
|
||||
end
|
||||
`).toEvaluateTo('outer error')
|
||||
})
|
||||
|
||||
test('try as expression', () => {
|
||||
expect(`
|
||||
x = try: 10 catch err: 0 end
|
||||
y = try: throw 'err' catch err: 20 end
|
||||
x + y
|
||||
`).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')
|
||||
})
|
||||
})
|
||||
|
|
@ -59,11 +59,11 @@ export const getAssignmentParts = (node: SyntaxNode) => {
|
|||
|
||||
export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
|
||||
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(
|
||||
`FunctionDef expected 5 children, got ${children.length}`,
|
||||
`FunctionDef expected at least 4 children, got ${children.length}`,
|
||||
node.from,
|
||||
node.to
|
||||
)
|
||||
|
|
@ -80,8 +80,48 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
|
|||
return input.slice(param.from, param.to)
|
||||
})
|
||||
|
||||
const bodyWithoutEnd = bodyNodes.slice(0, -1)
|
||||
return { paramNames, bodyNodes: bodyWithoutEnd }
|
||||
// Separate body nodes from catch/finally/end
|
||||
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) => {
|
||||
|
|
@ -231,3 +271,62 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
|
|||
|
||||
return { objectName, property }
|
||||
}
|
||||
|
||||
export const getTryExprParts = (node: SyntaxNode, input: string) => {
|
||||
const children = getAllChildren(node)
|
||||
|
||||
// First child is always 'try' keyword, second is colon, third is TryBlock or statement
|
||||
const [tryKeyword, _colon, tryBlock, ...rest] = children
|
||||
|
||||
if (!tryKeyword || !tryBlock) {
|
||||
throw new CompilerError(
|
||||
`TryExpr expected at least 3 children, got ${children.length}`,
|
||||
node.from,
|
||||
node.to
|
||||
)
|
||||
}
|
||||
|
||||
let catchExpr: SyntaxNode | undefined
|
||||
let catchVariable: string | undefined
|
||||
let catchBody: SyntaxNode | undefined
|
||||
let finallyExpr: SyntaxNode | undefined
|
||||
let finallyBody: SyntaxNode | undefined
|
||||
|
||||
rest.forEach((child) => {
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
tryBlock,
|
||||
catchExpr,
|
||||
catchVariable,
|
||||
catchBody,
|
||||
finallyExpr,
|
||||
finallyBody,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ item {
|
|||
consumeToTerminator {
|
||||
PipeExpr |
|
||||
ambiguousFunctionCall |
|
||||
TryExpr |
|
||||
Throw |
|
||||
IfExpr |
|
||||
FunctionDef |
|
||||
Assign |
|
||||
|
|
@ -97,11 +99,11 @@ FunctionDef {
|
|||
}
|
||||
|
||||
singleLineFunctionDef {
|
||||
Do Params colon consumeToTerminator @specialize[@name=keyword]<Identifier, "end">
|
||||
Do Params colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
||||
}
|
||||
|
||||
multilineFunctionDef {
|
||||
Do Params colon newlineOrSemicolon block @specialize[@name=keyword]<Identifier, "end">
|
||||
Do Params colon newlineOrSemicolon block CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
||||
}
|
||||
|
||||
IfExpr {
|
||||
|
|
@ -132,6 +134,34 @@ SingleLineThenBlock {
|
|||
consumeToTerminator
|
||||
}
|
||||
|
||||
TryExpr {
|
||||
singleLineTry | multilineTry
|
||||
}
|
||||
|
||||
singleLineTry {
|
||||
@specialize[@name=keyword]<Identifier, "try"> colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
||||
}
|
||||
|
||||
multilineTry {
|
||||
@specialize[@name=keyword]<Identifier, "try"> colon newlineOrSemicolon TryBlock CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
|
||||
}
|
||||
|
||||
CatchExpr {
|
||||
@specialize[@name=keyword]<Identifier, "catch"> Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator)
|
||||
}
|
||||
|
||||
FinallyExpr {
|
||||
@specialize[@name=keyword]<Identifier, "finally"> colon (newlineOrSemicolon TryBlock | consumeToTerminator)
|
||||
}
|
||||
|
||||
TryBlock {
|
||||
block
|
||||
}
|
||||
|
||||
Throw {
|
||||
@specialize[@name=keyword]<Identifier, "throw"> expression
|
||||
}
|
||||
|
||||
ConditionalOp {
|
||||
expression !comparison EqEq expression |
|
||||
expression !comparison Neq expression |
|
||||
|
|
|
|||
|
|
@ -39,15 +39,20 @@ export const
|
|||
FunctionDef = 37,
|
||||
Params = 38,
|
||||
colon = 39,
|
||||
keyword = 54,
|
||||
Underscore = 41,
|
||||
Array = 42,
|
||||
Null = 43,
|
||||
ConditionalOp = 44,
|
||||
PositionalArg = 45,
|
||||
IfExpr = 47,
|
||||
SingleLineThenBlock = 49,
|
||||
ThenBlock = 50,
|
||||
ElseIfExpr = 51,
|
||||
ElseExpr = 53,
|
||||
Assign = 55
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@ import {operatorTokenizer} from "./operatorTokenizer"
|
|||
import {tokenizer, specializeKeyword} from "./tokenizer"
|
||||
import {trackScope} from "./scopeTracker"
|
||||
import {highlighting} from "./highlight"
|
||||
const spec_Identifier = {__proto__:null,end:80, null:86, if:96, elseif:104, else:108}
|
||||
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({
|
||||
version: 14,
|
||||
states: "3UQYQbOOO#hQcO'#CvO$eOSO'#CxO$sQbO'#EVOOQ`'#DR'#DROOQa'#DO'#DOO%vQbO'#DWO&{QcO'#DzOOQa'#Dz'#DzO)PQcO'#DyO)xQRO'#CwO*]QcO'#DuO*tQcO'#DuO+VQbO'#CuO+}OpO'#CsOOQ`'#Dv'#DvO,SQbO'#DuO,bQbO'#E]OOQ`'#D]'#D]O-VQRO'#DeOOQ`'#Du'#DuO-[QQO'#DtOOQ`'#Dt'#DtOOQ`'#Df'#DfQYQbOOO-dQbO'#DPOOQa'#Dy'#DyOOQ`'#DZ'#DZOOQ`'#E['#E[OOQ`'#Dm'#DmO-nQbO,59^O.RQbO'#CzO.ZQWO'#C{OOOO'#D|'#D|OOOO'#Dg'#DgO.oOSO,59dOOQa,59d,59dOOQ`'#Di'#DiO.}QbO'#DSO/VQQO,5:qOOQ`'#Dh'#DhO/[QbO,59rO/cQQO,59jOOQa,59r,59rO/nQbO,59rO,bQbO,59cO,bQbO,59cO,bQbO,59cO,bQbO,59tO,bQbO,59tO,bQbO,59tO/xQRO,59aO0PQRO,59aO0bQRO,59aO0]QQO,59aO0mQQO,59aO0uObO,59_O1QQbO'#DnO1]QbO,59]O1nQRO,5:wO1uQRO,5:wO2QQbO,5:POOQ`,5:`,5:`OOQ`-E7d-E7dOOQ`,59k,59kOOQ`-E7k-E7kOOOO,59f,59fOOOO,59g,59gOOOO-E7e-E7eOOQa1G/O1G/OOOQ`-E7g-E7gO2[QbO1G0]OOQ`-E7f-E7fO2iQQO1G/UOOQa1G/^1G/^O2tQbO1G/^OOQO'#Dk'#DkO2iQQO1G/UOOQa1G/U1G/UOOQ`'#Dl'#DlO2tQbO1G/^OOQa1G.}1G.}O3gQcO1G.}O3qQcO1G.}O3{QcO1G.}OOQa1G/`1G/`O5_QcO1G/`O5fQcO1G/`O5mQcO1G/`OOQa1G.{1G.{OOQa1G.y1G.yO!ZQbO'#CvO%}QbO'#CrOOQ`,5:Y,5:YOOQ`-E7l-E7lO5tQbO1G0cOOQ`1G/k1G/kO6RQbO7+%wO6WQbO7+%xO6hQQO7+$pOOQa7+$p7+$pO6sQbO7+$xOOQa7+$x7+$xOOQO-E7i-E7iOOQ`-E7j-E7jOOQ`'#D_'#D_O6}QbO7+%}O7SQbO7+&OOOQ`<<Ic<<IcOOQ`'#Dj'#DjO7jQQO'#DjO7oQbO'#EXO8VQbO<<IdOOQa<<H[<<H[OOQa<<Hd<<HdOOQ`<<Ii<<IiOOQ`'#D`'#D`O8[QbO<<IjOOQ`,5:U,5:UOOQ`-E7h-E7hOOQ`AN?OAN?OO,bQbO'#DaOOQ`'#Do'#DoO8gQbOAN?UO8rQQO'#DcOOQ`AN?UAN?UO8wQbOAN?UO8|QRO,59{O9TQRO,59{OOQ`-E7m-E7mOOQ`G24pG24pO9`QbOG24pO9eQQO,59}O9jQQO1G/gOOQ`LD*[LD*[O6WQbO1G/iO7SQbO7+%ROOQ`7+%T7+%TOOQ`<<Hm<<Hm",
|
||||
stateData: "9r~O!fOS!gOS~O_PO`cOaWOb^OcROhWOpWOqWO{WO!QaO!l]O!oQO!vTO!wUO!xfO~O_jOaWOb^OcROhWOpWOqWOtiOykO{WO!l]O!oQO!vTO!wUO!OjX!xjX#RjX!}jXxjX~OP!mXQ!mXR!mXS!mXT!mXU!mXW!mXX!mXY!mXZ!mX[!mX]!mX^!mX~P!ZOmqO!otO!qoO!rpO~O_uOwvP~O_jOaWOb^OhWOpWOqWOtiO{WO!l]O!oQO!vTO!wUO!xxO~O!|{O~P${O_jOaWOb^OcROhWOpWOqWOtiOykO{WO!l]O!oQO!vTO!wUO~OP!nXQ!nXR!nXS!nXT!nXU!nXW!nXX!nXY!nXZ!nX[!nX]!nX^!nX!x!nX#R!nX!}!nXx!nX~P%}OP!mXQ!mXR!mXS!mXT!mXU!mXW!mXX!mXY!mXZ!mX[!mX]!mX^!mX~O!x!iX#R!iXx!iX~P(UOT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~OP!OOQ!OOR!POS!PO^}O~P)^OP!OOQ!OOR!POS!PO!x!iX#R!iXx!iX~OT!ROU!SO!x!iX#R!iXx!iX~O_POaWOb^OcROhWOpWOqWO{WO!l]O!oQO!vTO!wUO~O!k!YO~O!O!ZO!x!iX#R!iXx!iX~O_jOaWOb^OhWOpWOqWO{WO!l]O!oQO!vTO!wUO~OV!_O~O!x!`O#R!`O~OcROy!bO~P,bO!Ofa!xfa#Rfa!}faxfa~P%}O_!dO!l]O~O!o!eO!q!eO!r!eO!s!eO!t!eO!u!eO~OmqO!o!gO!qoO!rpO~O_uOwvX~Ow!iO~O!|!lO~P${OtiO!x!nO!|!pO~O!x!qO!|!lO~P,bO!}!{O~P(UOP!OOQ!OOR!POS!PO!}!{O~OT!ROU!SO!}!{O~O!O!ZO!}!{O~O_!|Oh!|O!l]O~O_!}Ob^O!l]O~O!O!ZO!xea#Rea!}eaxea~Ow#RO~P)^OT!ROU!SOw#RO~O`cO!QaO~P+VO`cO!QaO!x#UO~P+VOtiO!x!nO!|#WO~O!x!qO!|#YO~P,bO^}ORkiSki!xki#Rki!}kixki~OPkiQki~P3OOP!OOQ!OO~P3OOP!OOQ!OORkiSki!xki#Rki!}kixki~OW!QOX!QOY!QOZ!QO[!QO]!QOT|i!x|i#R|i!}|iw|ix|i~OU!SO~P4gOU!SO~P4yOU|i~P4gO`cO!QaO!x#_O~P+VOx#`O~O`cO!QaO!x#aOx!{P~P+VOtiO!x!nO!|#eO~O!x!qO!|#fO~P,bOx#gO~O`cO!QaO!x#aOx!{P!U!{P!W!{P~P+VO!x#jO~O`cO!QaO!x#aOx!{X!U!{X!W!{X~P+VOx#lO~Ox#qO!U#mO!W#pO~Ox#vO!U#mO!W#pO~Ow#xO~Ox#vO~Ow#yO~P)^OT!ROU!SOw#yO~Ox#zO~O!x#{O~O!x#|O~Ohq~",
|
||||
goto: ".}#RPPPPPPPPPPPPPPPPPPPPP#S#c#qP$i#c%g%|P&o&oPP%|&sP'W'qPPP%|P't(aP(hP(t(w)QP)UP(h)[)b)h)n)t)}*X*c*l*sPPPP*y*}+cPP+u-SP-yPPPPPPPP-}-}.bPP.j.q.qddOh!_!i#R#U#_#c#{#|R!W]i_O]h!Z!_!i#R#U#_#c#{#|fPO]h!_!i#R#U#_#c#{#|xjPUVainy|}!O!P!Q!R!S!m!r!}#O#X#mR!}!ZfVO]h!_!i#R#U#_#c#{#|xWPUVainy|}!O!P!Q!R!S!m!r!}#O#X#mQ!doQ!|!YR#O!ZdZOh!_!i#R#U#_#c#{#|Q!U]Q!s!OR!v!P!aWOPUV]ahiny|}!O!P!Q!R!S!_!i!m!r!}#O#R#U#X#_#c#m#{#|TqQsYlPVn!}#OQzUQ!kyX!nz!k!o#VddOh!_!i#R#U#_#c#{#|YkPVn!}#OQ!W]R!biRwRd[Oh!_!i#R#U#_#c#{#|Q!V]Q!^aQ!w!SQ!y!RR#t#mZlPVn!}#OedOh!_!i#R#U#_#c#{#|R#^#RQ#i#_Q#}#{R$O#|T#n#i#oQ#r#iR#w#oQhOR!ahQsQR!fsQyUR!jyQvRR!hvW#c#U#_#{#|R#k#cQ!ozQ#V!kT#Z!o#VQ!r|Q#X!mT#[!r#XWnPV!}#OR!cnS![`!XR#Q![Q#o#iR#u#oTgOhSeOhQ#S!_Q#T!iQ#]#RZ#b#U#_#c#{#|d`Oh!_!i#R#U#_#c#{#|Q!X]R#P!ZfYO]h!_!i#R#U#_#c#{#|YkPVn!}#OQ|UQ!]aQ!biQ!myW!q|!m!r#XQ!s}Q!t!OQ!u!PQ!w!QQ!x!RQ!z!SR#s#mdXOh!_!i#R#U#_#c#{#|xjPUVainy|}!O!P!Q!R!S!m!r!}#O#X#mR!T]TrQssSOPV]hin!_!i!}#O#R#U#_#c#{#|Q#d#UV#h#_#{#|ZmPVn!}#OebOh!_!i#R#U#_#c#{#|",
|
||||
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 IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword Assign",
|
||||
maxTerm: 95,
|
||||
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: "@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: "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 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,
|
||||
context: trackScope,
|
||||
nodeProps: [
|
||||
["closedBy", 39,"end"]
|
||||
|
|
@ -19,9 +19,9 @@ export const parser = LRParser.deserialize({
|
|||
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!fYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UmS!xQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZmS!gYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!gYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O!q~~'aO!o~U'hUmS!lQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUmS!}QOt#{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!wQmSOt#{uw#{x!_#{!_!`5O!`#O#{#P;'S#{;'S;=`$d<%lO#{U5TVmSOt#{uw#{x#O#{#P#Q5j#Q;'S#{;'S;=`$d<%lO#{U5qU!vQmSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6YO!r~U6aU!|QmSOt#{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!sWmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^=xY!uWmSOt#{uw#{x!_#{!_!`8R!`#O#{#P#T#{#T#o7^#o;'S#{;'S;=`$d<%lO#{^>o[!tWmSOt#{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#R~",
|
||||
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!k~~", 11)],
|
||||
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: 1135
|
||||
tokenPrec: 1463
|
||||
})
|
||||
|
|
|
|||
278
src/parser/tests/exceptions.test.ts
Normal file
278
src/parser/tests/exceptions.test.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
import { expect, describe, test } from 'bun:test'
|
||||
|
||||
import '../shrimp.grammar' // Importing this so changes cause it to retest!
|
||||
|
||||
describe('try/catch/finally/throw', () => {
|
||||
test('parses try with catch', () => {
|
||||
expect(`try:
|
||||
risky-operation
|
||||
catch err:
|
||||
handle-error err
|
||||
end`).toMatchTree(`
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCallOrIdentifier
|
||||
Identifier risky-operation
|
||||
CatchExpr
|
||||
keyword catch
|
||||
Identifier err
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCall
|
||||
Identifier handle-error
|
||||
PositionalArg
|
||||
Identifier err
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses try with finally', () => {
|
||||
expect(`try:
|
||||
do-work
|
||||
finally:
|
||||
cleanup
|
||||
end`).toMatchTree(`
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCallOrIdentifier
|
||||
Identifier do-work
|
||||
FinallyExpr
|
||||
keyword finally
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCallOrIdentifier
|
||||
Identifier cleanup
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses try with catch and finally', () => {
|
||||
expect(`try:
|
||||
risky-operation
|
||||
catch err:
|
||||
handle-error err
|
||||
finally:
|
||||
cleanup
|
||||
end`).toMatchTree(`
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCallOrIdentifier
|
||||
Identifier risky-operation
|
||||
CatchExpr
|
||||
keyword catch
|
||||
Identifier err
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCall
|
||||
Identifier handle-error
|
||||
PositionalArg
|
||||
Identifier err
|
||||
FinallyExpr
|
||||
keyword finally
|
||||
colon :
|
||||
TryBlock
|
||||
FunctionCallOrIdentifier
|
||||
Identifier cleanup
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses single-line try with catch', () => {
|
||||
expect('result = try: parse-number input catch err: 0 end').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier result
|
||||
Eq =
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
FunctionCall
|
||||
Identifier parse-number
|
||||
PositionalArg
|
||||
Identifier input
|
||||
CatchExpr
|
||||
keyword catch
|
||||
Identifier err
|
||||
colon :
|
||||
Number 0
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses single-line try with finally', () => {
|
||||
expect('try: work catch err: 0 finally: cleanup end').toMatchTree(`
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
FunctionCallOrIdentifier
|
||||
Identifier work
|
||||
CatchExpr
|
||||
keyword catch
|
||||
Identifier err
|
||||
colon :
|
||||
Number 0
|
||||
FinallyExpr
|
||||
keyword finally
|
||||
colon :
|
||||
FunctionCallOrIdentifier
|
||||
Identifier cleanup
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses throw statement with string', () => {
|
||||
expect("throw 'error message'").toMatchTree(`
|
||||
Throw
|
||||
keyword throw
|
||||
String
|
||||
StringFragment error message
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses throw statement with identifier', () => {
|
||||
expect('throw error-object').toMatchTree(`
|
||||
Throw
|
||||
keyword throw
|
||||
Identifier error-object
|
||||
`)
|
||||
})
|
||||
|
||||
test('parses throw statement with dict', () => {
|
||||
expect('throw [type=validation-error message=failed]').toMatchTree(`
|
||||
Throw
|
||||
keyword throw
|
||||
Dict
|
||||
NamedArg
|
||||
NamedArgPrefix type=
|
||||
Identifier validation-error
|
||||
NamedArg
|
||||
NamedArgPrefix message=
|
||||
Identifier failed
|
||||
`)
|
||||
})
|
||||
|
||||
test('does not parse identifiers that start with try', () => {
|
||||
expect('trying = try: work catch err: 0 end').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier trying
|
||||
Eq =
|
||||
TryExpr
|
||||
keyword try
|
||||
colon :
|
||||
FunctionCallOrIdentifier
|
||||
Identifier work
|
||||
CatchExpr
|
||||
keyword catch
|
||||
Identifier err
|
||||
colon :
|
||||
Number 0
|
||||
keyword end
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
`)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user