Compare commits

..

18 Commits

Author SHA1 Message Date
019f7d84b1 Add ?? and ??= operators 2025-11-08 16:12:20 -08:00
4c794944ef Merge pull request 'bitwise' (#36) from bitwise into main
Reviewed-on: #36
2025-11-09 00:11:19 +00:00
99a5aa5312 update reef, fix precedence test 2025-11-08 16:11:05 -08:00
7bbf43a725 bitwise operators 2025-11-08 16:10:13 -08:00
4c15526d1b Merge pull request 'hex and binary numbers' (#40) from hex-and-binary-numbers into main
Reviewed-on: #40
2025-11-09 00:05:46 +00:00
c741cfee51 hex and binary numbers 2025-11-08 16:05:28 -08:00
012b8c8cf1 Merge pull request 'ref keyword' (#38) from ref-keyword into main
Reviewed-on: #38
2025-11-09 00:03:44 +00:00
4c3f7a8bfc add ref for grabbing a function 2025-11-08 16:03:25 -08:00
fe6f54b402 Merge pull request 'Passing null to a function triggers default value' (#41) from null-triggers-default-values into main
Reviewed-on: #41
2025-11-09 00:02:27 +00:00
49f3f3e09f Merge pull request 'Fix calling no-arg dotget functions' (#37) from no-args-bug into main
Reviewed-on: #37
2025-11-09 00:01:57 +00:00
0d1dce4868 fix calling no-arg dotget functions 2025-11-08 16:01:40 -08:00
d18ab2507c Merge pull request 'Allow _ in numbers (10_000_000)' (#34) from underscore-in-numbers into main
Reviewed-on: #34
2025-11-09 00:00:40 +00:00
7e69356f79 allow _ in numbers (10_000_000) 2025-11-08 16:00:25 -08:00
9863f46f38 Merge pull request 'JSON!' (#33) from json into main
Reviewed-on: #33
2025-11-08 23:59:01 +00:00
45f31d0678 allow newlines in (some) parens expressions 2025-11-08 11:26:46 -08:00
49a6320fef add list.insert 2025-11-08 11:17:45 -08:00
51f67ac908 Passing null to a function triggers default value 2025-11-08 10:53:54 -08:00
d4a772e88b json.encode & json.decode 2025-11-07 19:42:04 -08:00
19 changed files with 776 additions and 76 deletions

View File

@ -62,7 +62,7 @@
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="], "hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#d7a971db24aea5ddcaae2c18ce9f10dab793db19", { "peerDependencies": { "typescript": "^5" } }, "d7a971db24aea5ddcaae2c18ce9f10dab793db19"], "reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#3e2e68b31f504347225a4d705c7568a0957d629e", { "peerDependencies": { "typescript": "^5" } }, "3e2e68b31f504347225a4d705c7568a0957d629e"],
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],

View File

@ -10,7 +10,7 @@
"repl": "bun generate-parser && bun bin/repl", "repl": "bun generate-parser && bun bin/repl",
"update-reef": "rm -rf ~/.bun/install/cache/ && rm bun.lock && bun update reefvm", "update-reef": "rm -rf ~/.bun/install/cache/ && rm bun.lock && bun update reefvm",
"cli:install": "ln -s \"$(pwd)/bin/shrimp\" ~/.bun/bin/shrimp", "cli:install": "ln -s \"$(pwd)/bin/shrimp\" ~/.bun/bin/shrimp",
"cli:remove": "rm ~/.bun/bin/shrimp", "cli:remove": "rm ~/.bun/bin/shrimp"
}, },
"dependencies": { "dependencies": {
"@codemirror/view": "^6.38.3", "@codemirror/view": "^6.38.3",

View File

@ -51,6 +51,7 @@ function processEscapeSeq(escapeSeq: string): string {
export class Compiler { export class Compiler {
instructions: ProgramItem[] = [] instructions: ProgramItem[] = []
labelCount = 0
fnLabelCount = 0 fnLabelCount = 0
ifLabelCount = 0 ifLabelCount = 0
tryLabelCount = 0 tryLabelCount = 0
@ -105,11 +106,21 @@ export class Compiler {
switch (node.type.id) { switch (node.type.id) {
case terms.Number: case terms.Number:
const number = Number(value) // Handle sign prefix for hex and binary literals
if (Number.isNaN(number)) // Number() doesn't parse '-0xFF' or '+0xFF' correctly
let numberValue: number
if (value.startsWith('-') && (value.includes('0x') || value.includes('0b'))) {
numberValue = -Number(value.slice(1))
} else if (value.startsWith('+') && (value.includes('0x') || value.includes('0b'))) {
numberValue = Number(value.slice(1))
} else {
numberValue = Number(value)
}
if (Number.isNaN(numberValue))
throw new CompilerError(`Invalid number literal: ${value}`, node.from, node.to) throw new CompilerError(`Invalid number literal: ${value}`, node.from, node.to)
return [[`PUSH`, number]] return [[`PUSH`, numberValue]]
case terms.String: { case terms.String: {
const { parts, hasInterpolation } = getStringParts(node, input) const { parts, hasInterpolation } = getStringParts(node, input)
@ -232,6 +243,24 @@ export class Compiler {
case '%': case '%':
instructions.push(['MOD']) instructions.push(['MOD'])
break break
case 'band':
instructions.push(['BIT_AND'])
break
case 'bor':
instructions.push(['BIT_OR'])
break
case 'bxor':
instructions.push(['BIT_XOR'])
break
case '<<':
instructions.push(['BIT_SHL'])
break
case '>>':
instructions.push(['BIT_SHR'])
break
case '>>>':
instructions.push(['BIT_USHR'])
break
default: default:
throw new CompilerError(`Unsupported binary operator: ${opValue}`, op.from, op.to) throw new CompilerError(`Unsupported binary operator: ${opValue}`, op.from, op.to)
} }
@ -385,7 +414,29 @@ export class Compiler {
case terms.FunctionCallOrIdentifier: { case terms.FunctionCallOrIdentifier: {
if (node.firstChild?.type.id === terms.DotGet) { if (node.firstChild?.type.id === terms.DotGet) {
return this.#compileNode(node.firstChild, input) const instructions: ProgramItem[] = []
const callLabel = `.call_dotget_${++this.labelCount}`
const afterLabel = `.after_dotget_${++this.labelCount}`
instructions.push(...this.#compileNode(node.firstChild, input))
instructions.push(['DUP'])
instructions.push(['TYPE'])
instructions.push(['PUSH', 'function'])
instructions.push(['EQ'])
instructions.push(['JUMP_IF_TRUE', callLabel])
instructions.push(['DUP'])
instructions.push(['TYPE'])
instructions.push(['PUSH', 'native'])
instructions.push(['EQ'])
instructions.push(['JUMP_IF_TRUE', callLabel])
instructions.push(['JUMP', afterLabel])
instructions.push([`${callLabel}:`])
instructions.push(['PUSH', 0])
instructions.push(['PUSH', 0])
instructions.push(['CALL'])
instructions.push([`${afterLabel}:`])
return instructions
} }
return [['TRY_CALL', value]] return [['TRY_CALL', value]]

View File

@ -0,0 +1,178 @@
import { expect, describe, test } from 'bun:test'
describe('bitwise operators', () => {
describe('band (bitwise AND)', () => {
test('basic AND operation', () => {
expect('5 band 3').toEvaluateTo(1)
// 5 = 0101, 3 = 0011, result = 0001 = 1
})
test('AND with zero', () => {
expect('5 band 0').toEvaluateTo(0)
})
test('AND with all bits set', () => {
expect('15 band 7').toEvaluateTo(7)
// 15 = 1111, 7 = 0111, result = 0111 = 7
})
test('AND in assignment', () => {
expect('x = 12 band 10').toEvaluateTo(8)
// 12 = 1100, 10 = 1010, result = 1000 = 8
})
})
describe('bor (bitwise OR)', () => {
test('basic OR operation', () => {
expect('5 bor 3').toEvaluateTo(7)
// 5 = 0101, 3 = 0011, result = 0111 = 7
})
test('OR with zero', () => {
expect('5 bor 0').toEvaluateTo(5)
})
test('OR with all bits set', () => {
expect('8 bor 4').toEvaluateTo(12)
// 8 = 1000, 4 = 0100, result = 1100 = 12
})
})
describe('bxor (bitwise XOR)', () => {
test('basic XOR operation', () => {
expect('5 bxor 3').toEvaluateTo(6)
// 5 = 0101, 3 = 0011, result = 0110 = 6
})
test('XOR with itself returns zero', () => {
expect('5 bxor 5').toEvaluateTo(0)
})
test('XOR with zero returns same value', () => {
expect('7 bxor 0').toEvaluateTo(7)
})
test('XOR in assignment', () => {
expect('result = 8 bxor 12').toEvaluateTo(4)
// 8 = 1000, 12 = 1100, result = 0100 = 4
})
})
describe('bnot (bitwise NOT)', () => {
test('NOT of positive number', () => {
expect('bnot 5').toEvaluateTo(-6)
// ~5 = -6 (two\'s complement)
})
test('NOT of zero', () => {
expect('bnot 0').toEvaluateTo(-1)
})
test('NOT of negative number', () => {
expect('bnot -1').toEvaluateTo(0)
})
test('double NOT returns original', () => {
expect('bnot (bnot 5)').toEvaluateTo(5)
})
})
describe('<< (left shift)', () => {
test('basic left shift', () => {
expect('5 << 2').toEvaluateTo(20)
// 5 << 2 = 20
})
test('shift by zero', () => {
expect('5 << 0').toEvaluateTo(5)
})
test('shift by one', () => {
expect('3 << 1').toEvaluateTo(6)
})
test('large shift', () => {
expect('1 << 10').toEvaluateTo(1024)
})
})
describe('>> (signed right shift)', () => {
test('basic right shift', () => {
expect('20 >> 2').toEvaluateTo(5)
// 20 >> 2 = 5
})
test('shift by zero', () => {
expect('20 >> 0').toEvaluateTo(20)
})
test('preserves sign for negative numbers', () => {
expect('-20 >> 2').toEvaluateTo(-5)
// Sign is preserved
})
test('negative number right shift', () => {
expect('-8 >> 1').toEvaluateTo(-4)
})
})
describe('>>> (unsigned right shift)', () => {
test('basic unsigned right shift', () => {
expect('20 >>> 2').toEvaluateTo(5)
})
test('unsigned shift of -1', () => {
expect('-1 >>> 1').toEvaluateTo(2147483647)
// -1 >>> 1 = 2147483647 (unsigned, no sign extension)
})
test('unsigned shift of negative number', () => {
expect('-8 >>> 1').toEvaluateTo(2147483644)
})
})
describe('compound expressions', () => {
test('multiple bitwise operations', () => {
expect('(5 band 3) bor (8 bxor 12)').toEvaluateTo(5)
// (5 & 3) | (8 ^ 12) = 1 | 4 = 5
})
test('bitwise with variables', () => {
expect(`
a = 5
b = 3
a bor b
`).toEvaluateTo(7)
})
test('shift operations with variables', () => {
expect(`
x = 16
y = 2
x >> y
`).toEvaluateTo(4)
})
test('mixing shifts and bitwise', () => {
expect('(8 << 1) band 15').toEvaluateTo(0)
// (8 << 1) & 15 = 16 & 15 = 0
})
test('mixing shifts and bitwise 2', () => {
expect('(7 << 1) band 15').toEvaluateTo(14)
// (7 << 1) & 15 = 14 & 15 = 14
})
})
describe('precedence', () => {
test('bitwise has correct precedence with arithmetic', () => {
expect('1 + 2 band 3').toEvaluateTo(3)
// (1 + 2) & 3 = 3 & 3 = 3
})
test('shift has correct precedence', () => {
expect('4 + 8 << 1').toEvaluateTo(24)
// (4 + 8) << 1 = 12 << 1 = 24
})
})
})

View File

@ -110,7 +110,10 @@ describe('compiler', () => {
}) })
test('function call with no args', () => { test('function call with no args', () => {
expect(`bloop = do: 'bloop' end; bloop`).toEvaluateTo('bloop') expect(`bloop = do: 'bleep' end; bloop`).toEvaluateTo('bleep')
expect(`bloop = [ go=do: 'bleep' end ]; bloop.go`).toEvaluateTo('bleep')
expect(`bloop = [ go=do: 'bleep' end ]; abc = do x: x end; abc (bloop.go)`).toEvaluateTo('bleep')
expect(`num = ((math.random) * 10 + 1) | math.floor; num >= 1 and num <= 10 `).toEvaluateTo(true)
}) })
test('function call with if statement and multiple expressions', () => { test('function call with if statement and multiple expressions', () => {
@ -298,6 +301,23 @@ describe('default params', () => {
expect('multiply = do x y=5: x * y end; multiply 5 2').toEvaluateTo(10) expect('multiply = do x y=5: x * y end; multiply 5 2').toEvaluateTo(10)
}) })
test('null triggers default value', () => {
expect('test = do n=true: n end; test').toEvaluateTo(true)
expect('test = do n=true: n end; test false').toEvaluateTo(false)
expect('test = do n=true: n end; test null').toEvaluateTo(true)
})
test('null triggers default for named parameters', () => {
expect("greet = do name='World': name end; greet name=null").toEvaluateTo('World')
expect("greet = do name='World': name end; greet name='Bob'").toEvaluateTo('Bob')
})
test('null triggers default with multiple parameters', () => {
expect('calc = do x=10 y=20: x + y end; calc null 5').toEvaluateTo(15)
expect('calc = do x=10 y=20: x + y end; calc 3 null').toEvaluateTo(23)
expect('calc = do x=10 y=20: x + y end; calc null null').toEvaluateTo(30)
})
test.skip('array default', () => { test.skip('array default', () => {
expect('abc = do alpha=[a b c]: alpha end; abc').toEvaluateTo(['a', 'b', 'c']) expect('abc = do alpha=[a b c]: alpha end; abc').toEvaluateTo(['a', 'b', 'c'])
expect('abc = do alpha=[a b c]: alpha end; abc [x y z]').toEvaluateTo(['x', 'y', 'z']) expect('abc = do alpha=[a b c]: alpha end; abc [x y z]').toEvaluateTo(['x', 'y', 'z'])

View File

@ -1,6 +1,44 @@
import { describe } from 'bun:test' import { describe } from 'bun:test'
import { expect, test } from 'bun:test' import { expect, test } from 'bun:test'
describe('number literals', () => {
test('binary literals', () => {
expect('0b110').toEvaluateTo(6)
expect('0b1010').toEvaluateTo(10)
expect('0b11111111').toEvaluateTo(255)
expect('0b0').toEvaluateTo(0)
expect('0b1').toEvaluateTo(1)
})
test('hex literals', () => {
expect('0xdeadbeef').toEvaluateTo(0xdeadbeef)
expect('0xdeadbeef').toEvaluateTo(3735928559)
expect('0xFF').toEvaluateTo(255)
expect('0xff').toEvaluateTo(255)
expect('0x10').toEvaluateTo(16)
expect('0x0').toEvaluateTo(0)
expect('0xABCDEF').toEvaluateTo(0xabcdef)
})
test('decimal literals still work', () => {
expect('42').toEvaluateTo(42)
expect('3.14').toEvaluateTo(3.14)
expect('0').toEvaluateTo(0)
expect('999999').toEvaluateTo(999999)
})
test('negative hex and binary', () => {
expect('-0xFF').toEvaluateTo(-255)
expect('-0b1010').toEvaluateTo(-10)
})
test('positive prefix', () => {
expect('+0xFF').toEvaluateTo(255)
expect('+0b110').toEvaluateTo(6)
expect('+42').toEvaluateTo(42)
})
})
describe('array literals', () => { describe('array literals', () => {
test('work with numbers', () => { test('work with numbers', () => {
expect('[1 2 3]').toEvaluateTo([1, 2, 3]) expect('[1 2 3]').toEvaluateTo([1, 2, 3])

View File

@ -5,6 +5,12 @@ type Operator = { str: string; tokenName: keyof typeof terms }
const operators: Array<Operator> = [ const operators: Array<Operator> = [
{ str: 'and', tokenName: 'And' }, { str: 'and', tokenName: 'And' },
{ str: 'or', tokenName: 'Or' }, { str: 'or', tokenName: 'Or' },
{ str: 'band', tokenName: 'Band' },
{ str: 'bor', tokenName: 'Bor' },
{ str: 'bxor', tokenName: 'Bxor' },
{ str: '>>>', tokenName: 'Ushr' }, // Must come before >>
{ str: '>>', tokenName: 'Shr' },
{ str: '<<', tokenName: 'Shl' },
{ str: '>=', tokenName: 'Gte' }, { str: '>=', tokenName: 'Gte' },
{ str: '<=', tokenName: 'Lte' }, { str: '<=', tokenName: 'Lte' },
{ str: '!=', tokenName: 'Neq' }, { str: '!=', tokenName: 'Neq' },

View File

@ -6,14 +6,18 @@
@top Program { item* } @top Program { item* }
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq, NullishCoalesce, NullishEq } @external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, EqEq, Neq, Lt, Lte, Gt, Gte, Modulo, PlusEq, MinusEq, StarEq, SlashEq, ModuloEq, Band, Bor, Bxor, Shl, Shr, Ushr, NullishCoalesce, NullishEq }
@tokens { @tokens {
@precedence { Number Regex } @precedence { Number Regex }
StringFragment { !['\\$]+ } StringFragment { !['\\$]+ }
NamedArgPrefix { $[a-z-]+ "=" } NamedArgPrefix { $[a-z] $[a-z0-9-]* "=" }
Number { ("-" | "+")? $[0-9]+ ('.' $[0-9]+)? } Number {
("-" | "+")? "0x" $[0-9a-fA-F]+ |
("-" | "+")? "0b" $[01]+ |
("-" | "+")? $[0-9]+ ("_"? $[0-9]+)* ('.' $[0-9]+ ("_"? $[0-9]+)*)?
}
Boolean { "true" | "false" } Boolean { "true" | "false" }
newlineOrSemicolon { "\n" | ";" } newlineOrSemicolon { "\n" | ";" }
eof { @eof } eof { @eof }
@ -48,7 +52,9 @@ null { @specialize[@name=Null]<Identifier, "null"> }
comparison @left, comparison @left,
multiplicative @left, multiplicative @left,
additive @left, additive @left,
call bitwise @left,
call,
functionWithNewlines
} }
item { item {
@ -186,11 +192,31 @@ BinOp {
(expression | BinOp) !multiplicative Star (expression | BinOp) | (expression | BinOp) !multiplicative Star (expression | BinOp) |
(expression | BinOp) !multiplicative Slash (expression | BinOp) | (expression | BinOp) !multiplicative Slash (expression | BinOp) |
(expression | BinOp) !additive Plus (expression | BinOp) | (expression | BinOp) !additive Plus (expression | BinOp) |
(expression | BinOp) !additive Minus (expression | BinOp) (expression | BinOp) !additive Minus (expression | BinOp) |
(expression | BinOp) !bitwise Band (expression | BinOp) |
(expression | BinOp) !bitwise Bor (expression | BinOp) |
(expression | BinOp) !bitwise Bxor (expression | BinOp) |
(expression | BinOp) !bitwise Shl (expression | BinOp) |
(expression | BinOp) !bitwise Shr (expression | BinOp) |
(expression | BinOp) !bitwise Ushr (expression | BinOp)
} }
ParenExpr { ParenExpr {
leftParen (IfExpr | ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen leftParen newlineOrSemicolon* (
FunctionCallWithNewlines |
IfExpr |
ambiguousFunctionCall |
BinOp newlineOrSemicolon* |
expressionWithoutIdentifier |
ConditionalOp newlineOrSemicolon* |
PipeExpr |
FunctionDef
)
rightParen
}
FunctionCallWithNewlines[@name=FunctionCall] {
(DotGet | Identifier | ParenExpr) newlineOrSemicolon+ arg !functionWithNewlines (newlineOrSemicolon+ arg)* newlineOrSemicolon*
} }
expression { expression {

View File

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

View File

@ -4,24 +4,24 @@ import {operatorTokenizer} from "./operatorTokenizer"
import {tokenizer, specializeKeyword} from "./tokenizer" import {tokenizer, specializeKeyword} from "./tokenizer"
import {trackScope} from "./parserScopeContext" import {trackScope} from "./parserScopeContext"
import {highlighting} from "./highlight" import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,while:64, if:72, null:100, catch:106, finally:112, end:114, else:122, try:140, throw:144} const spec_Identifier = {__proto__:null,while:76, null:108, catch:114, finally:120, end:122, if:130, else:136, try:154, throw:158}
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: "9hQYQbOOO!dOpO'#DSO!iOSO'#DZO$bQcO'#DmO&xQcO'#E[OOQ`'#Ej'#EjO'{QRO'#DnO)eQcO'#EYO)uQbO'#DOOOQa'#Dp'#DpO+wQbO'#DqOOQa'#E['#E[O,OQcO'#E[O,VQcO'#EZO-VQcO'#EYO-dQRO'#DwOOQ`'#EY'#EYO-{QbO'#EYO.SQQO'#EXOOQ`'#EX'#EXOOQ`'#Dy'#DyQYQbOOO._QbO'#DVO.jQbO'#DPO/_QbO'#C{O0SQQO'#DsO/_QbO'#DuO0XObO,59nO0dQbO'#D]O0lQWO'#D^OOOO'#Eb'#EbOOOO'#EO'#EOO1QOSO,59uOOQa,59u,59uOOQ`'#DU'#DUO1`QbO'#DiOOQ`'#E`'#E`OOQ`'#D{'#D{O1jQbO,59mOOQa'#EZ'#EZO/_QbO,5:YO/_QbO,5:YO/_QbO,5:YO/_QbO,59iO/_QbO,59iO/_QbO,59iO/_QbO,59iO2dQRO,59jO2pQQO,59jO2xQQO,59jO3TQRO,59jO3nQRO,59jO4PQQO'#CyOOQ`'#ER'#ERO4UQbO,5:]O4]QQO,5:[OOQa,5:],5:]O4hQbO,5:]O)uQbO,5:dO)uQbO,5:cO4rQbO,5:^O4yQbO,59eOOQ`,5:s,5:sO)uQbO'#DzOOQ`-E7w-E7wOOQ`'#D|'#D|O5eQbO'#DWO5pQbO'#DXOOQO'#D}'#D}O5hQQO'#DWO6OQQO,59qO6TQcO'#EZO6[QRO'#E^O7XQRO'#E^OOQO'#E^'#E^O7`QQO,59kO7eQRO,59gO7lQRO,59gO4rQbO,5:_O7zQcO,5:aO9SQcO,5:aO9dQcO,5:aOOQa1G/Y1G/YOOOO,59w,59wOOOO,59x,59xOOOO-E7|-E7|OOQa1G/a1G/aOOQ`,5:T,5:TOOQ`-E7y-E7yOOQa1G/t1G/tO:fQcO1G/tO:pQcO1G/tO:zQcO1G/tOOQa1G/T1G/TO<yQcO1G/TO=QQcO1G/TO=XQcO1G/TO>WQcO1G/TO=`QcO1G/TOOQa1G/U1G/UOOQ`-E8P-E8PO>nQQO1G/vOOQa1G/w1G/wO>yQbO1G/wOOQO'#ES'#ESO>nQQO1G/vOOQa1G/v1G/vOOQ`'#ET'#ETO>yQbO1G/wO?TQbO1G0OO?oQbO1G/}O@ZQbO'#DdO@lQbO'#DdOAPQbO1G/xOOQ`-E7x-E7xOOQ`,5:f,5:fOOQ`-E7z-E7zOA[QQO,59rOOQO,59s,59sOOQO-E7{-E7{OAdQbO1G/]O4rQbO1G/VO4rQbO1G/ROAkQbO1G/yOAvQQO7+%bOOQa7+%b7+%bOBRQbO7+%cOOQa7+%c7+%cOOQO-E8Q-E8QOOQ`-E8R-E8ROOQ`'#EP'#EPOB]QQO'#EPOBeQbO'#EiOOQ`,5:O,5:OOBxQbO'#DbOB}QQO'#DeOOQ`7+%d7+%dOCSQbO7+%dOCXQbO7+%dOCaQbO7+$wOCoQbO7+$wODPQbO7+$qODXQbO7+$mOOQ`7+%e7+%eOD^QbO7+%eODcQbO7+%eOOQa<<H|<<H|OOQa<<H}<<H}OOQ`,5:k,5:kOOQ`-E7}-E7}ODkQQO,59|O4rQbO,5:POOQ`<<IO<<IOODpQbO<<IOOOQ`<<Hc<<HcODuQbO<<HcODzQbO<<HcOESQbO<<HcOOQ`'#EQ'#EQOE_QbO<<H]OEgQbO'#DlOOQ`<<H]<<H]OEoQbO<<H]OOQ`<<HX<<HXOOQ`<<IP<<IPOEtQbO<<IPO4rQbO1G/hOOQ`1G/k1G/kOOQ`AN>jAN>jOOQ`AN=}AN=}OEyQbOAN=}OFOQbOAN=}OOQ`-E8O-E8OOOQ`AN=wAN=wOFWQbOAN=wO.jQbO,5:UO4rQbO,5:WOOQ`AN>kAN>kOOQ`7+%S7+%SOOQ`G23iG23iOF]QbOG23iPFbQbO'#DjOOQ`G23cG23cOFgQQO1G/pOOQ`1G/r1G/rOOQ`LD)TLD)TO4rQbO7+%[OOQ`<<Hv<<Hv", states: "<tQYQbOOO!dOpO'#DWO!iOSO'#D_O%^QcO'#DtO(WQcO'#EdOOQ`'#Er'#ErO(qQRO'#DuO*vQcO'#EbO+aQbO'#DUOOQa'#Dw'#DwO-fQbO'#DxOOQa'#Ed'#EdO-mQcO'#EdO/kQcO'#EcO0pQcO'#EbO0}QRO'#EOOOQ`'#Eb'#EbO1fQbO'#EbO1mQQO'#EaOOQ`'#Ea'#EaOOQ`'#EQ'#EQQYQbOOO1xQbO'#DZO2TQbO'#DnO2xQbO'#DRO3mQQO'#DzO2xQbO'#D|O3rObO,59rO3}QbO'#DaO4VQWO'#DbOOOO'#Ej'#EjOOOO'#EV'#EVO4kOSO,59yOOQa,59y,59yOOQ`'#DY'#DYO4yQbO'#DmOOQ`'#Eh'#EhOOQ`'#EY'#EYO5TQbO,5:[OOQa'#Ec'#EcO2xQbO,5:aO2xQbO,5:aO2xQbO,5:aO2xQbO,5:aO2xQbO,59oO2xQbO,59oO2xQbO,59oO2xQbO,59oOOQ`'#ES'#ESO+aQbO,59pO5}QcO'#DtO6UQcO'#EdO6]QRO,59pO6gQQO,59pO6lQQO,59pO6tQQO,59pO7PQRO,59pO7iQRO,59pO7pQQO'#DPO7uQbO,5:dO7|QQO,5:cOOQa,5:d,5:dO8XQbO,5:dO8cQbO,5:kO8cQbO,5:jO9jQbO,5:eO9qQbO,59kOOQ`,5:{,5:{O8cQbO'#EROOQ`-E8O-E8OOOQ`'#ET'#ETO:]QbO'#D[O:hQbO'#D]OOQO'#EU'#EUO:`QQO'#D[O:vQQO,59uO:{QcO'#EcO;xQRO'#EqO<uQRO'#EqOOQO'#Eq'#EqO<|QQO,5:YO=RQRO,59mO=YQRO,59mO9jQbO,5:fO=hQcO,5:hO>vQcO,5:hO?dQcO,5:hOOQa1G/^1G/^OOOO,59{,59{OOOO,59|,59|OOOO-E8T-E8TOOQa1G/e1G/eOOQ`,5:X,5:XOOQ`-E8W-E8WOOQa1G/{1G/{OA`QcO1G/{OAjQcO1G/{OBxQcO1G/{OCSQcO1G/{OCaQcO1G/{OOQa1G/Z1G/ZODrQcO1G/ZODyQcO1G/ZOEQQcO1G/ZOFPQcO1G/ZOEXQcO1G/ZOOQ`-E8Q-E8QOFgQRO1G/[OFqQQO1G/[OFvQQO1G/[OGOQQO1G/[OGZQRO1G/[OGbQRO1G/[OGiQbO,59qOGsQQO1G/[OOQa1G/[1G/[OG{QQO1G/}OOQa1G0O1G0OOHWQbO1G0OOOQO'#E['#E[OG{QQO1G/}OOQa1G/}1G/}OOQ`'#E]'#E]OHWQbO1G0OOHbQbO1G0VOH|QbO1G0UOIhQbO'#DhOIyQbO'#DhOJ^QbO1G0POOQ`-E8P-E8POOQ`,5:m,5:mOOQ`-E8R-E8ROJiQQO,59vOOQO,59w,59wOOQO-E8S-E8SOJqQbO1G/aO9jQbO1G/tO9jQbO1G/XOJxQbO1G0QOKTQQO7+$vOOQa7+$v7+$vOK]QQO1G/]OKeQQO7+%iOOQa7+%i7+%iOKpQbO7+%jOOQa7+%j7+%jOOQO-E8Y-E8YOOQ`-E8Z-E8ZOOQ`'#EW'#EWOKzQQO'#EWOLSQbO'#EpOOQ`,5:S,5:SOLgQbO'#DfOLlQQO'#DiOOQ`7+%k7+%kOLqQbO7+%kOLvQbO7+%kOMOQbO7+${OM^QbO7+${OMnQbO7+%`OMvQbO7+$sOOQ`7+%l7+%lOM{QbO7+%lONQQbO7+%lOOQa<<Hb<<HbONYQbO7+$wONgQQO7+$wOOQa<<IT<<ITOOQa<<IU<<IUOOQ`,5:r,5:rOOQ`-E8U-E8UONoQQO,5:QO9jQbO,5:TOOQ`<<IV<<IVONtQbO<<IVOOQ`<<Hg<<HgONyQbO<<HgO! OQbO<<HgO! WQbO<<HgOOQ`'#EZ'#EZO! cQbO<<HzO! kQbO'#DsOOQ`<<Hz<<HzO! sQbO<<HzOOQ`<<H_<<H_OOQ`<<IW<<IWO! xQbO<<IWOOQO,5:s,5:sO! }QbO<<HcOOQO-E8V-E8VO9jQbO1G/lOOQ`1G/o1G/oOOQ`AN>qAN>qOOQ`AN>RAN>RO!![QbOAN>RO!!aQbOAN>ROOQ`-E8X-E8XOOQ`AN>fAN>fO!!iQbOAN>fO2TQbO,5:]O9jQbO,5:_OOQ`AN>rAN>rPGiQbO'#ESOOQ`7+%W7+%WOOQ`G23mG23mO!!nQbOG23mP! nQbO'#DqOOQ`G24QG24QO!!sQQO1G/wOOQ`1G/y1G/yOOQ`LD)XLD)XO9jQbO7+%cOOQ`<<H}<<H}",
stateData: "Fo~O!zOSkOS~OfROg_OhZOiPOjfOphOtgOwZO!RZO!SZO!cZO!hiO!jjO#PWO#TQO#[cO#`XO#aYO~O#RkO~O!OnO#TqO#VlO#WmO~OfwOhZOiPOjfOwZO|sO!RZO!SZO![rO!cZO#PWO#TQO#`XO#aYOT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}Xd!}X~OP!}XQ!}XR!}XS!}X^!}Xn!aX!T!aX#[!aX#c!aX#_!aX!V!aX!Y!aX!Z!aX!_!aX~P!wOP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OXd#OXn#OX#[#OX#c#OX#_#OX!V#OX!Y#OX!Z#OX!_#OX~OfwOhZOiPOjfOwZO|sO!RZO!SZO![rO!cZO#PWO#TQO#`XO#aYO!T#OX~P%bOPyOQyORzOSzOT|OU}OW{OX{OY{OZ{O[{O]{O^xOd!OO~On!|X#[!|X#c!|X!V!|X!Y!|X!Z!|X#_!|X!_!|X~OPyOQyORzOSzO~P(yOfROg_OhZOiPOjfOphOtgOwZO!RZO!SZO!cZO!hiO!jjO#PWO#TQO#`XO#aYO~OfwOhZOiPOwZO|sO!RZO!SZO!cZO#PWO#TQO#[!VO#`XO#aYO~O#b!YO~P*|OV![O~P%bOP!}XQ!}XR!}XS!}XT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X^!}Xd!}X~P(yOT|OU}Od!OO~P(yOV![O_!]O`!]Oa!]Ob!]Oc!]Oe!]O~O!T!^O~P(yOn!aO#[!`O#c!`O~Of!cO|!eO!TzP~Of!iOhZOiPOwZO!RZO!SZO!cZO#PWO#TQO#`XO#aYO~OfwOhZOiPOwZO!RZO!SZO!cZO#PWO#TQO#`XO#aYO~O!T!pO~Of!tOw!tO#PWO~Of!uO#PWO~O#T!vO#V!vO#W!vO#X!vO#Y!vO#Z!vO~O!OnO#T!xO#VlO#WmO~OjfO![!yO~P/_OjfO|sO![rOnua!Tua#[ua#cua#_ua!Vua!Yua!Zua!_ua~P/_OPyOQyORzOSzO#_#VOn!|X~O!T!^O#_#VOn!|X~O#_#VOP!}XQ!}XR!}XS!}X^!}Xn!|X~P#sOT|OU}Od!OO#_#VOn!|X~On!aO~O#b#YO~P*|O|sO#[#[O#b#^O~O#[#_O#b#YO~P/_O#[#dO~P)uOn!aO#[ma#cma#_ma!Vma!Yma!Zma!_ma~Of!cO|!eO!TzX~Ow#jO!R#jO!S#jO#TQO~O!T#lO~O!T!}X~P!wOT|OU}Od!OO!T#QX~OT|OU}OW{OX{OY{OZ{O[{O]{Od!OO~O!T#QX~P6jO!T#mO~O!T#nO~P6jOT|OU}Od!OO!T#nO~On!ia#[!ia#c!ia!V!ia!Y!ia!Z!ia#_!ia!_!ia~P'{On!ia#[!ia#c!ia!V!ia!Y!ia!Z!ia#_!ia!_!ia~OPyOQyORzOSzO~P8hOT|OU}Od!OO~P8hO^xOR!biS!bin!bi#[!bi#c!bi#_!bi!V!bi!Y!bi!Z!bi!_!bi~OP!biQ!bi~P9qOPyOQyO~P9qOPyOQyOR!biS!bin!bi#[!bi#c!bi#_!bi!V!bi!Y!bi!Z!bi!_!bi~OW{OX{OY{OZ{O[{O]{OTqidqinqi#[qi#cqi#_qi!Tqi!Vqi!Yqi!Zqi!_qi~OU}O~P;rOU}O~P<UOUqi~P;rOT|OU}Odqinqi#[qi#cqi#_qi!Tqi!Vqi!Yqi!Zqi!_qi~OW{OX{OY{OZ{O[{O]{O~P=`O|sO#[#[O#b#qO~O#[#_O#b#sO~P/_On!aO#[!li#c!li!V!li!Y!li!Z!li#_!li!_!li~On!aO#[!ki#c!ki!V!ki!Y!ki!Z!ki#_!ki!_!ki~On!aO!V!WX!Y!WX!Z!WX!_!WX~O#[#vO!V#]P!Y#]P!Z#]P!_#]P~P)uO!V#zO!Y#{O!Z#|O~O|!eO!Tza~O#[$QO~P)uO!V#zO!Y#{O!Z$TO~O|sO#[#[O#b$WO~O#[#_O#b$XO~P/_On!aO#[$YO~O#[#vO!V#]X!Y#]X!Z#]X!_#]X~P)uOf$[O~O!T$]O~O!Z$^O~O!Y#{O!Z$^O~On!aO!V#zO!Y#{O!Z$`O~O#[#vO!V#]P!Y#]P!Z#]P~P)uO!Z$gO!_$fO~O!Z$iO~O!Z$jO~O!Y#{O!Z$jO~O!T$lO~O!Z$nO~O!Z$oO~O!Y#{O!Z$oO~O!V#zO!Y#{O!Z$oO~O!Z$sO!_$fO~Ot$uO!T$vO~O!Z$sO~O!Z$wO~O!Z$yO~O!Y#{O!Z$yO~O!Z$|O~O!Z%PO~Ot$uO~O!T%QO~Ow!c~", stateData: "!!{~O#SOSqOS~OlROm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#YcO#]QO#hXO#iYO~O#ZkO~O!SnO#]qO#_lO#`mO~OlwOnZOoPOpfO{ZO!QsO!VZO!WZO!`rO!jZO#XWO#]QO#hXO#iYOP#VXQ#VXR#VXS#VXT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VX^#VXd#VXe#VXf#VXg#VXh#VXi#VXj#VXt!hX!X!hX#g!hX~O#Y!hX#k!hX!Z!hX!^!hX!_!hX!f!hX~P!wOlwOnZOoPOpfO{ZO!QsO!VZO!WZO!`rO!jZO#XWO#]QO#hXO#iYOP#WXQ#WXR#WXS#WXT#WXU#WXW#WXX#WXY#WXZ#WX[#WX]#WX^#WXd#WXe#WXf#WXg#WXh#WXi#WXj#WXt#WX#g#WX~O#Y#WX#k#WX!X#WX!Z#WX!^#WX!_#WX!f#WX~P%tOPyOQyORzOSzOT}OU!OOW|OX|OY|OZ|O[|O]|O^xOd{Oe{Of{Og{Oh{Oi{Oj!PO~OPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{Ot#UX~O#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~P*ROl!SOm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#Y!QO#]QO#hXO#iYO~OlwOnZOoPO{ZO!QsO!VZO!WZO!jZO#XWO#Y!QO#]QO#hXO#iYO~O#j!_O~P,kOV!aO#Y#WX#k#WX!Z#WX!^#WX!_#WX!f#WX~P&pOP#VXQ#VXR#VXS#VXT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VX^#VXd#VXe#VXf#VXg#VXh#VXi#VXj#VXt#UX~O#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~P.WOt#UX#Y#UX#k#UX!Z#UX!^#UX!_#UX#g#UX!f#UX~OT}OU!OOj!PO~P0UOV!aO_!bO`!bOa!bOb!bOc!bOk!bO~O!X!cO~P0UOt!fO#Y!eO#k!eO~Ol!hO!Q!jO!X!OP~Ol!nOnZOoPO{ZO!VZO!WZO!jZO#XWO#]QO#hXO#iYO~OlwOnZOoPO{ZO!VZO!WZO!jZO#XWO#]QO#hXO#iYO~O!X!uO~Ol!yO{!yO#XWO~Ol!zO#XWO~O#]!{O#_!{O#`!{O#a!{O#b!{O#c!{O~O!SnO#]!}O#_lO#`mO~OpfO!`#OO~P2xOpfO!QsO!`rOt!da!X!da#Y!da#k!da#g!da!Z!da!^!da!_!da!f!da~P2xO#Y!QO~P!wO#Y!QO~P%tO#Y!QO#g#gO~P*RO#g#gO~O#g#gOt#UX~O!X!cO#g#gOt#UX~O#g#gO~P.WOT}OU!OOj!PO#Y!QOt#UX~O#g#gO~P7WOt!fO~O#j#iO~P,kO!QsO#Y#kO#j#mO~O#Y#nO#j#iO~P2xOlROm_OnZOoPOpfOvhO{ZO!VZO!WZO!cgO!jZO!oiO!qjO#XWO#]QO#hXO#iYO~O#Y#sO~P8cOt!fO#Ysa#ksa#gsa!Zsa!^sa!_sa!fsa~Ol!hO!Q!jO!X!OX~O{#yO!V#yO!W#yO#]QO~O!X#{O~OpfO!QsO!`rOT#VXU#VXW#VXX#VXY#VXZ#VX[#VX]#VXj#VX!X#VX~P2xOT}OU!OOj!PO!X#eX~OT}OU!OOW|OX|OY|OZ|O[|O]|Oj!PO~O!X#eX~P<WO!X#|O~O!X#}O~P<WOT}OU!OOj!PO!X#}O~Ot!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~P(qOPyOQyORzOSzOd{Oe{Of{Og{Oh{Oi{O~Ot!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~P>UOT}OU!OOj!POt!pa#Y!pa#k!pa!Z!pa!^!pa!_!pa#g!pa!f!pa~O^xOR!iiS!iid!iie!iif!iig!iih!iii!iit!ii#Y!ii#k!ii#g!ii!Z!ii!^!ii!_!ii!f!ii~OP!iiQ!ii~P@XOPyOQyO~P@XOPyOQyOd!iie!iif!iig!iih!iii!iit!ii#Y!ii#k!ii#g!ii!Z!ii!^!ii!_!ii!f!ii~OR!iiS!ii~PAtORzOSzO^xO~PAtORzOSzO~PAtOW|OX|OY|OZ|O[|O]|OTwijwitwi#Ywi#kwi#gwi!Xwi!Zwi!^wi!_wi!fwi~OU!OO~PCkOU!OO~PC}OUwi~PCkOT}OU!OOjwitwi#Ywi#kwi#gwi!Xwi!Zwi!^wi!_wi!fwi~OW|OX|OY|OZ|O[|O]|O~PEXO#Y!QO#g$QO~P*RO#g$QO~O#g$QOt#UX~O!X!cO#g$QOt#UX~O#g$QO~P.WO#g$QO~P7WOpfO!`rO~P,kO#Y!QO#g$QO~O!QsO#Y#kO#j$TO~O#Y#nO#j$VO~P2xOt!fO#Y!si#k!si!Z!si!^!si!_!si#g!si!f!si~Ot!fO#Y!ri#k!ri!Z!ri!^!ri!_!ri#g!ri!f!ri~Ot!fO!Z![X!^![X!_![X!f![X~O#Y$YO!Z#dP!^#dP!_#dP!f#dP~P8cO!Z$^O!^$_O!_$`O~O!Q!jO!X!Oa~O#Y$dO~P8cO!Z$^O!^$_O!_$gO~O#Y!QO#g$jO~O#Y!QO#gyi~O!QsO#Y#kO#j$mO~O#Y#nO#j$nO~P2xOt!fO#Y$oO~O#Y$YO!Z#dX!^#dX!_#dX!f#dX~P8cOl$qO~O!X$rO~O!_$sO~O!^$_O!_$sO~Ot!fO!Z$^O!^$_O!_$uO~O#Y$YO!Z#dP!^#dP!_#dP~P8cO!_$|O!f${O~O!_%OO~O!_%PO~O!^$_O!_%PO~OpfO!`rO#gyq~P,kO#Y!QO#gyq~O!X%UO~O!_%WO~O!_%XO~O!^$_O!_%XO~O!Z$^O!^$_O!_%XO~O!_%]O!f${O~O!X%`O!c%_O~O!_%]O~O!_%aO~OpfO!`rO#gyy~P,kO!_%dO~O!^$_O!_%dO~O!_%gO~O!_%jO~O!X%kO~O{!j~",
goto: "4z#_PPPPPPPPPPPPPPPPPPPPPPPPPPPPP#`P#vP$[%V#`P&Z&sP's'y(j(mP(sP)r)rPPP)vP*S*lPPP+S+fP+j+p,UP,u-q#v#vP#vP#v#v.o.u/R/Z/a/k/q/x0O0U0`PPP0j0n1b2|P3|P4SP4YPPPPPP4^4dr`Oe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QR!QWu`OWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%Qr^Oe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QQ!TWS!jg$uQ!ohQ!sjQ#P}Q#R|R#U!OxSOWeg![!]!^!a!p#d#l#m#n#x$Q$]$l$u$v%QxZRSYhjsvxyz{|}!O!W!Z!i#Z#`#rQ!tkR!ultTOWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QT!lg$utROWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QxwRSYhjsvxyz{|}!O!W!Z!i#Z#`#rT!ig$uXtRSv!ir`Oe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QWrRSv!iQ!QWR!ysR!hfX!ff!d!g#i!rZORSWYeghjsvxyz{|}!O!W!Z![!]!^!a!i!p#Z#`#d#l#m#n#r#x$Q$]$l$u$v%QR#j!eTnQpQ$O#eQ$V#oQ$b$PR$q$cQ#e!^Q#o!pQ$R#mQ$S#nQ$m$]Q$x$lQ%O$vR%R%QQ#}#eQ$U#oQ$_$OQ$a$PQ$k$VS$p$b$cR$z$qWtRSv!iQ!XYQ#X!WX#[!X#X#]#pT$d$R$eQ$h$RR$t$euTOWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QrVOe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QQ!PWQ!rjQ!{yR#Oz!sZORSWYeghjsvxyz{|}!O!W!Z![!]!^!a!i!p#Z#`#d#l#m#n#r#x$Q$]$l$u$v%Q|ZRSYghjsvxyz{|}!O!W!Z!i#Z#`#r$uu[OWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QQeOR!be^!_b!U#a#b#c#w$PR#f!_UvRS!iR!zvQ!dfR#h!dQ!gfQ#i!dT#k!g#iQpQR!wpS#x#d$QR$Z#xQ$e$RR$r$eQ!WYR#W!WQ#]!XQ#p#XT#t#]#pQ#`!ZQ#r#ZT#u#`#rTdOeSbOeQ!UWQ#a![Q#b!]`#c!^!p#m#n$]$l$v%QQ#g!aU#w#d#x$QR$P#ltUOWe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QWrRSv!iQ!ZYS!kg$uQ!nhQ!qjQ!ysQ!{xQ!|yQ!}zQ#P{Q#Q|Q#S}Q#T!OQ#Z!WX#_!Z#Z#`#rr]Oe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%Q|wRSYghjsvxyz{|}!O!W!Z!i#Z#`#r$uR!SWQ!mgR$}$uXuRSv!iToQpQ#y#dR$c$QraOe![!]!^!a!p#d#l#m#n#x$Q$]$l$v%QR!RW", goto: "8f#gPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#hP$RP$h%f&t&zP(U(b)[)_P)eP*l*lPPP*pP*|+fPPP+|#hP,f-PP-T-Z-pP.g/k$R$RP$RP$R$R0q0w1T1w1}2X2_2f2l2v2|3WPPP3b3f4Z6PPPP7ZP7kPPPPP7o7u7{r`Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!WWR#a!Rw`OWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kr^Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!ZWS!og%_Q!thQ!xjQ#W!OQ#Y}Q#]!PR#d!RvSOeg!a!b!c!f!u#s#{#|#}$[$d$r%U%_%`%k!WZRSYhjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%bS!TW!RQ!ykR!zlQ!VWR#`!RrROe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%k!WwRSYhjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%bS!SW!RT!ng%_etRSv!S!T!n#e$k%S%br`Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kdrRSv!S!T!n#e$k%S%bQ!WWQ#OsR#a!RR!mfX!kf!i!l#x#SZORSWYeghjsvxyz{|}!O!P!R!S!T!]!`!a!b!c!f!n!u#e#j#o#s#{#|#}$U$[$d$k$r%S%U%_%`%b%kR#y!jTnQpQ$b#tQ$i$OQ$w$cR%Z$xQ#t!cQ$O!uQ$e#|Q$f#}Q%V$rQ%c%UQ%i%`R%l%kQ$a#tQ$h$OQ$t$bQ$v$cQ%Q$iS%Y$w$xR%e%ZdtRSv!S!T!n#e$k%S%bQ!^YQ#h!]X#k!^#h#l$SvTOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kT!qg%_T$y$e$zQ$}$eR%^$zwTOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%krVOe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!UWQ!wjQ#QyQ#TzQ#V{R#_!R#TZORSWYeghjsvxyz{|}!O!P!R!S!T!]!`!a!b!c!f!n!u#e#j#o#s#{#|#}$U$[$d$k$r%S%U%_%`%b%k![ZRSYghjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%_%bw[OWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQeOR!ge^!db![#p#q#r$Z$cR#u!dQ!RWQ!]Y`#^!R!]#e#f$P$k%S%bS#e!S!TS#f!U!ZS$P#_#dQ$k$RR%S$lQ!ifR#w!iQ!lfQ#x!iT#z!l#xQpQR!|pS$[#s$dR$p$[Q$l$RR%T$lYvRS!S!T!nR#PvQ$z$eR%[$zQ#l!^Q$S#hT$W#l$SQ#o!`Q$U#jT$X#o$UTdOeSbOeS![W!RQ#p!aQ#q!b`#r!c!u#|#}$r%U%`%kQ#v!fU$Z#s$[$dR$c#{vUOWe!R!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kdrRSv!S!T!n#e$k%S%bQ!`YS!pg%_Q!shQ!vjQ#OsQ#QxQ#RyQ#SzQ#U{Q#W|Q#X}Q#Z!OQ#[!PQ#j!]X#n!`#j#o$Ur]Oe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%k![wRSYghjsvxyz{|}!O!P!S!T!]!`!n#e#j#o$U$k%S%_%bQ!YWR#c!R[uRSv!S!T!nQ$R#eV%R$k%S%bToQpQ$]#sR$x$dQ!rgR%h%_raOe!a!b!c!f!u#s#{#|#}$[$d$r%U%`%kQ!XWR#b!R",
nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq NullishCoalesce NullishEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr IfExpr keyword FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg ElseIfExpr keyword ElseExpr FunctionCallOrIdentifier BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword CompoundAssign Assign", nodeNames: "⚠ Star Slash Plus Minus And Or Eq EqEq Neq Lt Lte Gt Gte Modulo PlusEq MinusEq StarEq SlashEq ModuloEq Band Bor Bxor Shl Shr Ushr NullishCoalesce NullishEq Identifier AssignableIdentifier Word IdentifierBeforeDot Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg IfExpr keyword FunctionCall ElseIfExpr keyword ElseExpr FunctionCallOrIdentifier BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword CompoundAssign Assign",
maxTerm: 111, maxTerm: 119,
context: trackScope, context: trackScope,
nodeProps: [ nodeProps: [
["closedBy", 51,"end"] ["closedBy", 55,"end"]
], ],
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0,27], skippedNodes: [0,33],
repeatNodeCount: 11, repeatNodeCount: 12,
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$QU!OSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qU!OS!zYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[U!OS#[QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZkY!OSOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mSkYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#V~~'aO#T~U'hU!OS#PQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RU!OS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jW!OSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZY!OSwQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OW!OSOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oW!OSwQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^!OSOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[U|Q!OSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sW!OSOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^!OSOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^!OS!cQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fX!cQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^U!cQ#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;=`<%l0aU3ZW!OSOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zb!OS!cQOt#{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[!OSOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bU!OS!TQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#aQ!OSOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jV!OSOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#`Q!OSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#W~U8vU#bQ!OSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aU!OS![QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^!OSOt#{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^!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#X,Y#X#Y=t#Y#o,Y#o;'S#{;'S;=`$d<%lO#{U={[!RQ!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^>x[#XW!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#ZW!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#YW!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^!OSOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j<s#j#o,Y#o;'S#{;'S;=`$d<%lO#{UBvUnQ!OSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C_O#c~", tokenData: "IS~R}OX$OXY$mYZ%WZp$Opq$mqs$Ost%qtu'Yuw$Owx'_xy'dyz'}z{$O{|(h|}$O}!O(h!O!P$O!P!Q0o!Q!R)Y!R![+w![!]9[!]!^%W!^!}$O!}#O9u#O#P;k#P#Q;p#Q#R$O#R#S<Z#S#T$O#T#Y<t#Y#Z>`#Z#b<t#b#cC|#c#f<t#f#gEP#g#h<t#h#iFS#i#o<t#o#p$O#p#qHd#q;'S$O;'S;=`$g<%l~$O~O$O~~H}S$TU!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OS$jP;=`<%l$O^$tU!SS#SYOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU%_U!SS#YQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O^%xZqY!SSOY%qYZ$OZt%qtu&kuw%qwx&kx#O%q#O#P&k#P;'S%q;'S;=`'S<%lO%qY&pSqYOY&kZ;'S&k;'S;=`&|<%lO&kY'PP;=`<%l&k^'VP;=`<%l%q~'_O#_~~'dO#]~U'kU!SS#XQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(UU!SS#gQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU(mX!SSOt$Ouw$Ox!Q$O!Q!R)Y!R![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU)a`!SS{QOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S#U$O#U#V-c#V#l$O#l#m.w#m;'S$O;'S;=`$g<%lO$OU*hW!SSOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P;'S$O;'S;=`$g<%lO$OU+XY!SS{QOt$Ouw$Ox!Q$O!Q![+Q![#O$O#P#R$O#R#S*c#S;'S$O;'S;=`$g<%lO$OU,O[!SS{QOt$Ouw$Ox!O$O!O!P*c!P!Q$O!Q![+w![#O$O#P#R$O#R#S,t#S;'S$O;'S;=`$g<%lO$OU,yW!SSOt$Ouw$Ox!Q$O!Q![+w![#O$O#P;'S$O;'S;=`$g<%lO$OU-hX!SSOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.[X!SS{QOt$Ouw$Ox!Q$O!Q!R.T!R!S.T!S#O$O#P;'S$O;'S;=`$g<%lO$OU.|[!SSOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU/y[!SS{QOt$Ouw$Ox!Q$O!Q![/r![!c$O!c!i/r!i#O$O#P#T$O#T#Z/r#Z;'S$O;'S;=`$g<%lO$OU0tW!SSOt$Ouw$Ox!P$O!P!Q1^!Q#O$O#P;'S$O;'S;=`$g<%lO$OU1c^!SSOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q$O!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_U2f^!SS!jQOY2_YZ$OZt2_tu3buw2_wx3bx!P2_!P!Q6V!Q!}2_!}#O8T#O#P5p#P;'S2_;'S;=`9U<%lO2_Q3gX!jQOY3bZ!P3b!P!Q4S!Q!}3b!}#O4q#O#P5p#P;'S3b;'S;=`6P<%lO3bQ4VP!P!Q4YQ4_U!jQ#Z#[4Y#]#^4Y#a#b4Y#g#h4Y#i#j4Y#m#n4YQ4tVOY4qZ#O4q#O#P5Z#P#Q3b#Q;'S4q;'S;=`5j<%lO4qQ5^SOY4qZ;'S4q;'S;=`5j<%lO4qQ5mP;=`<%l4qQ5sSOY3bZ;'S3b;'S;=`6P<%lO3bQ6SP;=`<%l3bU6[W!SSOt$Ouw$Ox!P$O!P!Q6t!Q#O$O#P;'S$O;'S;=`$g<%lO$OU6{b!SS!jQOt$Ouw$Ox#O$O#P#Z$O#Z#[6t#[#]$O#]#^6t#^#a$O#a#b6t#b#g$O#g#h6t#h#i$O#i#j6t#j#m$O#m#n6t#n;'S$O;'S;=`$g<%lO$OU8Y[!SSOY8TYZ$OZt8Ttu4quw8Twx4qx#O8T#O#P5Z#P#Q2_#Q;'S8T;'S;=`9O<%lO8TU9RP;=`<%l8TU9XP;=`<%l2_U9cU!SS!XQOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU9|W#iQ!SSOt$Ouw$Ox!_$O!_!`:f!`#O$O#P;'S$O;'S;=`$g<%lO$OU:kV!SSOt$Ouw$Ox#O$O#P#Q;Q#Q;'S$O;'S;=`$g<%lO$OU;XU#hQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~;pO#`~U;wU#jQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<bU!SS!`QOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU<y^!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$OU=|U!QQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$OU>e_!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#U?d#U#o<t#o;'S$O;'S;=`$g<%lO$OU?i`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#`<t#`#a@k#a#o<t#o;'S$O;'S;=`$g<%lO$OU@p`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#g<t#g#hAr#h#o<t#o;'S$O;'S;=`$g<%lO$OUAw`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#X<t#X#YBy#Y#o<t#o;'S$O;'S;=`$g<%lO$OUCQ^!VQ!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^DT^#aW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^EW^#cW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#o<t#o;'S$O;'S;=`$g<%lO$O^FZ`#bW!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#f<t#f#gG]#g#o<t#o;'S$O;'S;=`$g<%lO$OUGb`!SSOt$Ouw$Ox}$O}!O<t!O!Q$O!Q![<t![!_$O!_!`=u!`#O$O#P#T$O#T#i<t#i#jAr#j#o<t#o;'S$O;'S;=`$g<%lO$OUHkUtQ!SSOt$Ouw$Ox#O$O#P;'S$O;'S;=`$g<%lO$O~ISO#k~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#R~~", 11)], tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#Z~~", 11)],
topRules: {"Program":[0,28]}, topRules: {"Program":[0,34]},
specialized: [{term: 22, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 22, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}], specialized: [{term: 28, get: (value: any, stack: any) => (specializeKeyword(value, stack) << 1), external: specializeKeyword},{term: 28, get: (value: keyof typeof spec_Identifier) => spec_Identifier[value] || -1}],
tokenPrec: 1730 tokenPrec: 2202
}) })

View File

@ -368,6 +368,118 @@ describe('Parentheses', () => {
PositionalArg PositionalArg
Number 3`) Number 3`)
}) })
test('function call with named args on multiple lines in parens', () => {
expect(`(tail
arg1=true
arg2=30
)`).toMatchTree(`
ParenExpr
FunctionCall
Identifier tail
NamedArg
NamedArgPrefix arg1=
Boolean true
NamedArg
NamedArgPrefix arg2=
Number 30
`)
expect(`(
tail
arg1=true
arg2=30
)`).toMatchTree(`
ParenExpr
FunctionCall
Identifier tail
NamedArg
NamedArgPrefix arg1=
Boolean true
NamedArg
NamedArgPrefix arg2=
Number 30
`)
})
test('binop with newlines in parens', () => {
expect(`(
1 + 2
)`).toMatchTree(`
ParenExpr
BinOp
Number 1
Plus +
Number 2`)
})
test('comparison with newlines in parens', () => {
expect(`(
1 < 2
)`).toMatchTree(`
ParenExpr
ConditionalOp
Number 1
Lt <
Number 2`)
})
test('function call with multiple identifiers on separate lines in parens', () => {
expect(`(echo
arg1
arg2
arg3
)`).toMatchTree(`
ParenExpr
FunctionCall
Identifier echo
PositionalArg
Identifier arg1
PositionalArg
Identifier arg2
PositionalArg
Identifier arg3`)
})
})
describe('Number literals', () => {
test('allows underscores in integer literals', () => {
expect('10_000').toMatchTree(`Number 10_000`)
expect('1_000_000').toMatchTree(`Number 1_000_000`)
expect('100_000').toMatchTree(`Number 100_000`)
})
test('allows underscores in decimal literals', () => {
expect('3.14_159').toMatchTree(`Number 3.14_159`)
expect('1_000.50').toMatchTree(`Number 1_000.50`)
expect('0.000_001').toMatchTree(`Number 0.000_001`)
})
test('allows underscores in negative numbers', () => {
expect('-10_000').toMatchTree(`Number -10_000`)
expect('-3.14_159').toMatchTree(`Number -3.14_159`)
})
test('allows underscores in positive numbers with explicit sign', () => {
expect('+10_000').toMatchTree(`Number +10_000`)
expect('+3.14_159').toMatchTree(`Number +3.14_159`)
})
test('works in expressions', () => {
expect('1_000 + 2_000').toMatchTree(`
BinOp
Number 1_000
Plus +
Number 2_000`)
})
test('works in function calls', () => {
expect('echo 10_000').toMatchTree(`
FunctionCall
Identifier echo
PositionalArg
Number 10_000`)
})
}) })
describe('BinOp', () => { describe('BinOp', () => {

View File

@ -0,0 +1,72 @@
import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('bitwise operators - grammar', () => {
test('parses band (bitwise AND)', () => {
expect('5 band 3').toMatchTree(`
BinOp
Number 5
Band band
Number 3`)
})
test('parses bor (bitwise OR)', () => {
expect('5 bor 3').toMatchTree(`
BinOp
Number 5
Bor bor
Number 3`)
})
test('parses bxor (bitwise XOR)', () => {
expect('5 bxor 3').toMatchTree(`
BinOp
Number 5
Bxor bxor
Number 3`)
})
test('parses << (left shift)', () => {
expect('5 << 2').toMatchTree(`
BinOp
Number 5
Shl <<
Number 2`)
})
test('parses >> (signed right shift)', () => {
expect('20 >> 2').toMatchTree(`
BinOp
Number 20
Shr >>
Number 2`)
})
test('parses >>> (unsigned right shift)', () => {
expect('-1 >>> 1').toMatchTree(`
BinOp
Number -1
Ushr >>>
Number 1`)
})
test('parses bnot (bitwise NOT) as function call', () => {
expect('bnot 5').toMatchTree(`
FunctionCall
Identifier bnot
PositionalArg
Number 5`)
})
test('bitwise operators work in expressions', () => {
expect('x = 5 band 3').toMatchTree(`
Assign
AssignableIdentifier x
Eq =
BinOp
Number 5
Band band
Number 3`)
})
})

View File

@ -2,6 +2,65 @@ import { expect, describe, test } from 'bun:test'
import '../shrimp.grammar' // Importing this so changes cause it to retest! import '../shrimp.grammar' // Importing this so changes cause it to retest!
describe('number literals', () => {
test('binary numbers', () => {
expect('0b110').toMatchTree(`
Number 0b110
`)
})
test('hex numbers', () => {
expect('0xdeadbeef').toMatchTree(`
Number 0xdeadbeef
`)
})
test('hex numbers uppercase', () => {
expect('0xFF').toMatchTree(`
Number 0xFF
`)
})
test('decimal numbers still work', () => {
expect('42').toMatchTree(`
Number 42
`)
})
test('negative binary', () => {
expect('-0b110').toMatchTree(`
Number -0b110
`)
})
test('negative hex', () => {
expect('-0xFF').toMatchTree(`
Number -0xFF
`)
})
test('positive prefix binary', () => {
expect('+0b110').toMatchTree(`
Number +0b110
`)
})
test('positive prefix hex', () => {
expect('+0xFF').toMatchTree(`
Number +0xFF
`)
})
test('hex and binary in arrays', () => {
expect('[0xFF 0b110 42]').toMatchTree(`
Array
Number 0xFF
Number 0b110
Number 42
`)
})
})
describe('array literals', () => { describe('array literals', () => {
test('work with numbers', () => { test('work with numbers', () => {
expect('[1 2 3]').toMatchTree(` expect('[1 2 3]').toMatchTree(`

View File

@ -6,6 +6,7 @@ import {
} from 'reefvm' } from 'reefvm'
import { dict } from './dict' import { dict } from './dict'
import { json } from './json'
import { load } from './load' import { load } from './load'
import { list } from './list' import { list } from './list'
import { math } from './math' import { math } from './math'
@ -13,6 +14,7 @@ import { str } from './str'
export const globals = { export const globals = {
dict, dict,
json,
load, load,
list, list,
math, math,
@ -40,6 +42,7 @@ export const globals = {
'var?': function (this: VM, v: string) { 'var?': function (this: VM, v: string) {
return typeof v !== 'string' || this.scope.has(v) return typeof v !== 'string' || this.scope.has(v)
}, },
ref: (fn: Function) => fn,
// env // env
args: Bun.argv.slice(1), args: Bun.argv.slice(1),
@ -60,6 +63,7 @@ export const globals = {
// boolean/logic // boolean/logic
not: (v: any) => !v, not: (v: any) => !v,
bnot: (n: number) => ~(n | 0),
// utilities // utilities
inc: (n: number) => n + 1, inc: (n: number) => n + 1,

7
src/prelude/json.ts Normal file
View File

@ -0,0 +1,7 @@
export const json = {
encode: (s: any) => JSON.stringify(s),
decode: (s: string) => JSON.parse(s),
}
; (json as any).parse = json.decode
; (json as any).stringify = json.encode

View File

@ -73,6 +73,13 @@ export const list = {
const realItems = items.map(item => item.value) const realItems = items.map(item => item.value)
return toValue(realList.splice(realStart, realDeleteCount, ...realItems)) return toValue(realList.splice(realStart, realDeleteCount, ...realItems))
}, },
insert: (list: Value, index: Value, item: Value) => {
if (list.type !== 'array') return toNull()
const realList = list.value as any[]
const realIndex = index.value as number
realList.splice(realIndex, 0, item)
return toValue(realList.length)
},
// sequence operations // sequence operations
reverse: (list: any[]) => list.slice().reverse(), reverse: (list: any[]) => list.slice().reverse(),
@ -143,3 +150,4 @@ export const list = {
; (list.pop as any).raw = true ; (list.pop as any).raw = true
; (list.shift as any).raw = true ; (list.shift as any).raw = true
; (list.unshift as any).raw = true ; (list.unshift as any).raw = true
; (list.insert as any).raw = true

View File

@ -91,3 +91,15 @@ describe('environment', () => {
await expect(`list.first args | str.ends-with? 'shrimp.test.ts'`).toEvaluateTo(true) await expect(`list.first args | str.ends-with? 'shrimp.test.ts'`).toEvaluateTo(true)
}) })
}) })
describe('ref', () => {
expect(`rnd = do x: true end; rnd | type`).toEvaluateTo('boolean')
expect(`rnd = do x: true end; ref rnd | type`).toEvaluateTo('function')
expect(`math.random | type`).toEvaluateTo('number')
expect(`ref math.random | type`).toEvaluateTo('native')
expect(`rnd = math.random; rnd | type`).toEvaluateTo('number')
expect(`rnd = ref math.random; rnd | type`).toEvaluateTo('number')
expect(`rnd = ref math.random; ref rnd | type`).toEvaluateTo('native')
})

View File

@ -0,0 +1,84 @@
import { expect, describe, test } from 'bun:test'
describe('json', () => {
test('json.decode', () => {
expect(`json.decode '[1,2,3]'`).toEvaluateTo([1, 2, 3])
expect(`json.decode '"heya"'`).toEvaluateTo('heya')
expect(`json.decode '[true, false, null]'`).toEvaluateTo([true, false, null])
expect(`json.decode '{"a": true, "b": false, "c": "yeah"}'`).toEvaluateTo({ a: true, b: false, c: "yeah" })
})
test('json.encode', () => {
expect(`json.encode [1 2 3]`).toEvaluateTo('[1,2,3]')
expect(`json.encode 'heya'`).toEvaluateTo('"heya"')
expect(`json.encode [true false null]`).toEvaluateTo('[true,false,null]')
expect(`json.encode [a=true b=false c='yeah'] | json.decode`).toEvaluateTo({ a: true, b: false, c: "yeah" })
})
test('edge cases - empty structures', () => {
expect(`json.decode '[]'`).toEvaluateTo([])
expect(`json.decode '{}'`).toEvaluateTo({})
expect(`json.encode []`).toEvaluateTo('[]')
expect(`json.encode [=]`).toEvaluateTo('{}')
})
test('edge cases - special characters in strings', () => {
expect(`json.decode '"hello\\\\nworld"'`).toEvaluateTo('hello\nworld')
expect(`json.decode '"tab\\\\there"'`).toEvaluateTo('tab\there')
expect(`json.decode '"forward/slash"'`).toEvaluateTo('forward/slash')
expect(`json.decode '"with\\\\\\\\backslash"'`).toEvaluateTo('with\\backslash')
})
test('numbers - integers and floats', () => {
expect(`json.decode '42'`).toEvaluateTo(42)
expect(`json.decode '0'`).toEvaluateTo(0)
expect(`json.decode '-17'`).toEvaluateTo(-17)
expect(`json.decode '3.14159'`).toEvaluateTo(3.14159)
expect(`json.decode '-0.5'`).toEvaluateTo(-0.5)
})
test('numbers - scientific notation', () => {
expect(`json.decode '1e10'`).toEvaluateTo(1e10)
expect(`json.decode '2.5e-3'`).toEvaluateTo(2.5e-3)
expect(`json.decode '1.23E+5'`).toEvaluateTo(1.23e5)
})
test('unicode - emoji and special characters', () => {
expect(`json.decode '"hello 👋"'`).toEvaluateTo('hello 👋')
expect(`json.decode '"🎉🚀✨"'`).toEvaluateTo('🎉🚀✨')
expect(`json.encode '你好'`).toEvaluateTo('"你好"')
expect(`json.encode 'café'`).toEvaluateTo('"café"')
})
test('nested structures - arrays', () => {
expect(`json.decode '[[1,2],[3,4],[5,6]]'`).toEvaluateTo([[1, 2], [3, 4], [5, 6]])
expect(`json.decode '[1,[2,[3,[4]]]]'`).toEvaluateTo([1, [2, [3, [4]]]])
})
test('nested structures - objects', () => {
expect(`json.decode '{"user":{"name":"Alice","age":30}}'`).toEvaluateTo({
user: { name: 'Alice', age: 30 }
})
expect(`json.decode '{"a":{"b":{"c":"deep"}}}'`).toEvaluateTo({
a: { b: { c: 'deep' } }
})
})
test('nested structures - mixed arrays and objects', () => {
expect(`json.decode '[{"id":1,"tags":["a","b"]},{"id":2,"tags":["c"]}]'`).toEvaluateTo([
{ id: 1, tags: ['a', 'b'] },
{ id: 2, tags: ['c'] }
])
expect(`json.decode '{"items":[1,2,3],"meta":{"count":3}}'`).toEvaluateTo({
items: [1, 2, 3],
meta: { count: 3 }
})
})
test('error handling - invalid json', () => {
expect(`json.decode '{invalid}'`).toFailEvaluation()
expect(`json.decode '[1,2,3'`).toFailEvaluation()
expect(`json.decode 'undefined'`).toFailEvaluation()
expect(`json.decode ''`).toFailEvaluation()
})
})

View File

@ -349,6 +349,22 @@ describe('collections', () => {
await expect(`arr = [1 2 3 4 5]; list.splice arr 3 2; arr`).toEvaluateTo([1, 2, 3]) await expect(`arr = [1 2 3 4 5]; list.splice arr 3 2; arr`).toEvaluateTo([1, 2, 3])
}) })
test('list.insert adds element at index and mutates array', async () => {
await expect(`arr = [1 2 4 5]; list.insert arr 2 3; arr`).toEvaluateTo([1, 2, 3, 4, 5])
})
test('list.insert returns array length', async () => {
await expect(`list.insert [1 2 4] 2 3`).toEvaluateTo(4)
})
test('list.insert at start', async () => {
await expect(`arr = [2 3]; list.insert arr 0 1; arr`).toEvaluateTo([1, 2, 3])
})
test('list.insert at end', async () => {
await expect(`arr = [1 2]; list.insert arr 2 99; arr`).toEvaluateTo([1, 2, 99])
})
test('list.sort with no callback sorts ascending', async () => { test('list.sort with no callback sorts ascending', async () => {
await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5]) await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5])
}) })