Compare commits

...

13 Commits

16 changed files with 849 additions and 133 deletions

View File

@ -12,7 +12,7 @@ Go to http://localhost:3000 to try out the playground.
tail log.txt lines=50 tail log.txt lines=50
name = "Shrimp" name = "Shrimp"
greet = fn person: echo "Hello" person greet = do person: echo "Hello" person
result = tail log.txt lines=10 result = tail log.txt lines=10

View File

@ -2,9 +2,11 @@
import { Compiler } from '../src/compiler/compiler' import { Compiler } from '../src/compiler/compiler'
import { colors, globals } from '../src/prelude' import { colors, globals } from '../src/prelude'
import { parser } from '../src/parser/shrimp'
import { treeToString } from '../src/utils/tree'
import { VM, fromValue, bytecodeToString } from 'reefvm' import { VM, fromValue, bytecodeToString } from 'reefvm'
import { readFileSync, writeFileSync, mkdirSync } from 'fs' import { readFileSync, writeFileSync, mkdirSync } from 'fs'
import { randomUUID } from "crypto" import { randomUUID } from 'crypto'
import { spawn } from 'child_process' import { spawn } from 'child_process'
import { join } from 'path' import { join } from 'path'
@ -32,6 +34,17 @@ async function compileFile(filePath: string) {
} }
} }
async function parseFile(filePath: string) {
try {
const code = readFileSync(filePath, 'utf-8')
const tree = parser.parse(code)
return treeToString(tree, code)
} catch (error: any) {
console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
process.exit(1)
}
}
function showHelp() { function showHelp() {
console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell. console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell.
@ -39,6 +52,7 @@ ${colors.bright}Usage:${colors.reset} shrimp <command> [...args]
${colors.bright}Commands:${colors.reset} ${colors.bright}Commands:${colors.reset}
${colors.cyan}run ${colors.yellow}./my-file.sh${colors.reset} Execute a file with Shrimp ${colors.cyan}run ${colors.yellow}./my-file.sh${colors.reset} Execute a file with Shrimp
${colors.cyan}parse ${colors.yellow}./my-file.sh${colors.reset} Print parse tree for Shrimp file
${colors.cyan}bytecode ${colors.yellow}./my-file.sh${colors.reset} Print bytecode for Shrimp file ${colors.cyan}bytecode ${colors.yellow}./my-file.sh${colors.reset} Print bytecode for Shrimp file
${colors.cyan}eval ${colors.yellow}'some code'${colors.reset} Evaluate a line of Shrimp code ${colors.cyan}eval ${colors.yellow}'some code'${colors.reset} Evaluate a line of Shrimp code
${colors.cyan}repl${colors.reset} Start REPL ${colors.cyan}repl${colors.reset} Start REPL
@ -102,6 +116,16 @@ async function main() {
return return
} }
if (['parse', '-parse', '--parse', '-p'].includes(command)) {
const file = args[1]
if (!file) {
console.log(`${colors.bright}usage: shrimp parse <file>${colors.reset}`)
process.exit(1)
}
console.log(await parseFile(file))
return
}
if (['run', '-run', '--run', '-r'].includes(command)) { if (['run', '-run', '--run', '-r'].includes(command)) {
const file = args[1] const file = args[1]
if (!file) { if (!file) {

View File

@ -62,7 +62,7 @@
"hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="], "hono": ["hono@4.9.8", "", {}, "sha512-JW8Bb4RFWD9iOKxg5PbUarBYGM99IcxFl2FPBo2gSJO11jjUDqlP1Bmfyqt8Z/dGhIQ63PMA9LdcLefXyIasyg=="],
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#c69b172c78853756ec8acba5bc33d93eb6a571c6", { "peerDependencies": { "typescript": "^5" } }, "c69b172c78853756ec8acba5bc33d93eb6a571c6"], "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#0f39e9401eb7a0a7c906e150127f9829458a79b6", { "peerDependencies": { "typescript": "^5" } }, "0f39e9401eb7a0a7c906e150127f9829458a79b6"],
"style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="],

View File

@ -54,6 +54,7 @@ export class Compiler {
fnLabelCount = 0 fnLabelCount = 0
ifLabelCount = 0 ifLabelCount = 0
tryLabelCount = 0 tryLabelCount = 0
loopLabelCount = 0
bytecode: Bytecode bytecode: Bytecode
pipeCounter = 0 pipeCounter = 0
@ -388,9 +389,7 @@ export class Compiler {
return instructions return instructions
} }
case terms.ThenBlock: case terms.Block: {
case terms.SingleLineThenBlock:
case terms.TryBlock: {
const children = getAllChildren(node) const children = getAllChildren(node)
const instructions: ProgramItem[] = [] const instructions: ProgramItem[] = []
@ -405,6 +404,51 @@ export class Compiler {
return instructions return instructions
} }
case terms.FunctionCallWithBlock: {
const [fn, _colon, ...block] = getAllChildren(node)
let instructions: ProgramItem[] = []
const fnLabel: Label = `.func_${this.fnLabelCount++}`
const afterLabel: Label = `.after_${fnLabel}`
instructions.push(['JUMP', afterLabel])
instructions.push([`${fnLabel}:`])
instructions.push(
...block.filter(x => x.type.name !== 'keyword')
.map(x => this.#compileNode(x!, input))
.flat()
)
instructions.push(['RETURN'])
instructions.push([`${afterLabel}:`])
if (fn?.type.id === terms.FunctionCallOrIdentifier) {
instructions.push(['LOAD', input.slice(fn!.from, fn!.to)])
instructions.push(['MAKE_FUNCTION', [], fnLabel])
instructions.push(['PUSH', 1])
instructions.push(['PUSH', 0])
instructions.push(['CALL'])
} else if (fn?.type.id === terms.FunctionCall) {
let body = this.#compileNode(fn!, input)
const namedArgCount = (body[body.length - 2]![1] as number) * 2
const startSlice = body.length - namedArgCount - 3
body = [
...body.slice(0, startSlice),
['MAKE_FUNCTION', [], fnLabel],
...body.slice(startSlice)
]
// @ts-ignore
body[body.length - 3]![1] += 1
instructions.push(...body)
} else {
throw new Error(`FunctionCallWithBlock: Expected FunctionCallOrIdentifier or FunctionCall`)
}
return instructions
}
case terms.TryExpr: { case terms.TryExpr: {
const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input) const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input)
@ -629,6 +673,24 @@ export class Compiler {
return instructions return instructions
} }
case terms.WhileExpr: {
const [_while, test, _colon, block] = getAllChildren(node)
const instructions: ProgramItem[] = []
this.loopLabelCount++
const startLoop = `.loop_${this.loopLabelCount}:`
const endLoop = `.end_loop_${this.loopLabelCount}:`
instructions.push([`${startLoop}:`])
instructions.push(...this.#compileNode(test!, input))
instructions.push(['JUMP_IF_FALSE', endLoop])
instructions.push(...this.#compileNode(block!, input))
instructions.push(['JUMP', startLoop])
instructions.push([`${endLoop}:`])
return instructions
}
default: default:
throw new CompilerError( throw new CompilerError(
`Compiler doesn't know how to handle a "${node.type.name}" node.`, `Compiler doesn't know how to handle a "${node.type.name}" node.`,

View File

@ -0,0 +1,55 @@
import { expect, describe, test } from 'bun:test'
describe('single line function blocks', () => {
test('work with no args', () => {
expect(`trap = do x: x end; trap: true end`).toEvaluateTo(true)
})
test('work with one arg', () => {
expect(`trap = do x y: [ x (y) ] end; trap EXIT: true end`).toEvaluateTo(['EXIT', true])
})
test('work with named args', () => {
expect(`attach = do signal fn: [ signal (fn) ] end; attach signal='exit': true end`).toEvaluateTo(['exit', true])
})
test('work with dot-get', () => {
expect(`signals = [trap=do x y: [x (y)] end]; signals.trap 'EXIT': true end`).toEvaluateTo(['EXIT', true])
})
})
describe('multi line function blocks', () => {
test('work with no args', () => {
expect(`
trap = do x: x end
trap:
true
end`).toEvaluateTo(true)
})
test('work with one arg', () => {
expect(`
trap = do x y: [ x (y) ] end
trap EXIT:
true
end`).toEvaluateTo(['EXIT', true])
})
test('work with named args', () => {
expect(`
attach = do signal fn: [ signal (fn) ] end
attach signal='exit':
true
end`).toEvaluateTo(['exit', true])
})
test('work with dot-get', () => {
expect(`
signals = [trap=do x y: [x (y)] end]
signals.trap 'EXIT':
true
end`).toEvaluateTo(['EXIT', true])
})
})

View File

@ -0,0 +1,115 @@
import { expect, describe, test, beforeEach } from 'bun:test'
const buffer: string[] = []
const ribbitGlobals = {
ribbit: async (cb: Function) => {
await cb()
return buffer.join("\n")
},
tag: async (tagFn: Function, atDefaults = {}) => {
return (atNamed = {}, ...args: any[]) => tagFn(Object.assign({}, atDefaults, atNamed), ...args)
},
head: (atNamed: {}, ...args: any[]) => tag('head', atNamed, ...args),
title: (atNamed: {}, ...args: any[]) => tag('title', atNamed, ...args),
meta: (atNamed: {}, ...args: any[]) => tag('meta', atNamed, ...args),
p: (atNamed: {}, ...args: any[]) => tag('p', atNamed, ...args),
h1: (atNamed: {}, ...args: any[]) => tag('h1', atNamed, ...args),
h2: (atNamed: {}, ...args: any[]) => tag('h2', atNamed, ...args),
b: (atNamed: {}, ...args: any[]) => tag('b', atNamed, ...args),
ul: (atNamed: {}, ...args: any[]) => tag('ul', atNamed, ...args),
li: (atNamed: {}, ...args: any[]) => tag('li', atNamed, ...args),
nospace: () => NOSPACE_TOKEN,
echo: (...args: any[]) => console.log(...args)
}
function raw(fn: Function) { (fn as any).raw = true }
const tagBlock = async (tagName: string, props = {}, fn: Function) => {
const attrs = Object.entries(props).map(([key, value]) => `${key}="${value}"`)
const space = attrs.length ? ' ' : ''
buffer.push(`<${tagName}${space}${attrs.join(' ')}>`)
await fn()
buffer.push(`</${tagName}>`)
}
const tagCall = (tagName: string, atNamed = {}, ...args: any[]) => {
const attrs = Object.entries(atNamed).map(([key, value]) => `${key}="${value}"`)
const space = attrs.length ? ' ' : ''
const children = args
.reverse()
.map(a => a === TAG_TOKEN ? buffer.pop() : a)
.reverse().join(' ')
.replaceAll(` ${NOSPACE_TOKEN} `, '')
if (SELF_CLOSING.includes(tagName))
buffer.push(`<${tagName}${space}${attrs.join(' ')} />`)
else
buffer.push(`<${tagName}${space}${attrs.join(' ')}>${children}</${tagName}>`)
}
const tag = async (tagName: string, atNamed = {}, ...args: any[]) => {
if (typeof args[0] === 'function') {
await tagBlock(tagName, atNamed, args[0])
} else {
tagCall(tagName, atNamed, ...args)
return TAG_TOKEN
}
}
const NOSPACE_TOKEN = '!!ribbit-nospace!!'
const TAG_TOKEN = '!!ribbit-tag!!'
const SELF_CLOSING = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"]
describe('ribbit', () => {
beforeEach(() => buffer.length = 0)
test('head tag', () => {
expect(`
ribbit:
head:
title What up
meta charset=UTF-8
meta name=viewport content='width=device-width, initial-scale=1, viewport-fit=cover'
end
end
`).toEvaluateTo(`<head>
<title>What up</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
</head>`, ribbitGlobals)
})
test('custom tags', () => {
expect(`
list = tag ul class=list
ribbit:
list:
li border-bottom='1px solid black' one
li two
li three
end
end`).toEvaluateTo(`<ul class="list">
<li border-bottom="1px solid black">one</li>
<li>two</li>
<li>three</li>
</ul>`, ribbitGlobals)
})
test('inline expressions', () => {
expect(`
ribbit:
p class=container:
h1 class=bright style='font-family: helvetica' Heya
h2 man that is (b wild) (nospace) !
p Double the fun.
end
end`).toEvaluateTo(
`<p class="container">
<h1 class="bright" style="font-family: helvetica">Heya</h1>
<h2>man that is <b>wild</b>!</h2>
<p>Double the fun.</p>
</p>`, ribbitGlobals)
})
})

View File

@ -0,0 +1,48 @@
import { describe } from 'bun:test'
import { expect, test } from 'bun:test'
describe('while', () => {
test('basic variable', () => {
expect(`
a = true
b = ''
while a:
a = false
b = done
end
b`)
.toEvaluateTo('done')
})
test('basic expression', () => {
expect(`
a = 0
while a < 10:
a += 1
end
a`)
.toEvaluateTo(10)
})
test('compound expression', () => {
expect(`
a = 1
b = 0
while a > 0 and b < 100:
b += 1
end
b`)
.toEvaluateTo(100)
})
test('returns value', () => {
expect(`
a = 0
ret = while a < 10:
a += 1
done
end
ret`)
.toEvaluateTo('done')
})
})

View File

@ -210,7 +210,7 @@ export const getIfExprParts = (node: SyntaxNode, input: string) => {
} }
elseThenBlock = parts.at(-1) elseThenBlock = parts.at(-1)
} else if (child.type.id === terms.ElseIfExpr) { } else if (child.type.id === terms.ElseIfExpr) {
const [_keyword, conditional, _colon, thenBlock] = parts const [_else, _if, conditional, _colon, thenBlock] = parts
if (!conditional || !thenBlock) { if (!conditional || !thenBlock) {
const names = parts.map((p) => p.type.name).join(', ') const names = parts.map((p) => p.type.name).join(', ')
const message = `ElseIfExpr expected conditional and thenBlock, got ${names}` const message = `ElseIfExpr expected conditional and thenBlock, got ${names}`
@ -309,7 +309,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
export const getTryExprParts = (node: SyntaxNode, input: string) => { export const getTryExprParts = (node: SyntaxNode, input: string) => {
const children = getAllChildren(node) const children = getAllChildren(node)
// First child is always 'try' keyword, second is colon, third is TryBlock or statement // First child is always 'try' keyword, second is colon, third is Block
const [tryKeyword, _colon, tryBlock, ...rest] = children const [tryKeyword, _colon, tryBlock, ...rest] = children
if (!tryKeyword || !tryBlock) { if (!tryKeyword || !tryBlock) {

View File

@ -25,9 +25,18 @@
Underscore { "_" } Underscore { "_" }
Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar
"|"[@name=operator] "|"[@name=operator]
} }
end { @specialize[@name=keyword]<Identifier, "end"> }
while { @specialize[@name=keyword]<Identifier, "while"> }
if { @specialize[@name=keyword]<Identifier, "if"> }
else { @specialize[@name=keyword]<Identifier, "else"> }
try { @specialize[@name=keyword]<Identifier, "try"> }
catch { @specialize[@name=keyword]<Identifier, "catch"> }
finally { @specialize[@name=keyword]<Identifier, "finally"> }
throw { @specialize[@name=keyword]<Identifier, "throw"> }
null { @specialize[@name=Null]<Identifier, "null"> }
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot } @external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
@external specialize {Identifier} specializeKeyword from "./tokenizer" { Do } @external specialize {Identifier} specializeKeyword from "./tokenizer" { Do }
@ -47,9 +56,10 @@ item {
newlineOrSemicolon // allow blank lines newlineOrSemicolon // allow blank lines
} }
consumeToTerminator { consumeToTerminator {
PipeExpr | PipeExpr |
WhileExpr |
FunctionCallWithBlock |
ambiguousFunctionCall | ambiguousFunctionCall |
TryExpr | TryExpr |
Throw | Throw |
@ -70,6 +80,18 @@ pipeOperand {
FunctionCall | FunctionCallOrIdentifier FunctionCall | FunctionCallOrIdentifier
} }
WhileExpr {
while (ConditionalOp | expression) colon Block end
}
Block {
consumeToTerminator | newlineOrSemicolon block
}
FunctionCallWithBlock {
ambiguousFunctionCall colon Block CatchExpr? FinallyExpr? end
}
FunctionCallOrIdentifier { FunctionCallOrIdentifier {
DotGet | Identifier DotGet | Identifier
} }
@ -86,7 +108,6 @@ arg {
PositionalArg | NamedArg PositionalArg | NamedArg
} }
PositionalArg { PositionalArg {
expression | FunctionDef | Underscore expression | FunctionDef | Underscore
} }
@ -96,71 +117,35 @@ NamedArg {
} }
FunctionDef { FunctionDef {
singleLineFunctionDef | multilineFunctionDef Do Params colon (consumeToTerminator | newlineOrSemicolon block) CatchExpr? FinallyExpr? end
}
singleLineFunctionDef {
Do Params colon consumeToTerminator CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
}
multilineFunctionDef {
Do Params colon newlineOrSemicolon block CatchExpr? FinallyExpr? @specialize[@name=keyword]<Identifier, "end">
} }
IfExpr { IfExpr {
singleLineIf | multilineIf if (ConditionalOp | expression) colon Block ElseIfExpr* ElseExpr? end
}
singleLineIf {
@specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon SingleLineThenBlock @specialize[@name=keyword]<Identifier, "end">
}
multilineIf {
@specialize[@name=keyword]<Identifier, "if"> (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock ElseIfExpr* ElseExpr? @specialize[@name=keyword]<Identifier, "end">
} }
ElseIfExpr { ElseIfExpr {
@specialize[@name=keyword]<Identifier, "elseif"> (ConditionalOp | expression) colon newlineOrSemicolon ThenBlock else if (ConditionalOp | expression) colon Block
} }
ElseExpr { ElseExpr {
@specialize[@name=keyword]<Identifier, "else"> colon newlineOrSemicolon ThenBlock else colon Block
}
ThenBlock {
block
}
SingleLineThenBlock {
consumeToTerminator
} }
TryExpr { TryExpr {
singleLineTry | multilineTry try colon Block CatchExpr? FinallyExpr? end
}
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 { CatchExpr {
@specialize[@name=keyword]<Identifier, "catch"> Identifier colon (newlineOrSemicolon TryBlock | consumeToTerminator) catch Identifier colon Block
} }
FinallyExpr { FinallyExpr {
@specialize[@name=keyword]<Identifier, "finally"> colon (newlineOrSemicolon TryBlock | consumeToTerminator) finally colon Block
}
TryBlock {
block
} }
Throw { Throw {
@specialize[@name=keyword]<Identifier, "throw"> (BinOp | ConditionalOp | expression) throw (BinOp | ConditionalOp | expression)
} }
ConditionalOp { ConditionalOp {
@ -179,7 +164,7 @@ Params {
} }
NamedParam { NamedParam {
NamedArgPrefix (String | Number | Boolean | @specialize[@name=Null]<Identifier, "null">) NamedArgPrefix (String | Number | Boolean | null)
} }
Assign { Assign {
@ -217,7 +202,6 @@ expression {
} }
String { "'" stringContent* "'" } String { "'" stringContent* "'" }
} }
stringContent { stringContent {
@ -253,7 +237,7 @@ Array {
// to go through ambiguousFunctionCall (which is what we want semantically). // to go through ambiguousFunctionCall (which is what we want semantically).
// Yes, it is annoying and I gave up trying to use GLR to fix it. // Yes, it is annoying and I gave up trying to use GLR to fix it.
expressionWithoutIdentifier { expressionWithoutIdentifier {
ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | @specialize[@name=Null]<Identifier, "null"> ParenExpr | Word | String | Number | Boolean | Regex | Dict | Array | null
} }
block { block {

View File

@ -47,19 +47,19 @@ export const
Null = 45, Null = 45,
colon = 46, colon = 46,
CatchExpr = 47, CatchExpr = 47,
keyword = 69, keyword = 68,
TryBlock = 49, Block = 49,
FinallyExpr = 50, FinallyExpr = 50,
Underscore = 53, Underscore = 53,
Array = 54, Array = 54,
ConditionalOp = 55, ConditionalOp = 55,
PositionalArg = 56, PositionalArg = 56,
TryExpr = 58, WhileExpr = 58,
Throw = 60, FunctionCallWithBlock = 60,
IfExpr = 62, TryExpr = 61,
SingleLineThenBlock = 64, Throw = 63,
ThenBlock = 65, IfExpr = 65,
ElseIfExpr = 66, ElseIfExpr = 67,
ElseExpr = 68, ElseExpr = 69,
CompoundAssign = 70, CompoundAssign = 70,
Assign = 71 Assign = 71

View File

@ -4,14 +4,14 @@ 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,null:90, catch:96, finally:102, end:104, try:118, throw:122, if:126, elseif:134, else:138} const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, else:136}
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: ":QQYQbOOO#tQcO'#C{O$qOSO'#C}O%PQbO'#EhOOQ`'#DW'#DWOOQa'#DT'#DTO&VQbO'#DdO'hQcO'#E]OOQa'#E]'#E]O(kQcO'#E]O)mQcO'#E[O*QQRO'#C|O+^QcO'#EWO+nQcO'#EWO+xQbO'#CzO,pOpO'#CxOOQ`'#EX'#EXO,uQbO'#EWO,|QQO'#EnOOQ`'#Dh'#DhO-RQbO'#DjO-RQbO'#EpOOQ`'#Dl'#DlO-vQRO'#DtOOQ`'#EW'#EWO.[QQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO.dQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Em'#EmOOQ`'#EO'#EOO.nQbO,59cO/bQbO'#DPO/jQWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0OOSO,59iOOQa,59i,59iOOQ`'#Dy'#DyO0^QbO'#DXO0iQbO'#DYOOQO'#Dz'#DzO0aQQO'#DXO0wQQO,5;SOOQ`'#Dx'#DxO0|QbO,5:OO1TQQO,59oOOQa,5:O,5:OO1`QbO,5:OO1jQbO,5:aO-RQbO,59hO-RQbO,59hO-RQbO,59hO-RQbO,5:PO-RQbO,5:PO-RQbO,5:PO1zQRO,59fO2RQRO,59fO2dQRO,59fO2_QQO,59fO2oQQO,59fO2wObO,59dO3SQbO'#EPO3_QbO,59bO3vQbO,5;YO4ZQcO,5:UO5PQcO,5:UO5aQcO,5:UO6VQRO,5;[O6^QRO,5;[O1jQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7w-E7wO6iQQO,59sOOQO,59t,59tOOQO-E7x-E7xO6qQbO1G0nOOQ`-E7v-E7vO7UQQO1G/ZOOQa1G/j1G/jO7aQbO1G/jOOQO'#D|'#D|O7UQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O7aQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO8YQcO1G/SO8dQcO1G/SO8nQcO1G/SOOQa1G/k1G/kO:^QcO1G/kO:eQcO1G/kO:lQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!aQbO'#C{O:sQbO'#CwOOQ`,5:k,5:kOOQ`-E7}-E7}O;QQbO1G0tO;]QbO1G0uO;yQbO1G0vOOQ`1G/z1G/zO<^QbO7+&YO;]QbO7+&[O<iQQO7+$uOOQa7+$u7+$uO<tQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{O=OQbO'#D]O=TQQO'#D`OOQ`7+&`7+&`O=YQbO7+&`O=_QbO7+&`OOQ`'#D{'#D{O=gQQO'#D{O=lQbO'#EiOOQ`'#D_'#D_O>`QbO7+&aOOQ`'#Dn'#DnO>kQbO7+&bO>pQbO7+&cOOQ`<<It<<ItO?^QbO<<ItO?cQbO<<ItO?kQbO<<IvOOQa<<Ha<<HaOOQa<<Hp<<HpO?vQQO,59wO?{QbO,59zOOQ`<<Iz<<IzO@`QbO<<IzOOQ`,5:g,5:gOOQ`-E7y-E7yOOQ`<<I{<<I{O@eQbO<<I{O@jQbO<<I{OOQ`<<I|<<I|OOQ`'#Do'#DoO@rQbO<<I}OOQ`AN?`AN?`O@}QbOAN?`OOQ`AN?bAN?bOASQbOAN?bOAXQbOAN?bOAaQbO1G/cOAtQbO1G/fOOQ`1G/f1G/fOOQ`AN?fAN?fOOQ`AN?gAN?gOB[QbOAN?gO-RQbO'#DpOOQ`'#EQ'#EQOBaQbOAN?iOBlQQO'#DrOOQ`AN?iAN?iOBqQbOAN?iOOQ`G24zG24zOOQ`G24|G24|OBvQbOG24|OB{QbO7+$}OOQ`7+$}7+$}OOQ`7+%Q7+%QOOQ`G25RG25ROCfQRO,5:[OCmQRO,5:[OOQ`-E8O-E8OOOQ`G25TG25TOCxQbOG25TOC}QQO,5:^OOQ`LD*hLD*hOOQ`<<Hi<<HiODSQQO1G/vOOQ`LD*oLD*oOAtQbO1G/xO>pQbO7+%bOOQ`7+%d7+%dOOQ`<<H|<<H|", states: "9OQYQbOOO#zQcO'#C{O$zOSO'#C}OOQa'#DT'#DTO&TQbO'#DdO'iQcO'#E]OOQa'#E]'#E]O(lQcO'#E]O)nQcO'#E[O*UQRO'#C|O+eQcO'#EWO+uQcO'#EWO,PQbO'#CzO,wOpO'#CxOOQ`'#EX'#EXO,|QbO'#EWO-WQRO'#DtOOQ`'#EW'#EWO-lQQO'#EVOOQ`'#EV'#EVOOQ`'#Dv'#DvQYQbOOO-tQbO'#DWO.PQbO'#DhO.tQQO'#DkO.PQbO'#DmO.PQbO'#DoO.yQbO'#DUOOQa'#E['#E[OOQ`'#Df'#DfOOQ`'#Ek'#EkOOQ`'#EO'#EOO/TQbO,59cO/}QbO'#DPO0VQWO'#DQOOOO'#E_'#E_OOOO'#Dw'#DwO0kOSO,59iOOQa,59i,59iOOQ`'#Dx'#DxO0yQbO,5:OO1QQQO,59oOOQa,5:O,5:OO1]QbO,5:OO1gQbO,5:aO.PQbO,59hO.PQbO,59hO.PQbO,59hO.PQbO,5:PO.PQbO,5:PO.PQbO,5:PO1zQRO,59fO2RQRO,59fO2dQRO,59fO2_QQO,59fO2oQQO,59fO2wObO,59dO3SQbO'#EPO3_QbO,59bO3yQbO,5:UO1gQbO,5:`OOQ`,5:q,5:qOOQ`-E7t-E7tOOQ`'#Dy'#DyO4aQbO'#DXO4lQbO'#DYOOQO'#Dz'#DzO4dQQO'#DXO4zQQO,59rO5kQRO,5:SO5rQRO,5:SO3yQbO,5:VO5}QcO,5:XO6yQcO,5:XO7ZQcO,5:XO7eQRO,5:ZO7lQRO,5:ZOOQ`,59p,59pOOQ`-E7|-E7|OOOO,59k,59kOOOO,59l,59lOOOO-E7u-E7uOOQa1G/T1G/TOOQ`-E7v-E7vO7wQQO1G/ZOOQa1G/j1G/jO8SQbO1G/jOOQO'#D|'#D|O7wQQO1G/ZOOQa1G/Z1G/ZOOQ`'#D}'#D}O8SQbO1G/jOOQ`1G/{1G/{OOQa1G/S1G/SO9OQcO1G/SO9YQcO1G/SO9dQcO1G/SOOQa1G/k1G/kO;YQcO1G/kO;aQcO1G/kO;hQcO1G/kOOQa1G/Q1G/QOOQa1G/O1G/OO!dQbO'#C{O;oQbO'#CwOOQ`,5:k,5:kOOQ`-E7}-E7}OOQ`'#D_'#D_O;|QbO'#D_O<pQbO1G/pOOQ`1G/z1G/zOOQ`-E7w-E7wO<{QQO,59sOOQO,59t,59tOOQO-E7x-E7xO=TQbO1G/^O3yQbO1G/nO=kQbO1G/qO3yQbO1G/uO=vQQO7+$uOOQa7+$u7+$uO>RQbO7+%UOOQa7+%U7+%UOOQO-E7z-E7zOOQ`-E7{-E7{OOQ`'#D{'#D{O>]QQO'#D{O>bQbO'#EhOOQ`,59y,59yO?UQbO'#D]O?ZQQO'#D`OOQ`7+%[7+%[O?`QbO7+%[O?eQbO7+%[O?mQbO7+$xO?xQbO7+$xO@iQbO7+%YOOQ`7+%]7+%]O@nQbO7+%]O@sQbO7+%]O@{QbO7+%aOOQa<<Ha<<HaOOQa<<Hp<<HpOOQ`,5:g,5:gOOQ`-E7y-E7yOATQQO,59wO3yQbO,59zOOQ`<<Hv<<HvOAYQbO<<HvOOQ`<<Hd<<HdOA_QbO<<HdOAdQbO<<HdOAlQbO<<HdOOQ`<<Ht<<HtOOQ`<<Hw<<HwOAwQbO<<HwOOQ`'#EQ'#EQOA|QbO<<H{OBUQbO'#DsOOQ`<<H{<<H{OB^QbO<<H{O3yQbO1G/cOOQ`1G/f1G/fOOQ`AN>bAN>bOOQ`AN>OAN>OOBcQbOAN>OOBhQbOAN>OOOQ`AN>cAN>cOOQ`-E8O-E8OOOQ`AN>gAN>gOBpQbOAN>gO.PQbO,5:]O3yQbO,5:_OOQ`7+$}7+$}OOQ`G23jG23jOBuQbOG23jPBXQbO'#DqOOQ`G24RG24ROBzQRO1G/wOCRQRO1G/wOOQ`1G/y1G/yOOQ`LD)ULD)UO3yQbO7+%cOOQ`<<H}<<H}",
stateData: "D[~O!wOS!xOS~OdPOegOfWOg_OhROmWOuWOvWO}WO!]bO!_dO!aeO!}^O#QQO#XTO#YUO#ZjO~OdnOfWOg_OhROmWOuWOvWOymO}WO!VoO!}^O#QQO#XTO#YUO!ZoX#ZoX#foX#`oX!QoX!ToX!UoX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~P!aOruO#QxO#SsO#TtO~OdyOy{O!O{P~OdnOfWOg_OmWOuWOvWOymO}WO!}^O#QQO#XTO#YUO#Z!PO~O#_!SO~P%[OP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#f#PX!Q#PX!T#PX!U#PX~OdnOfWOg_OhROmWOuWOvWOymO}WO!VoO!}^O#QQO#XTO#YUO#`#PX~P&^OV!UO~P&^OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#f!zX!Q!zX!T!zX!U!zX~P(rOP!WOQ!WOR!XOS!XOT!ZOU![OW!YOX!YOY!YOZ!YO[!YO]!YO^!VO~O#Z!zX#f!zX!Q!zX!T!zX!U!zX~OP!WOQ!WOR!XOS!XO~P*{OT!ZOU![O~P*{OdPOfWOg_OhROmWOuWOvWO}WO!}^O#QQO#XTO#YUO~O!|!bO~O!Z!cO~P*{O!O!eO~OdnOfWOg_OmWOuWOvWO}WO!}^O#QQO#XTO#YUO~OV!UO_!kO`!kOa!kOb!kOc!kO~O#Z!lO#f!lO~OhRO!V!nO~P-ROhROymO!VoO!Zka#Zka#fka#`ka!Qka!Tka!Uka~P-ROd!pO!}^O~O#Q!qO#S!qO#T!qO#U!qO#V!qO#W!qO~OruO#Q!sO#SsO#TtO~OdyOy{O!O{X~Om!vOu!vO}!vO#QQO~O!O!xO~O#_!{O~P%[OymO#Z!}O#_#PO~O#Z#QO#_!{O~P-ROegO!]bO!_dO!aeO~P+xO#`#]O~P(rOP!WOQ!WOR!XOS!XO#`#]O~OT!ZOU![O#`#]O~O!Z!cO#`#]O~Od#^Om#^O!}^O~Od#_Og_O!}^O~O!Z!cO#Zja#fja#`ja!Qja!Tja!Uja~OegO!]bO!_dO!aeO#Z#dO~P+xO#Z!^a#f!^a!Q!^a!T!^a!U!^a~P*QO#Z!^a#f!^a!Q!^a!T!^a!U!^a~OP!WOQ!WOR!XOS!XO~P4nOT!ZOU![O~P4nOT!ZOU![OW!YOX!YOY!YOZ!YO[!YO]!YO~O!O#eO~P5kOT!ZOU![O!O#eO~Oy{O!O{a~OegO!]bO!_dO!aeO#Z#hO~P+xOymO#Z!}O#_#jO~O#Z#QO#_#lO~P-RO^!VORpiSpi#Zpi#fpi#`pi!Qpi!Tpi!Upi~OPpiQpi~P7kOP!WOQ!WO~P7kOP!WOQ!WORpiSpi#Zpi#fpi#`pi!Qpi!Tpi!Upi~OW!YOX!YOY!YOZ!YO[!YO]!YOT!Xi#Z!Xi#f!Xi#`!Xi!O!Xi!Q!Xi!T!Xi!U!Xi~OU![O~P9`OU![O~P9rOU!Xi~P9`OhROymO!VoO~P-RO!Q#oO!T#pO!U#qO~OegO!]bO!_dO!aeO#Z#tO!Q#]P!T#]P!U#]P~P+xOegO!]bO!_dO!aeO#Z#{O~P+xO!Q#oO!T#pO!U#|O~OymO#Z!}O#_$QO~O#Z#QO#_$RO~P-ROd$SO~O!O$TO~O!U$UO~O!T#pO!U$UO~O#Z$WO~OegO!]bO!_dO!aeO#Z#tO!Q#]X!T#]X!U#]X!e#]X!g#]X~P+xO!Q#oO!T#pO!U$YO~O!U$]O~OegO!]bO!_dO!aeO#Z#tO!U#]P!e#]P!g#]P~P+xO!U$`O~O!T#pO!U$`O~O!Q#oO!T#pO!U$bO~O!O$eO~OegO!]bO!_dO!aeO#Z$fO~P+xO!U$hO~O!U$iO~O!T#pO!U$iO~O!U$oO!e$kO!g$nO~O!U$qO~O!U$rO~O!T#pO!U$rO~OegO!]bO!_dO!aeO#Z$tO~P+xOegO!]bO!_dO!aeO#Z#tO!U#]P~P+xO!U$wO~O!U${O!e$kO!g$nO~O!O$}O~O!U${O~O!U%OO~OegO!]bO!_dO!aeO#Z#tO!T#]P!U#]P~P+xO!O%QO~P5kOT!ZOU![O!O%QO~O!U%RO~O#Z%SO~O#Z%TO~Omv~", stateData: "Ca~O!wOS!xOS~OdPOe`OfUOg]OhfOmUOuUOvUO}UO!]gO!`hO!biO!djO!}[O#QQO#XRO#YSO#ZcO~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO!ZoX#ZoX#`oX#^oX!QoX!ToX!UoX!foX~OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX!OoX~P!dOrsO#QvO#SqO#TrO~OdlOfUOg]OmUOuUOvUOykO}UO!}[O#QQO#XRO#YSO#ZwO~O#]zO~P%YOP#PXQ#PXR#PXS#PXT#PXU#PXW#PXX#PXY#PXZ#PX[#PX]#PX^#PX#Z#PX#`#PX!Q#PX!T#PX!U#PX!f#PX~OdlOfUOg]OhfOmUOuUOvUOykO}UO!VmO!}[O#QQO#XRO#YSO#^#PX~P&[OV|O~P&[OP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OX~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!f!zX~P(sOP!OOQ!OOR!POS!POT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO^}O~O#Z!zX#`!zX!Q!zX!T!zX!U!zX!f!zX~OP!OOQ!OOR!POS!PO~P+POT!ROU!SO~P+POdPOfUOg]OhfOmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!|!YO~O!O!]O!Z!ZO~P+POV|O_!^O`!^Oa!^Ob!^Oc!^O~O#Z!_O#`!_O~Od!aOy!cO!O{P~OdlOfUOg]OmUOuUOvUO}UO!}[O#QQO#XRO#YSO~O!O!iO~OhfO!V!oO~P.POhfOykO!VmO!Oka!Zka#Zka#`ka#^ka!Qka!Tka!Uka!fka~P.POd!qO!}[O~O#Q!rO#S!rO#T!rO#U!rO#V!rO#W!rO~OrsO#Q!tO#SqO#TrO~O#]!wO~P%YOykO#Z!yO#]!{O~O#Z!|O#]!wO~P.POe`O!]gO!`hO!biO!djO~P,PO#^#XO~P(sOP!OOQ!OOR!POS!PO#^#XO~OT!ROU!SO#^#XO~O!Z!ZO#^#XO~Od#YOm#YO!}[O~Od#ZOg]O!}[O~O!Z!ZO#Zja#`ja#^ja!Qja!Tja!Uja!fja~Oe`O!]gO!`hO!biO!djO#Z#`O~P,POd!aOy!cO!O{X~Om#eOu#eO}#eO#QQO~O!O#gO~OT!ROU!SOW!QOX!QOY!QOZ!QO[!QO]!QO~O!O#hO~P5POT!ROU!SO!O#hO~O#Z!aa#`!aa!Q!aa!T!aa!U!aa!f!aa~P*UO#Z!aa#`!aa!Q!aa!T!aa!U!aa!f!aa~OP!OOQ!OOR!POS!PO~P6eOT!ROU!SO~P6eO!O#jO~P5POT!ROU!SO!O#jO~OykO#Z!yO#]#lO~O#Z!|O#]#nO~P.PO^}ORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!fpi~OPpiQpi~P8^OP!OOQ!OO~P8^OP!OOQ!OORpiSpi#Zpi#`pi#^pi!Qpi!Tpi!Upi!fpi~OW!QOX!QOY!QOZ!QO[!QO]!QOT!Xi#Z!Xi#`!Xi#^!Xi!O!Xi!Q!Xi!T!Xi!U!Xi!f!Xi~OU!SO~P:XOU!SO~P:kOU!Xi~P:XOhfOykO!VmO~P.POe`O!]gO!`hO!biO!djO#Z#qO!Q#[P!T#[P!U#[P!f#[P~P,PO!Q#uO!T#vO!U#wO~Oy!cO!O{a~Oe`O!]gO!`hO!biO!djO#Z#{O~P,PO!Q#uO!T#vO!U#}O~OykO#Z!yO#]$RO~O#Z!|O#]$SO~P.PO#Z$TO~Oe`O!]gO!`hO!biO!djO#Z#qO!Q#[X!T#[X!U#[X!f#[X~P,POd$VO~O!O$WO~O!U$XO~O!T#vO!U$XO~O!Q#uO!T#vO!U$ZO~Oe`O!]gO!`hO!biO!djO#Z#qO!Q#[P!T#[P!U#[P~P,PO!U$_O~O!U$`O~O!T#vO!U$`O~O!U$eO!f$dO~O!O$gO~O!U$iO~O!U$jO~O!T#vO!U$jO~O!Q#uO!T#vO!U$jO~O!U$mO~O!U$oO!f$dO~O!O$rO!d$qO~O!U$oO~O!U$tO~O!T#vO!U$tO~O!U$wO~O!U${O~O!O$|O~P5POT!ROU!SO!O$|O~Omv~",
goto: "5{#fPPPPPPPPPPPPPPPPPPPPPPPPPP#g#}$dP%d#}&j'ZP(X(XPP(])WP)k*]*`PP*fP*r*{PPP+e,b-XP-`P-`P-`P-s-v.PP.TP-`-`.Z.a.g.m.s.}/Z/e/o/x0PPPPP0V0Z1OPP1i3QP4PPPPPPPPP4T4o4TPP4|5T5T5h5hrhOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TR!`^w`O^l!U!c!e!k!x#d#e#h#v#{$T$e$f$t%S%TtPO^l!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TznPUVdemr!Q!T!V!W!X!Y!Z![!|#R#_#`#k$kR#_!ctVO^l!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TzWPUVdemr!Q!T!V!W!X!Y!Z![!|#R#_#`#k$kQ!psQ#^!bR#`!cr[Ol!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TQ!^^Q!gdQ#T!WR#W!X!pWOPUV^delmr!Q!T!U!V!W!X!Y!Z![!e!k!x!|#R#_#`#d#e#h#k#v#{$T$e$f$k$t%S%TR!v{TuQw!qWOPUV^delmr!Q!T!U!V!W!X!Y!Z![!e!k!x!|#R#_#`#d#e#h#k#v#{$T$e$f$k$t%S%TYpPVr#_#`Q!RUQ!z!QX!}!R!z#O#irhOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TYoPVr#_#`Q!`^R!nmR!ORX|Rz}!uQ#s#cQ$O#gQ$[#xR$d$PQ#x#dQ$v$fR%P$tQ#r#cQ#}#gQ$V#sQ$Z#xQ$a$OQ$c$PQ$j$[R$s$d|WPUV^demr!Q!T!V!W!X!Y!Z![!|#R#_#`#k$ksXOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%Tr]Ol!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TQ!_^Q!hdQ!jeQ#X![Q#Z!ZR$y$kZpPVr#_#`shOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TR#z#eQ$_#{Q%U%SR%V%TT$l$_$mQ$p$_R$|$mQlOR!mlQwQR!rwQ!QUR!y!QQzRR!tzQ}RQ!uzT!w}!u^#v#d#h#{$f$t%S%TR$X#vQ#O!RQ#i!zT#m#O#iQ#R!TQ#k!|T#n#R#kWrPV#_#`R!orS!da!aR#b!dQ$m$_R$z$mTkOlSiOlQ#S!UQ#c!eQ#f!kQ#g!x`#u#d#h#v#{$f$t%S%TQ#y#eQ$g$TR$u$eraOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TQ!a^R#a!ctZO^l!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TYoPVr#_#`Q!TUQ!fdQ!ieQ!nmQ!|!QW#Q!T!|#R#kQ#T!VQ#U!WQ#V!XQ#X!YQ#Y!ZQ#[![R$x$krYOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TznPUVdemr!Q!T!V!W!X!Y!Z![!|#R#_#`#k$kR!]^TvQw!RSOPV^lmr!U!e!k!x#_#`#d#e#h#v#{$T$e$f$t%S%TU#w#d$f$tQ$P#hV$^#{%S%TZqPVr#_#`scOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%TsfOl!U!e!k!x#d#e#h#v#{$T$e$f$t%S%T", goto: "3}#`PPPPPPPPPPPPPPPPPPPPPPPPPP#a#v$[P%[#v&b'QP(O(OPP(S(}P)b*R*UPP*[P*h+QPPP+h,e-^P-eP-e-eP-eP-eP-wP-{-e-e.R.X._.e.k.u.|/W/b/k/rPPPP/x/|0jPP1S2mP3lPPPPPPPP3pPP3vpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|R!W[u^O[e|!Z!]!^!i#`#g#h#j#s#{$W$g$r$|rPO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR#Z!ZrTO[e|!]!^!i#`#g#h#j#s#{$W$g$r$||UPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qQ!qqQ#Y!YR#[!ZpYOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!U[Q!kiQ#P!OR#S!P!pUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|R#e!cTsQu!qUOPST[egijkpx{|}!O!P!Q!R!S!]!^!i!x!}#Z#[#`#g#h#j#m#s#{$W$g$q$r$|YnPTp#Z#[QySQ!vxX!yy!v!z#kpaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q!W[R!okR!ffX!df!b!e#dQ#y#aQ$P#iQ$]#zR$l$^Q#a!]Q#i!iQ#|#hQ$Q#jQ$h$WQ$s$gQ$z$rR$}$|Q#x#aQ$O#iQ$Y#yQ$[#zQ$a$PS$k$]$^R$u$l!OUPST[gijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qqVOe|!]!^!i#`#g#h#j#s#{$W$g$r$|pZOe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!V[Q!hgQ!liQ!njQ#T!SQ#V!RR$y$qZnPTp#Z#[qaOe|!]!^!i#`#g#h#j#s#{$W$g$r$|T$b$Q$cQ$f$QR$p$cQeOR!`eQuQR!suQxSR!uxQ!bfR#c!bQ!efQ#d!bT#f!e#dS#s#`#{R$U#sQ!zyQ#k!vT#o!z#kQ!}{Q#m!xT#p!}#mWpPT#Z#[R!ppS![_!XR#^![Q$c$QR$n$cTdOeSbOeQ#O|`#_!]!i#h#j$W$g$r$|Q#b!^U#r#`#s#{R#z#gp_Oe|!]!^!i#`#g#h#j#s#{$W$g$r$|Q!X[R#]!ZrXO[e|!]!^!i#`#g#h#j#s#{$W$g$r$|YmPTp#Z#[Q{SQ!ggQ!jiQ!mjQ!okQ!xxW!|{!x!}#mQ#P}Q#Q!OQ#R!PQ#T!QQ#U!RQ#W!SR$x$qpWOe|!]!^!i#`#g#h#j#s#{$W$g$r$||lPSTgijkpx{}!O!P!Q!R!S!x!}#Z#[#m$qR!T[TtQuQ#t#`R$^#{ZoPTp#Z#[",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword TryBlock FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator TryExpr keyword Throw keyword IfExpr keyword SingleLineThenBlock ThenBlock ElseIfExpr keyword ElseExpr keyword CompoundAssign Assign", nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Program PipeExpr FunctionCall DotGet Number ParenExpr FunctionCallOrIdentifier BinOp String StringFragment Interpolation EscapeSeq Boolean Regex Dict NamedArg NamedArgPrefix FunctionDef Params NamedParam Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword FunctionCallWithBlock TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr CompoundAssign Assign",
maxTerm: 114, maxTerm: 108,
context: trackScope, context: trackScope,
nodeProps: [ nodeProps: [
["closedBy", 46,"end"] ["closedBy", 46,"end"]
@ -19,9 +19,9 @@ export const parser = LRParser.deserialize({
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0], skippedNodes: [0],
repeatNodeCount: 11, repeatNodeCount: 11,
tokenData: "C_~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O+X!O!P#{!P!Q-n!Q![)S![!]6Z!]!^%T!^!}#{!}#O6t#O#P8j#P#Q8o#Q#R#{#R#S9Y#S#T#{#T#Y,Y#Y#Z9s#Z#b,Y#b#c>q#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#`QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#_QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#h<s#h#o,Y#o;'S#{;'S;=`$d<%lO#{U<x^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#X,Y#X#Y=t#Y#o,Y#o;'S#{;'S;=`$d<%lO#{U={[uQrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^>x[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j<s#j#o,Y#o;'S#{;'S;=`$d<%lO#{UBvU!ZQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C_O#f~", tokenData: "C_~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O+X!O!P#{!P!Q-n!Q![)S![!]6Z!]!^%T!^!}#{!}#O6t#O#P8j#P#Q8o#Q#R#{#R#S9Y#S#T#{#T#Y,Y#Y#Z9s#Z#b,Y#b#c>q#c#f,Y#f#g?n#g#h,Y#h#i@k#i#o,Y#o#p#{#p#qBo#q;'S#{;'S;=`$d<%l~#{~O#{~~CYS$QUrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUrS!wYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UrS#ZQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZrS!xYOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mS!xYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#S~~'aO#Q~U'hUrS!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUrS#^QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWrSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZYrSmQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OWrSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oWrSmQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^rSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UyQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sWrSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^rSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^rSvQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fXvQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^UvQ#Z#[1X#]#^1X#a#b1X#g#h1X#i#j1X#m#n1XQ1sVOY1pZ#O1p#O#P2Y#P#Q0a#Q;'S1p;'S;=`2i<%lO1pQ2]SOY1pZ;'S1p;'S;=`2i<%lO1pQ2lP;=`<%l1pQ2rSOY0aZ;'S0a;'S;=`3O<%lO0aQ3RP;=`<%l0aU3ZWrSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zbrSvQOt#{uw#{x#O#{#P#Z#{#Z#[3s#[#]#{#]#^3s#^#a#{#a#b3s#b#g#{#g#h3s#h#i#{#i#j3s#j#m#{#m#n3s#n;'S#{;'S;=`$d<%lO#{U5X[rSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bUrS!OQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#YQrSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jVrSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#XQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#T~U8vU#]QrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aUrS!VQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#g,Y#g#h<s#h#o,Y#o;'S#{;'S;=`$d<%lO#{U<x^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#X,Y#X#Y=t#Y#o,Y#o;'S#{;'S;=`$d<%lO#{U={[uQrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^>x[#UWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#WWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#VWrSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^rSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j<s#j#o,Y#o;'S#{;'S;=`$d<%lO#{UBvU!ZQrSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C_O#`~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!|~~", 11)], tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!|~~", 11)],
topRules: {"Program":[0,25]}, topRules: {"Program":[0,25]},
specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 1619 tokenPrec: 1578
}) })

View File

@ -752,7 +752,7 @@ Assign
EqEq == EqEq ==
Number 5 Number 5
colon : colon :
ThenBlock Block
Boolean true Boolean true
keyword end keyword end
keyword end keyword end
@ -794,7 +794,7 @@ Assign
EqEq == EqEq ==
Number 5 Number 5
colon : colon :
ThenBlock Block
Boolean true Boolean true
keyword end keyword end
keyword end keyword end

View File

@ -12,7 +12,7 @@ describe('if/elseif/else', () => {
EqEq == EqEq ==
Number 1 Number 1
colon : colon :
SingleLineThenBlock Block
String String
StringFragment cool StringFragment cool
keyword end keyword end
@ -26,7 +26,7 @@ describe('if/elseif/else', () => {
keyword if keyword if
Identifier x Identifier x
colon : colon :
SingleLineThenBlock Block
Number 2 Number 2
keyword end keyword end
`) `)
@ -44,7 +44,7 @@ describe('if/elseif/else', () => {
Lt < Lt <
Number 9 Number 9
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier yes Identifier yes
keyword end keyword end
@ -61,13 +61,13 @@ describe('if/elseif/else', () => {
keyword if keyword if
Identifier with-else Identifier with-else
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
ElseExpr ElseExpr
keyword else keyword else
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier y Identifier y
keyword end keyword end
@ -75,23 +75,24 @@ describe('if/elseif/else', () => {
}) })
test('parses multiline if with else if', () => { test('parses multiline if with else if', () => {
expect(`if with-elseif: expect(`if with-else-if:
x x
else if another-condition: else if another-condition:
y y
end`).toMatchTree(` end`).toMatchTree(`
IfExpr IfExpr
keyword if keyword if
Identifier with-elseif Identifier with-else-if
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
ElseIfExpr ElseIfExpr
keyword elseif keyword else
keyword if
Identifier another-condition Identifier another-condition
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier y Identifier y
keyword end keyword end
@ -99,7 +100,7 @@ describe('if/elseif/else', () => {
}) })
test('parses multiline if with multiple else if and else', () => { test('parses multiline if with multiple else if and else', () => {
expect(`if with-elseif-else: expect(`if with-else-if-else:
x x
else if another-condition: else if another-condition:
y y
@ -110,29 +111,31 @@ describe('if/elseif/else', () => {
end`).toMatchTree(` end`).toMatchTree(`
IfExpr IfExpr
keyword if keyword if
Identifier with-elseif-else Identifier with-else-if-else
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier x Identifier x
ElseIfExpr ElseIfExpr
keyword elseif keyword else
keyword if
Identifier another-condition Identifier another-condition
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier y Identifier y
ElseIfExpr ElseIfExpr
keyword elseif keyword else
keyword if
Identifier yet-another-condition Identifier yet-another-condition
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier z Identifier z
ElseExpr ElseExpr
keyword else keyword else
colon : colon :
ThenBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier oh-no Identifier oh-no
keyword end keyword end
@ -148,9 +151,124 @@ describe('if/elseif/else', () => {
keyword if keyword if
Boolean true Boolean true
colon : colon :
SingleLineThenBlock Block
Number 2 Number 2
keyword end keyword end
`) `)
}) })
}) })
describe('while', () => {
test('infinite loop', () => {
expect(`while true: true end`).toMatchTree(`
WhileExpr
keyword while
Boolean true
colon :
Block
Boolean true
keyword end`)
})
test('basic expression', () => {
expect(`while a > 0: true end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
Identifier a
Gt >
Number 0
colon :
Block
Boolean true
keyword end`)
})
test('compound expression', () => {
expect(`while a > 0 and b < 100 and c < 1000: true end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
ConditionalOp
ConditionalOp
Identifier a
Gt >
Number 0
And and
ConditionalOp
Identifier b
Lt <
Number 100
And and
ConditionalOp
Identifier c
Lt <
Number 1000
colon :
Block
Boolean true
keyword end`)
})
test('multiline infinite loop', () => {
expect(`
while true:
true
end`).toMatchTree(`
WhileExpr
keyword while
Boolean true
colon :
Block
Boolean true
keyword end`)
})
test('multiline basic expression', () => {
expect(`
while a > 0:
true
end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
Identifier a
Gt >
Number 0
colon :
Block
Boolean true
keyword end`)
})
test('multiline compound expression', () => {
expect(`
while a > 0 and b < 100 and c < 1000:
true
end`).toMatchTree(`
WhileExpr
keyword while
ConditionalOp
ConditionalOp
ConditionalOp
Identifier a
Gt >
Number 0
And and
ConditionalOp
Identifier b
Lt <
Number 100
And and
ConditionalOp
Identifier c
Lt <
Number 1000
colon :
Block
Boolean true
keyword end`)
})
})

View File

@ -12,14 +12,14 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier risky-operation Identifier risky-operation
CatchExpr CatchExpr
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
TryBlock Block
FunctionCall FunctionCall
Identifier handle-error Identifier handle-error
PositionalArg PositionalArg
@ -37,13 +37,13 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier do-work Identifier do-work
FinallyExpr FinallyExpr
keyword finally keyword finally
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier cleanup Identifier cleanup
keyword end keyword end
@ -61,14 +61,14 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier risky-operation Identifier risky-operation
CatchExpr CatchExpr
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
TryBlock Block
FunctionCall FunctionCall
Identifier handle-error Identifier handle-error
PositionalArg PositionalArg
@ -76,7 +76,7 @@ describe('try/catch/finally/throw', () => {
FinallyExpr FinallyExpr
keyword finally keyword finally
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier cleanup Identifier cleanup
keyword end keyword end
@ -91,6 +91,7 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
Block
FunctionCall FunctionCall
Identifier parse-number Identifier parse-number
PositionalArg PositionalArg
@ -99,6 +100,7 @@ describe('try/catch/finally/throw', () => {
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
Block
Number 0 Number 0
keyword end keyword end
`) `)
@ -109,16 +111,19 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier work Identifier work
CatchExpr CatchExpr
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
Block
Number 0 Number 0
FinallyExpr FinallyExpr
keyword finally keyword finally
colon : colon :
Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier cleanup Identifier cleanup
keyword end keyword end
@ -164,12 +169,14 @@ describe('try/catch/finally/throw', () => {
TryExpr TryExpr
keyword try keyword try
colon : colon :
Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier work Identifier work
CatchExpr CatchExpr
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
Block
Number 0 Number 0
keyword end keyword end
`) `)
@ -199,7 +206,7 @@ describe('function-level exception handling', () => {
keyword catch keyword catch
Identifier e Identifier e
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier empty-string Identifier empty-string
keyword end keyword end
@ -227,7 +234,7 @@ describe('function-level exception handling', () => {
FinallyExpr FinallyExpr
keyword finally keyword finally
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier close-resources Identifier close-resources
keyword end keyword end
@ -259,7 +266,7 @@ describe('function-level exception handling', () => {
keyword catch keyword catch
Identifier err Identifier err
colon : colon :
TryBlock Block
FunctionCall FunctionCall
Identifier log Identifier log
PositionalArg PositionalArg
@ -269,7 +276,7 @@ describe('function-level exception handling', () => {
FinallyExpr FinallyExpr
keyword finally keyword finally
colon : colon :
TryBlock Block
FunctionCallOrIdentifier FunctionCallOrIdentifier
Identifier cleanup Identifier cleanup
keyword end keyword end

View File

@ -0,0 +1,303 @@
import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('single line function blocks', () => {
test('work with no args', () => {
expect(`trap: echo bye bye end`).toMatchTree(`
FunctionCallWithBlock
FunctionCallOrIdentifier
Identifier trap
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with one arg', () => {
expect(`trap EXIT: echo bye bye end`).toMatchTree(`
FunctionCallWithBlock
FunctionCall
Identifier trap
PositionalArg
Word EXIT
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with named args', () => {
expect(`attach signal='exit': echo bye bye end`).toMatchTree(`
FunctionCallWithBlock
FunctionCall
Identifier attach
NamedArg
NamedArgPrefix signal=
String
StringFragment exit
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with dot-get', () => {
expect(`signals = [=]; signals.trap 'EXIT': echo bye bye end`).toMatchTree(`
Assign
AssignableIdentifier signals
Eq =
Dict [=]
FunctionCallWithBlock
FunctionCall
DotGet
IdentifierBeforeDot signals
Identifier trap
PositionalArg
String
StringFragment EXIT
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
})
describe('multi line function blocks', () => {
test('work with no args', () => {
expect(`
trap:
echo bye bye
end
`).toMatchTree(`
FunctionCallWithBlock
FunctionCallOrIdentifier
Identifier trap
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with one arg', () => {
expect(`
trap EXIT:
echo bye bye
end`).toMatchTree(`
FunctionCallWithBlock
FunctionCall
Identifier trap
PositionalArg
Word EXIT
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with named args', () => {
expect(`
attach signal='exit' code=1:
echo bye bye
end`).toMatchTree(`
FunctionCallWithBlock
FunctionCall
Identifier attach
NamedArg
NamedArgPrefix signal=
String
StringFragment exit
NamedArg
NamedArgPrefix code=
Number 1
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
test('work with dot-get', () => {
expect(`
signals = [=]
signals.trap 'EXIT':
echo bye bye
end`).toMatchTree(`
Assign
AssignableIdentifier signals
Eq =
Dict [=]
FunctionCallWithBlock
FunctionCall
DotGet
IdentifierBeforeDot signals
Identifier trap
PositionalArg
String
StringFragment EXIT
colon :
Block
FunctionCall
Identifier echo
PositionalArg
Identifier bye
PositionalArg
Identifier bye
keyword end`
)
})
})
describe('ribbit', () => {
test('head tag', () => {
expect(`
head:
title What up
meta charSet=UTF-8
meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'
end`).toMatchTree(`
FunctionCallWithBlock
FunctionCallOrIdentifier
Identifier head
colon :
Block
FunctionCall
Identifier title
PositionalArg
Word What
PositionalArg
Identifier up
FunctionCall
Identifier meta
PositionalArg
Word charSet=UTF-8
FunctionCall
Identifier meta
NamedArg
NamedArgPrefix name=
String
StringFragment viewport
NamedArg
NamedArgPrefix content=
String
StringFragment width=device-width, initial-scale=1, viewport-fit=cover
keyword end
`)
})
test('li', () => {
expect(`
list:
li border-bottom='1px solid black' one
li two
li three
end`).toMatchTree(`
FunctionCallWithBlock
FunctionCallOrIdentifier
Identifier list
colon :
Block
FunctionCall
Identifier li
NamedArg
NamedArgPrefix border-bottom=
String
StringFragment 1px solid black
PositionalArg
Identifier one
FunctionCall
Identifier li
PositionalArg
Identifier two
FunctionCall
Identifier li
PositionalArg
Identifier three
keyword end`)
})
test('inline expressions', () => {
expect(`
p:
h1 class=bright style='font-family: helvetica' Heya
h2 man that is (b wild)!
end`)
.toMatchTree(`
FunctionCallWithBlock
FunctionCallOrIdentifier
Identifier p
colon :
Block
FunctionCall
Identifier h1
NamedArg
NamedArgPrefix class=
Identifier bright
NamedArg
NamedArgPrefix style=
String
StringFragment font-family: helvetica
PositionalArg
Word Heya
FunctionCall
Identifier h2
PositionalArg
Identifier man
PositionalArg
Identifier that
PositionalArg
Identifier is
PositionalArg
ParenExpr
FunctionCall
Identifier b
PositionalArg
Identifier wild
PositionalArg
Word !
keyword end`)
})
})