Compare commits

..

No commits in common. "e0e5e828692713fcaf5d62f88d3ad2c3a43802d4" and "d707ee7e6b074cc0d64179004e5b6cc8250e0c91" have entirely different histories.

16 changed files with 133 additions and 849 deletions

View File

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

View File

@ -2,11 +2,9 @@
import { Compiler } from '../src/compiler/compiler'
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 { readFileSync, writeFileSync, mkdirSync } from 'fs'
import { randomUUID } from 'crypto'
import { randomUUID } from "crypto"
import { spawn } from 'child_process'
import { join } from 'path'
@ -34,17 +32,6 @@ 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() {
console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell.
@ -52,7 +39,6 @@ ${colors.bright}Usage:${colors.reset} shrimp <command> [...args]
${colors.bright}Commands:${colors.reset}
${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}eval ${colors.yellow}'some code'${colors.reset} Evaluate a line of Shrimp code
${colors.cyan}repl${colors.reset} Start REPL
@ -116,16 +102,6 @@ async function main() {
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)) {
const file = args[1]
if (!file) {

View File

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

View File

@ -54,7 +54,6 @@ export class Compiler {
fnLabelCount = 0
ifLabelCount = 0
tryLabelCount = 0
loopLabelCount = 0
bytecode: Bytecode
pipeCounter = 0
@ -389,7 +388,9 @@ export class Compiler {
return instructions
}
case terms.Block: {
case terms.ThenBlock:
case terms.SingleLineThenBlock:
case terms.TryBlock: {
const children = getAllChildren(node)
const instructions: ProgramItem[] = []
@ -404,51 +405,6 @@ export class Compiler {
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: {
const { tryBlock, catchVariable, catchBody, finallyBody } = getTryExprParts(node, input)
@ -673,24 +629,6 @@ export class Compiler {
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:
throw new CompilerError(
`Compiler doesn't know how to handle a "${node.type.name}" node.`,

View File

@ -154,18 +154,18 @@ describe('compiler', () => {
end`).toEvaluateTo('white')
})
test('if else if', () => {
test('if elseif', () => {
expect(`if false:
boromir
else if true:
elseif true:
frodo
end`).toEvaluateTo('frodo')
})
test('if else if else', () => {
test('if elseif else', () => {
expect(`if false:
destroyed
else if true:
elseif true:
fire
else:
darkness
@ -173,9 +173,9 @@ describe('compiler', () => {
expect(`if false:
king
else if false:
elseif false:
elf
else if true:
elseif true:
dwarf
else:
scattered

View File

@ -1,55 +0,0 @@
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

@ -1,115 +0,0 @@
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

@ -1,48 +0,0 @@
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)
} else if (child.type.id === terms.ElseIfExpr) {
const [_else, _if, conditional, _colon, thenBlock] = parts
const [_keyword, conditional, _colon, thenBlock] = parts
if (!conditional || !thenBlock) {
const names = parts.map((p) => p.type.name).join(', ')
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) => {
const children = getAllChildren(node)
// First child is always 'try' keyword, second is colon, third is Block
// First child is always 'try' keyword, second is colon, third is TryBlock or statement
const [tryKeyword, _colon, tryBlock, ...rest] = children
if (!tryKeyword || !tryBlock) {

View File

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

View File

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

View File

@ -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,null:90, catch:96, finally:102, end:104, while:118, try:124, throw:128, if:132, else:136}
const spec_Identifier = {__proto__:null,null:90, catch:96, finally:102, end:104, try:118, throw:122, if:126, elseif:134, else:138}
export const parser = LRParser.deserialize({
version: 14,
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: "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: "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 Block FinallyExpr keyword keyword Underscore Array ConditionalOp PositionalArg operator WhileExpr keyword FunctionCallWithBlock TryExpr keyword Throw keyword IfExpr keyword ElseIfExpr keyword ElseExpr CompoundAssign Assign",
maxTerm: 108,
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|",
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~",
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",
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",
maxTerm: 114,
context: trackScope,
nodeProps: [
["closedBy", 46,"end"]
@ -19,9 +19,9 @@ export const parser = LRParser.deserialize({
propSources: [highlighting],
skippedNodes: [0],
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#`~",
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~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO!|~~", 11)],
topRules: {"Program":[0,25]},
specialized: [{term: 20, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 20, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 1578
tokenPrec: 1619
})

View File

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

View File

@ -2,7 +2,7 @@ import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('if/else if/else', () => {
describe('if/elseif/else', () => {
test('parses single line if', () => {
expect(`if y == 1: 'cool' end`).toMatchTree(`
IfExpr
@ -12,7 +12,7 @@ describe('if/else if/else', () => {
EqEq ==
Number 1
colon :
Block
SingleLineThenBlock
String
StringFragment cool
keyword end
@ -26,7 +26,7 @@ describe('if/else if/else', () => {
keyword if
Identifier x
colon :
Block
SingleLineThenBlock
Number 2
keyword end
`)
@ -44,7 +44,7 @@ describe('if/else if/else', () => {
Lt <
Number 9
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier yes
keyword end
@ -61,81 +61,78 @@ describe('if/else if/else', () => {
keyword if
Identifier with-else
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier x
ElseExpr
keyword else
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier y
keyword end
`)
})
test('parses multiline if with else if', () => {
expect(`if with-else-if:
test('parses multiline if with elseif', () => {
expect(`if with-elseif:
x
else if another-condition:
elseif another-condition:
y
end`).toMatchTree(`
IfExpr
keyword if
Identifier with-else-if
Identifier with-elseif
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword else
keyword if
keyword elseif
Identifier another-condition
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier y
keyword end
`)
})
test('parses multiline if with multiple else if and else', () => {
expect(`if with-else-if-else:
test('parses multiline if with multiple elseif and else', () => {
expect(`if with-elseif-else:
x
else if another-condition:
elseif another-condition:
y
else if yet-another-condition:
elseif yet-another-condition:
z
else:
oh-no
end`).toMatchTree(`
IfExpr
keyword if
Identifier with-else-if-else
Identifier with-elseif-else
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier x
ElseIfExpr
keyword else
keyword if
keyword elseif
Identifier another-condition
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier y
ElseIfExpr
keyword else
keyword if
keyword elseif
Identifier yet-another-condition
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier z
ElseExpr
keyword else
colon :
Block
ThenBlock
FunctionCallOrIdentifier
Identifier oh-no
keyword end
@ -151,124 +148,9 @@ describe('if/else if/else', () => {
keyword if
Boolean true
colon :
Block
SingleLineThenBlock
Number 2
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
keyword try
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
Block
TryBlock
FunctionCall
Identifier handle-error
PositionalArg
@ -37,13 +37,13 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier do-work
FinallyExpr
keyword finally
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@ -61,14 +61,14 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier risky-operation
CatchExpr
keyword catch
Identifier err
colon :
Block
TryBlock
FunctionCall
Identifier handle-error
PositionalArg
@ -76,7 +76,7 @@ describe('try/catch/finally/throw', () => {
FinallyExpr
keyword finally
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end
@ -91,17 +91,15 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
Block
FunctionCall
Identifier parse-number
PositionalArg
Identifier input
FunctionCall
Identifier parse-number
PositionalArg
Identifier input
CatchExpr
keyword catch
Identifier err
colon :
Block
Number 0
Number 0
keyword end
`)
})
@ -111,21 +109,18 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
Block
FunctionCallOrIdentifier
Identifier work
FunctionCallOrIdentifier
Identifier work
CatchExpr
keyword catch
Identifier err
colon :
Block
Number 0
Number 0
FinallyExpr
keyword finally
colon :
Block
FunctionCallOrIdentifier
Identifier cleanup
FunctionCallOrIdentifier
Identifier cleanup
keyword end
`)
})
@ -169,15 +164,13 @@ describe('try/catch/finally/throw', () => {
TryExpr
keyword try
colon :
Block
FunctionCallOrIdentifier
Identifier work
FunctionCallOrIdentifier
Identifier work
CatchExpr
keyword catch
Identifier err
colon :
Block
Number 0
Number 0
keyword end
`)
})
@ -206,7 +199,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier e
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier empty-string
keyword end
@ -234,7 +227,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier close-resources
keyword end
@ -266,7 +259,7 @@ describe('function-level exception handling', () => {
keyword catch
Identifier err
colon :
Block
TryBlock
FunctionCall
Identifier log
PositionalArg
@ -276,7 +269,7 @@ describe('function-level exception handling', () => {
FinallyExpr
keyword finally
colon :
Block
TryBlock
FunctionCallOrIdentifier
Identifier cleanup
keyword end

View File

@ -1,303 +0,0 @@
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`)
})
})