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
20 changed files with 1022 additions and 79 deletions

View File

@ -62,7 +62,7 @@
"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=="],

View File

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

View File

@ -51,6 +51,7 @@ function processEscapeSeq(escapeSeq: string): string {
export class Compiler {
instructions: ProgramItem[] = []
labelCount = 0
fnLabelCount = 0
ifLabelCount = 0
tryLabelCount = 0
@ -105,11 +106,21 @@ export class Compiler {
switch (node.type.id) {
case terms.Number:
const number = Number(value)
if (Number.isNaN(number))
// Handle sign prefix for hex and binary literals
// 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)
return [[`PUSH`, number]]
return [[`PUSH`, numberValue]]
case terms.String: {
const { parts, hasInterpolation } = getStringParts(node, input)
@ -232,6 +243,24 @@ export class Compiler {
case '%':
instructions.push(['MOD'])
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:
throw new CompilerError(`Unsupported binary operator: ${opValue}`, op.from, op.to)
}
@ -274,13 +303,31 @@ export class Compiler {
const { identifier, operator, right } = getCompoundAssignmentParts(node)
const identifierName = input.slice(identifier.from, identifier.to)
const instructions: ProgramItem[] = []
const opValue = input.slice(operator.from, operator.to)
// will throw if undefined
instructions.push(['LOAD', identifierName])
// Special handling for ??= since it needs conditional evaluation
if (opValue === '??=') {
instructions.push(['LOAD', identifierName])
const rightInstructions = this.#compileNode(right, input)
instructions.push(['DUP'])
instructions.push(['PUSH', null])
instructions.push(['NEQ'])
instructions.push(['JUMP_IF_TRUE', rightInstructions.length + 1])
instructions.push(['POP'])
instructions.push(...rightInstructions)
instructions.push(['DUP'])
instructions.push(['STORE', identifierName])
return instructions
}
// Standard compound assignments: evaluate both sides, then operate
instructions.push(['LOAD', identifierName]) // will throw if undefined
instructions.push(...this.#compileNode(right, input))
const opValue = input.slice(operator.from, operator.to)
switch (opValue) {
case '+=':
instructions.push(['ADD'])
@ -367,7 +414,29 @@ export class Compiler {
case terms.FunctionCallOrIdentifier: {
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]]
@ -587,6 +656,18 @@ export class Compiler {
break
case '??':
// Nullish coalescing: return left if not null, else right
instructions.push(...leftInstructions)
instructions.push(['DUP'])
instructions.push(['PUSH', null])
instructions.push(['NEQ'])
instructions.push(['JUMP_IF_TRUE', rightInstructions.length + 1])
instructions.push(['POP'])
instructions.push(...rightInstructions)
break
default:
throw new CompilerError(`Unsupported conditional operator: ${opValue}`, op.from, op.to)
}

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', () => {
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', () => {
@ -298,6 +301,23 @@ describe('default params', () => {
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', () => {
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'])
@ -313,3 +333,118 @@ describe('default params', () => {
).toEvaluateTo({ name: 'Jon', age: 21 })
})
})
describe('Nullish coalescing operator (??)', () => {
test('returns left side when not null', () => {
expect('5 ?? 10').toEvaluateTo(5)
})
test('returns right side when left is null', () => {
expect('null ?? 10').toEvaluateTo(10)
})
test('returns left side when left is false', () => {
expect('false ?? 10').toEvaluateTo(false)
})
test('returns left side when left is 0', () => {
expect('0 ?? 10').toEvaluateTo(0)
})
test('returns left side when left is empty string', () => {
expect(`'' ?? 'default'`).toEvaluateTo('')
})
test('chains left to right', () => {
expect('null ?? null ?? 42').toEvaluateTo(42)
expect('null ?? 10 ?? 20').toEvaluateTo(10)
})
test('short-circuits evaluation', () => {
const throwError = () => { throw new Error('Should not evaluate') }
expect('5 ?? throw-error').toEvaluateTo(5, { 'throw-error': throwError })
})
test('works with variables', () => {
expect('x = null; x ?? 5').toEvaluateTo(5)
expect('y = 3; y ?? 5').toEvaluateTo(3)
})
test('works with function calls', () => {
const getValue = () => null
const getDefault = () => 42
// Note: identifiers without parentheses refer to the function, not call it
// Use explicit call syntax to invoke the function
expect('(get-value) ?? (get-default)').toEvaluateTo(42, {
'get-value': getValue,
'get-default': getDefault
})
})
})
describe('Nullish coalescing assignment (??=)', () => {
test('assigns when variable is null', () => {
expect('x = null; x ??= 5; x').toEvaluateTo(5)
})
test('does not assign when variable is not null', () => {
expect('x = 3; x ??= 10; x').toEvaluateTo(3)
})
test('does not assign when variable is false', () => {
expect('x = false; x ??= true; x').toEvaluateTo(false)
})
test('does not assign when variable is 0', () => {
expect('x = 0; x ??= 100; x').toEvaluateTo(0)
})
test('does not assign when variable is empty string', () => {
expect(`x = ''; x ??= 'default'; x`).toEvaluateTo('')
})
test('returns the final value', () => {
expect('x = null; x ??= 5').toEvaluateTo(5)
expect('y = 3; y ??= 10').toEvaluateTo(3)
})
test('short-circuits evaluation when not null', () => {
const throwError = () => { throw new Error('Should not evaluate') }
expect('x = 5; x ??= throw-error; x').toEvaluateTo(5, { 'throw-error': throwError })
})
test('works with expressions', () => {
expect('x = null; x ??= 2 + 3; x').toEvaluateTo(5)
})
test('works with function calls', () => {
const getDefault = () => 42
expect('x = null; x ??= (get-default); x').toEvaluateTo(42, { 'get-default': getDefault })
})
test('throws when variable is undefined', () => {
expect(() => expect('undefined-var ??= 5').toEvaluateTo(null)).toThrow()
})
})
describe('Compound assignment operators', () => {
test('+=', () => {
expect('x = 5; x += 3; x').toEvaluateTo(8)
})
test('-=', () => {
expect('x = 10; x -= 4; x').toEvaluateTo(6)
})
test('*=', () => {
expect('x = 3; x *= 4; x').toEvaluateTo(12)
})
test('/=', () => {
expect('x = 20; x /= 5; x').toEvaluateTo(4)
})
test('%=', () => {
expect('x = 10; x %= 3; x').toEvaluateTo(1)
})
})

View File

@ -1,6 +1,44 @@
import { describe } 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', () => {
test('work with numbers', () => {
expect('[1 2 3]').toEvaluateTo([1, 2, 3])

View File

@ -5,18 +5,28 @@ type Operator = { str: string; tokenName: keyof typeof terms }
const operators: Array<Operator> = [
{ str: 'and', tokenName: 'And' },
{ 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: 'Lte' },
{ str: '!=', tokenName: 'Neq' },
{ str: '==', tokenName: 'EqEq' },
// Compound assignment operators (must come before single-char operators)
{ str: '??=', tokenName: 'NullishEq' },
{ str: '+=', tokenName: 'PlusEq' },
{ str: '-=', tokenName: 'MinusEq' },
{ str: '*=', tokenName: 'StarEq' },
{ str: '/=', tokenName: 'SlashEq' },
{ str: '%=', tokenName: 'ModuloEq' },
// Nullish coalescing (must come before it could be mistaken for other tokens)
{ str: '??', tokenName: 'NullishCoalesce' },
// Single-char operators
{ str: '*', tokenName: 'Star' },
{ str: '=', tokenName: 'Eq' },

View File

@ -6,14 +6,18 @@
@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 }
@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 {
@precedence { Number Regex }
StringFragment { !['\\$]+ }
NamedArgPrefix { $[a-z-]+ "=" }
Number { ("-" | "+")? $[0-9]+ ('.' $[0-9]+)? }
NamedArgPrefix { $[a-z] $[a-z0-9-]* "=" }
Number {
("-" | "+")? "0x" $[0-9a-fA-F]+ |
("-" | "+")? "0b" $[01]+ |
("-" | "+")? $[0-9]+ ("_"? $[0-9]+)* ('.' $[0-9]+ ("_"? $[0-9]+)*)?
}
Boolean { "true" | "false" }
newlineOrSemicolon { "\n" | ";" }
eof { @eof }
@ -44,10 +48,13 @@ null { @specialize[@name=Null]<Identifier, "null"> }
pipe @left,
or @left,
and @left,
nullish @left,
comparison @left,
multiplicative @left,
additive @left,
call
bitwise @left,
call,
functionWithNewlines
}
item {
@ -160,7 +167,8 @@ ConditionalOp {
expression !comparison Gt expression |
expression !comparison Gte expression |
(expression | ConditionalOp) !and And (expression | ConditionalOp) |
(expression | ConditionalOp) !or Or (expression | ConditionalOp)
(expression | ConditionalOp) !or Or (expression | ConditionalOp) |
(expression | ConditionalOp) !nullish NullishCoalesce (expression | ConditionalOp)
}
Params {
@ -176,7 +184,7 @@ Assign {
}
CompoundAssign {
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq) consumeToTerminator
AssignableIdentifier (PlusEq | MinusEq | StarEq | SlashEq | ModuloEq | NullishEq) consumeToTerminator
}
BinOp {
@ -184,11 +192,31 @@ BinOp {
(expression | BinOp) !multiplicative Star (expression | BinOp) |
(expression | BinOp) !multiplicative Slash (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 {
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 {

View File

@ -19,48 +19,57 @@ export const
StarEq = 17,
SlashEq = 18,
ModuloEq = 19,
Identifier = 20,
AssignableIdentifier = 21,
Word = 22,
IdentifierBeforeDot = 23,
Do = 24,
Comment = 25,
Program = 26,
PipeExpr = 27,
WhileExpr = 29,
keyword = 70,
ConditionalOp = 31,
ParenExpr = 32,
IfExpr = 33,
FunctionCall = 35,
DotGet = 36,
Number = 37,
PositionalArg = 38,
FunctionDef = 39,
Params = 40,
NamedParam = 41,
NamedArgPrefix = 42,
String = 43,
StringFragment = 44,
Interpolation = 45,
EscapeSeq = 46,
Boolean = 47,
Null = 48,
colon = 49,
CatchExpr = 50,
Block = 52,
FinallyExpr = 53,
Underscore = 56,
NamedArg = 57,
ElseIfExpr = 58,
ElseExpr = 60,
FunctionCallOrIdentifier = 61,
BinOp = 62,
Regex = 63,
Dict = 64,
Array = 65,
FunctionCallWithBlock = 66,
TryExpr = 67,
Throw = 69,
CompoundAssign = 71,
Assign = 72
Band = 20,
Bor = 21,
Bxor = 22,
Shl = 23,
Shr = 24,
Ushr = 25,
NullishCoalesce = 26,
NullishEq = 27,
Identifier = 28,
AssignableIdentifier = 29,
Word = 30,
IdentifierBeforeDot = 31,
Do = 32,
Comment = 33,
Program = 34,
PipeExpr = 35,
WhileExpr = 37,
keyword = 79,
ConditionalOp = 39,
ParenExpr = 40,
FunctionCallWithNewlines = 41,
DotGet = 42,
Number = 43,
PositionalArg = 44,
FunctionDef = 45,
Params = 46,
NamedParam = 47,
NamedArgPrefix = 48,
String = 49,
StringFragment = 50,
Interpolation = 51,
EscapeSeq = 52,
Boolean = 53,
Null = 54,
colon = 55,
CatchExpr = 56,
Block = 58,
FinallyExpr = 59,
Underscore = 62,
NamedArg = 63,
IfExpr = 64,
FunctionCall = 66,
ElseIfExpr = 67,
ElseExpr = 69,
FunctionCallOrIdentifier = 70,
BinOp = 71,
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 {trackScope} from "./parserScopeContext"
import {highlighting} from "./highlight"
const spec_Identifier = {__proto__:null,while:60, if:68, null:96, catch:102, finally:108, end:110, else:118, try:136, throw:140}
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({
version: 14,
states: "9UQYQbOOO!dOpO'#DQO!iOSO'#DXO$_QcO'#DkO&rQcO'#EYOOQ`'#Eh'#EhO'uQRO'#DlO)[QcO'#EWO)lQbO'#C|OOQa'#Dn'#DnO+nQbO'#DoOOQa'#EY'#EYO+uQcO'#EYO+|QcO'#EXO,yQcO'#EWO-TQRO'#DuOOQ`'#EW'#EWO-iQbO'#EWO-pQQO'#EVOOQ`'#EV'#EVOOQ`'#Dw'#DwQYQbOOO-{QbO'#DTO.WQbO'#C}O.{QbO'#CyO/pQQO'#DqO.{QbO'#DsO/uObO,59lO0QQbO'#DZO0YQWO'#D[OOOO'#E`'#E`OOOO'#D|'#D|O0nOSO,59sOOQa,59s,59sOOQ`'#DS'#DSO0|QbO'#DgOOQ`'#E^'#E^OOQ`'#Dy'#DyO1WQbO,59kOOQa'#EX'#EXO.{QbO,5:WO.{QbO,5:WO.{QbO,5:WO.{QbO,59gO.{QbO,59gO.{QbO,59gO2QQRO,59hO2^QQO,59hO2fQQO,59hO2qQRO,59hO3[QRO,59hO3jQQO'#CwOOQ`'#EP'#EPO3oQbO,5:ZO3vQQO,5:YOOQa,5:Z,5:ZO4RQbO,5:ZO)lQbO,5:bO)lQbO,5:aO4]QbO,5:[O4dQbO,59cOOQ`,5:q,5:qO)lQbO'#DxOOQ`-E7u-E7uOOQ`'#Dz'#DzO5OQbO'#DUO5ZQbO'#DVOOQO'#D{'#D{O5RQQO'#DUO5iQQO,59oO5nQcO'#EXO5uQRO'#E[O6lQRO'#E[OOQO'#E['#E[O6sQQO,59iO6xQRO,59eO7PQRO,59eO4]QbO,5:]O7[QcO,5:_O8dQcO,5:_O8tQcO,5:_OOQa1G/W1G/WOOOO,59u,59uOOOO,59v,59vOOOO-E7z-E7zOOQa1G/_1G/_OOQ`,5:R,5:ROOQ`-E7w-E7wOOQa1G/r1G/rO9sQcO1G/rO9}QcO1G/rO:XQcO1G/rOOQa1G/R1G/RO<TQcO1G/RO<[QcO1G/RO<cQcO1G/ROOQa1G/S1G/SOOQ`-E7}-E7}O<jQQO1G/tOOQa1G/u1G/uO<uQbO1G/uOOQO'#EQ'#EQO<jQQO1G/tOOQa1G/t1G/tOOQ`'#ER'#ERO<uQbO1G/uO=PQbO1G/|O=kQbO1G/{O>VQbO'#DbO>hQbO'#DbO>{QbO1G/vOOQ`-E7v-E7vOOQ`,5:d,5:dOOQ`-E7x-E7xO?WQQO,59pOOQO,59q,59qOOQO-E7y-E7yO?`QbO1G/ZO4]QbO1G/TO4]QbO1G/PO?gQbO1G/wO?rQQO7+%`OOQa7+%`7+%`O?}QbO7+%aOOQa7+%a7+%aOOQO-E8O-E8OOOQ`-E8P-E8POOQ`'#D}'#D}O@XQQO'#D}O@aQbO'#EgOOQ`,59|,59|O@tQbO'#D`O@yQQO'#DcOOQ`7+%b7+%bOAOQbO7+%bOATQbO7+%bOA]QbO7+$uOAkQbO7+$uOA{QbO7+$oOBTQbO7+$kOOQ`7+%c7+%cOBYQbO7+%cOB_QbO7+%cOOQa<<Hz<<HzOOQa<<H{<<H{OOQ`,5:i,5:iOOQ`-E7{-E7{OBgQQO,59zO4]QbO,59}OOQ`<<H|<<H|OBlQbO<<H|OOQ`<<Ha<<HaOBqQbO<<HaOBvQbO<<HaOCOQbO<<HaOOQ`'#EO'#EOOCZQbO<<HZOCcQbO'#DjOOQ`<<HZ<<HZOCkQbO<<HZOOQ`<<HV<<HVOOQ`<<H}<<H}OCpQbO<<H}O4]QbO1G/fOOQ`1G/i1G/iOOQ`AN>hAN>hOOQ`AN={AN={OCuQbOAN={OCzQbOAN={OOQ`-E7|-E7|OOQ`AN=uAN=uODSQbOAN=uO.WQbO,5:SO4]QbO,5:UOOQ`AN>iAN>iOOQ`7+%Q7+%QOOQ`G23gG23gODXQbOG23gPD^QbO'#DhOOQ`G23aG23aODcQQO1G/nOOQ`1G/p1G/pOOQ`LD)RLD)RO4]QbO7+%YOOQ`<<Ht<<Ht",
stateData: "Dk~O!xOSiOS~OdROe_OfZOgPOhfOnhOrgOuZO!PZO!QZO!aZO!fiO!hjO!}WO#RQO#YcO#^XO#_YO~O#PkO~O|nO#RqO#TlO#UmO~OdwOfZOgPOhfOuZOzsO!PZO!QZO!YrO!aZO!}WO#RQO#^XO#_YOT!{XU!{XW!{XX!{XY!{XZ!{X[!{X]!{X~OP!{XQ!{XR!{XS!{X^!{Xl!_X!R!_X#Y!_X#a!_X#]!_X!T!_X!W!_X!X!_X!]!_X~P!wOP!|XQ!|XR!|XS!|XT!|XU!|XW!|XX!|XY!|XZ!|X[!|X]!|X^!|Xl!|X#Y!|X#a!|X#]!|X!T!|X!W!|X!X!|X!]!|X~OdwOfZOgPOhfOuZOzsO!PZO!QZO!YrO!aZO!}WO#RQO#^XO#_YO!R!|X~P%_OPyOQyORzOSzOT|OU}OW{OX{OY{OZ{O[{O]{O^xO~Ol!zX#Y!zX#a!zX!T!zX!W!zX!X!zX#]!zX!]!zX~OPyOQyORzOSzO~P(pOdROe_OfZOgPOhfOnhOrgOuZO!PZO!QZO!aZO!fiO!hjO!}WO#RQO#^XO#_YO~OdwOfZOgPOuZOzsO!PZO!QZO!aZO!}WO#RQO#Y!UO#^XO#_YO~O#`!XO~P*sOV!ZO~P%_OP!{XQ!{XR!{XS!{XT!{XU!{XW!{XX!{XY!{XZ!{X[!{X]!{X^!{X~P(pOT|OU}O~P(pOV!ZO_![O`![Oa![Ob![Oc![O~O!R!]O~P(pOl!`O#Y!_O#a!_O~Od!bOz!dO!RxP~Od!hOfZOgPOuZO!PZO!QZO!aZO!}WO#RQO#^XO#_YO~OdwOfZOgPOuZO!PZO!QZO!aZO!}WO#RQO#^XO#_YO~O!R!oO~Od!sOu!sO!}WO~Od!tO!}WO~O#R!uO#T!uO#U!uO#V!uO#W!uO#X!uO~O|nO#R!wO#TlO#UmO~OhfO!Y!xO~P.{OhfOzsO!YrOlsa!Rsa#Ysa#asa#]sa!Tsa!Wsa!Xsa!]sa~P.{OPyOQyORzOSzO#]#SOl!zX~O!R!]O#]#SOl!zX~O#]#SOP!{XQ!{XR!{XS!{X^!{Xl!zX~P#sOT|OU}O#]#SOl!zX~Ol!`O~O#`#VO~P*sOzsO#Y#XO#`#ZO~O#Y#[O#`#VO~P.{O#Y#aO~P)lOl!`O#Yka#aka#]ka!Tka!Wka!Xka!]ka~Od!bOz!dO!RxX~Ou#gO!P#gO!Q#gO#RQO~O!R#iO~O!R!{X~P!wOT|OU}O!R#OX~OT|OU}OW{OX{OY{OZ{O[{O]{O~O!R#OX~P6QO!R#jO~O!R#kO~P6QOT|OU}O!R#kO~Ol!ga#Y!ga#a!ga!T!ga!W!ga!X!ga#]!ga!]!ga~P'uOl!ga#Y!ga#a!ga!T!ga!W!ga!X!ga#]!ga!]!ga~OPyOQyORzOSzO~P7xOT|OU}O~P7xO^xOR!`iS!`il!`i#Y!`i#a!`i#]!`i!T!`i!W!`i!X!`i!]!`i~OP!`iQ!`i~P9OOPyOQyO~P9OOPyOQyOR!`iS!`il!`i#Y!`i#a!`i#]!`i!T!`i!W!`i!X!`i!]!`i~OW{OX{OY{OZ{O[{O]{OToiloi#Yoi#aoi#]oi!Roi!Toi!Woi!Xoi!]oi~OU}O~P;POU}O~P;cOUoi~P;POzsO#Y#XO#`#nO~O#Y#[O#`#pO~P.{Ol!`O#Y!ji#a!ji!T!ji!W!ji!X!ji#]!ji!]!ji~Ol!`O#Y!ii#a!ii!T!ii!W!ii!X!ii#]!ii!]!ii~Ol!`O!T!UX!W!UX!X!UX!]!UX~O#Y#sO!T#ZP!W#ZP!X#ZP!]#ZP~P)lO!T#wO!W#xO!X#yO~Oz!dO!Rxa~O#Y#}O~P)lO!T#wO!W#xO!X$QO~OzsO#Y#XO#`$TO~O#Y#[O#`$UO~P.{Ol!`O#Y$VO~O#Y#sO!T#ZX!W#ZX!X#ZX!]#ZX~P)lOd$XO~O!R$YO~O!X$ZO~O!W#xO!X$ZO~Ol!`O!T#wO!W#xO!X$]O~O#Y#sO!T#ZP!W#ZP!X#ZP~P)lO!X$dO!]$cO~O!X$fO~O!X$gO~O!W#xO!X$gO~O!R$iO~O!X$kO~O!X$lO~O!W#xO!X$lO~O!T#wO!W#xO!X$lO~O!X$pO!]$cO~Or$rO!R$sO~O!X$pO~O!X$tO~O!X$vO~O!W#xO!X$vO~O!X$yO~O!X$|O~Or$rO~O!R$}O~Ou!a~",
goto: "4l#]PPPPPPPPPPPPPPPPPPPPPPPPPPP#^P#tP$Y%Q#^P&T&mP'l'r(c(fP(lP)j)jPPP)nP)z*dPPP*z+^P+b+h+|P,m-h#t#tP#tP#t#t.e.k.w/P/V/a/g/n/t/z0UPPP0`0d1W2oP3nP3tP3zPPPPPP4O4Ur`Oe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}R!PWu`OWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}r^Oe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}Q!SWS!ig$rQ!nhQ!rjQ#O}R#Q|xSOWeg!Z![!]!`!o#a#i#j#k#u#}$Y$i$r$s$}vZRSYhjsvxyz{|}!V!Y!h#W#]#oQ!skR!tltTOWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}T!kg$rtROWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}vwRSYhjsvxyz{|}!V!Y!h#W#]#oT!hg$rXtRSv!hr`Oe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}WrRSv!hQ!PWR!xsR!gfX!ef!c!f#f!pZORSWYeghjsvxyz{|}!V!Y!Z![!]!`!h!o#W#]#a#i#j#k#o#u#}$Y$i$r$s$}R#g!dTnQpQ#{#bQ$S#lQ$_#|R$n$`Q#b!]Q#l!oQ$O#jQ$P#kQ$j$YQ$u$iQ${$sR%O$}Q#z#bQ$R#lQ$[#{Q$^#|Q$h$SS$m$_$`R$w$nWtRSv!hQ!WYQ#U!VX#X!W#U#Y#mT$a$O$bQ$e$OR$q$buTOWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}rVOe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}Q!OWQ!qjQ!zyR!}z!qZORSWYeghjsvxyz{|}!V!Y!Z![!]!`!h!o#W#]#a#i#j#k#o#u#}$Y$i$r$s$}zZRSYghjsvxyz{|}!V!Y!h#W#]#o$ru[OWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}QeOR!ae^!^b!T#^#_#`#t#|R#c!^UvRS!hR!yvQ!cfR#e!cQ!ffQ#f!cT#h!f#fQpQR!vpS#u#a#}R$W#uQ$b$OR$o$bQ!VYR#T!VQ#Y!WQ#m#UT#q#Y#mQ#]!YQ#o#WT#r#]#oTdOeSbOeQ!TWQ#^!ZQ#_![`#`!]!o#j#k$Y$i$s$}Q#d!`U#t#a#u#}R#|#itUOWe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}WrRSv!hQ!YYS!jg$rQ!mhQ!pjQ!xsQ!zxQ!{yQ!|zQ#O{Q#P|Q#R}Q#W!VX#[!Y#W#]#or]Oe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}zwRSYghjsvxyz{|}!V!Y!h#W#]#o$rR!RWQ!lgR$z$rXuRSv!hToQpQ#v#aR$`#}raOe!Z![!]!`!o#a#i#j#k#u#}$Y$i$s$}R!QW",
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 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",
maxTerm: 109,
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: "!!{~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: "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 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: 119,
context: trackScope,
nodeProps: [
["closedBy", 49,"end"]
["closedBy", 55,"end"]
],
propSources: [highlighting],
skippedNodes: [0,25],
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$QU|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qU|S!xYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[U|S#YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZiY|SOY%nYZ#{Zt%ntu&huw%nwx&hx#O%n#O#P&h#P;'S%n;'S;=`'P<%lO%nY&mSiYOY&hZ;'S&h;'S;=`&y<%lO&hY&|P;=`<%l&h^'SP;=`<%l%n~'[O#T~~'aO#R~U'hU|S!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RU|S#]QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jW|SOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)ZY|SuQOt#{uw#{x!O#{!O!P)y!P!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U*OW|SOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U*oW|SuQOt#{uw#{x!Q#{!Q![*h![#O#{#P;'S#{;'S;=`$d<%lO#{U+^^|SOt#{uw#{x}#{}!O,Y!O!Q#{!Q![)S![!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U,_[|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{U-[UzQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U-sW|SOt#{uw#{x!P#{!P!Q.]!Q#O#{#P;'S#{;'S;=`$d<%lO#{U.b^|SOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q#{!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^U/e^|S!aQOY/^YZ#{Zt/^tu0auw/^wx0ax!P/^!P!Q3U!Q!}/^!}#O5S#O#P2o#P;'S/^;'S;=`6T<%lO/^Q0fX!aQOY0aZ!P0a!P!Q1R!Q!}0a!}#O1p#O#P2o#P;'S0a;'S;=`3O<%lO0aQ1UP!P!Q1XQ1^U!aQ#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|SOt#{uw#{x!P#{!P!Q3s!Q#O#{#P;'S#{;'S;=`$d<%lO#{U3zb|S!aQOt#{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[|SOY5SYZ#{Zt5Stu1puw5Swx1px#O5S#O#P2Y#P#Q/^#Q;'S5S;'S;=`5}<%lO5SU6QP;=`<%l5SU6WP;=`<%l/^U6bU|S!RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U6{W#_Q|SOt#{uw#{x!_#{!_!`7e!`#O#{#P;'S#{;'S;=`$d<%lO#{U7jV|SOt#{uw#{x#O#{#P#Q8P#Q;'S#{;'S;=`$d<%lO#{U8WU#^Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~8oO#U~U8vU#`Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9aU|S!YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9x]|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#U:q#U#o,Y#o;'S#{;'S;=`$d<%lO#{U:v^|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#`,Y#`#a;r#a#o,Y#o;'S#{;'S;=`$d<%lO#{U;w^|SOt#{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^|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#X,Y#X#Y=t#Y#o,Y#o;'S#{;'S;=`$d<%lO#{U={[!PQ|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^>x[#VW|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^?u[#XW|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#o,Y#o;'S#{;'S;=`$d<%lO#{^@r^#WW|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#f,Y#f#gAn#g#o,Y#o;'S#{;'S;=`$d<%lO#{UAs^|SOt#{uw#{x}#{}!O,Y!O!_#{!_!`-T!`#O#{#P#T#{#T#i,Y#i#j<s#j#o,Y#o;'S#{;'S;=`$d<%lO#{UBvUlQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~C_O#a~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#P~~", 11)],
topRules: {"Program":[0,26]},
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: 1634
skippedNodes: [0,33],
repeatNodeCount: 12,
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#Z~~", 11)],
topRules: {"Program":[0,34]},
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: 2202
})

View File

@ -368,6 +368,118 @@ describe('Parentheses', () => {
PositionalArg
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', () => {
@ -595,6 +707,87 @@ describe('CompoundAssign', () => {
PositionalArg
Number 3`)
})
test('parses ??= operator', () => {
expect('x ??= 5').toMatchTree(`
CompoundAssign
AssignableIdentifier x
NullishEq ??=
Number 5`)
})
test('parses ??= with expression', () => {
expect('config ??= get-default').toMatchTree(`
CompoundAssign
AssignableIdentifier config
NullishEq ??=
FunctionCallOrIdentifier
Identifier get-default`)
})
})
describe('Nullish coalescing operator', () => {
test('? can still end an identifier', () => {
expect('what?').toMatchTree(`
FunctionCallOrIdentifier
Identifier what?`)
})
test('?? can still end an identifier', () => {
expect('what??').toMatchTree(`
FunctionCallOrIdentifier
Identifier what??`)
})
test('?? can still be in a word', () => {
expect('what??the').toMatchTree(`
FunctionCallOrIdentifier
Identifier what??the`)
})
test('?? can still start a word', () => {
expect('??what??the').toMatchTree(`
Word ??what??the`)
})
test('parses ?? operator', () => {
expect('x ?? 5').toMatchTree(`
ConditionalOp
Identifier x
NullishCoalesce ??
Number 5`)
})
test('parses chained ?? operators', () => {
expect('a ?? b ?? c').toMatchTree(`
ConditionalOp
ConditionalOp
Identifier a
NullishCoalesce ??
Identifier b
NullishCoalesce ??
Identifier c`)
})
test('parses ?? with expressions', () => {
expect('get-value ?? default-value').toMatchTree(`
ConditionalOp
Identifier get-value
NullishCoalesce ??
Identifier default-value`)
})
test('parses ?? with parenthesized function call', () => {
expect('get-value ?? (default 10)').toMatchTree(`
ConditionalOp
Identifier get-value
NullishCoalesce ??
ParenExpr
FunctionCall
Identifier default
PositionalArg
Number 10`)
})
})
describe('DotGet whitespace sensitivity', () => {

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!
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', () => {
test('work with numbers', () => {
expect('[1 2 3]').toMatchTree(`

View File

@ -193,6 +193,15 @@ const chooseIdentifierToken = (input: InputStream, stack: Stack): number => {
const nextCh = getFullCodePoint(input, peekPos)
const nextCh2 = getFullCodePoint(input, peekPos + 1)
const nextCh3 = getFullCodePoint(input, peekPos + 2)
// Check for ??= (three-character compound operator)
if (nextCh === 63 /* ? */ && nextCh2 === 63 /* ? */ && nextCh3 === 61 /* = */) {
const charAfterOp = getFullCodePoint(input, peekPos + 3)
if (isWhiteSpace(charAfterOp) || charAfterOp === -1 /* EOF */) {
return AssignableIdentifier
}
}
// Check for compound assignment operators: +=, -=, *=, /=, %=
if (

View File

@ -6,6 +6,7 @@ import {
} from 'reefvm'
import { dict } from './dict'
import { json } from './json'
import { load } from './load'
import { list } from './list'
import { math } from './math'
@ -13,6 +14,7 @@ import { str } from './str'
export const globals = {
dict,
json,
load,
list,
math,
@ -40,6 +42,7 @@ export const globals = {
'var?': function (this: VM, v: string) {
return typeof v !== 'string' || this.scope.has(v)
},
ref: (fn: Function) => fn,
// env
args: Bun.argv.slice(1),
@ -60,6 +63,7 @@ export const globals = {
// boolean/logic
not: (v: any) => !v,
bnot: (n: number) => ~(n | 0),
// utilities
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)
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
reverse: (list: any[]) => list.slice().reverse(),
@ -143,3 +150,4 @@ export const list = {
; (list.pop as any).raw = true
; (list.shift 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)
})
})
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])
})
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 () => {
await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5])
})