Compare commits

...

4 Commits

14 changed files with 368 additions and 77 deletions

View File

@ -7,6 +7,9 @@ import * as readline from 'readline'
import { readFileSync, writeFileSync } from 'fs' import { readFileSync, writeFileSync } from 'fs'
import { basename } from 'path' import { basename } from 'path'
globals.$.script.name = '(repl)'
globals.$.script.path = '(repl)'
async function repl() { async function repl() {
const commands = ['/clear', '/reset', '/vars', '/funcs', '/history', '/bytecode', '/exit', '/save', '/quit'] const commands = ['/clear', '/reset', '/vars', '/funcs', '/history', '/bytecode', '/exit', '/save', '/quit']

View File

@ -1,8 +1,9 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import { colors } from '../src/prelude' import { colors, globals as prelude } from '../src/prelude'
import { treeToString } from '../src/utils/tree' import { treeToString } from '../src/utils/tree'
import { runCode, runFile, compileFile, parseCode } from '../src' import { runCode, runFile, compileFile, parseCode } from '../src'
import { resolve } from 'path'
import { bytecodeToString } from 'reefvm' import { bytecodeToString } from 'reefvm'
import { readFileSync } from 'fs' import { readFileSync } from 'fs'
import { spawn } from 'child_process' import { spawn } from 'child_process'
@ -34,6 +35,9 @@ function showVersion() {
} }
async function evalCode(code: string, imports: string[]) { async function evalCode(code: string, imports: string[]) {
const idx = Bun.argv.indexOf('--')
prelude.$.args = idx >= 0 ? Bun.argv.slice(idx + 1) : []
const importStatement = imports.length > 0 ? `import ${imports.join(' ')}` : '' const importStatement = imports.length > 0 ? `import ${imports.join(' ')}` : ''
if (importStatement) code = `${importStatement}; ${code}` if (importStatement) code = `${importStatement}; ${code}`
return await runCode(code) return await runCode(code)
@ -149,10 +153,12 @@ async function main() {
console.log(`${colors.bright}usage: shrimp run <file>${colors.reset}`) console.log(`${colors.bright}usage: shrimp run <file>${colors.reset}`)
process.exit(1) process.exit(1)
} }
prelude.$.script.path = resolve(file)
await runFile(file) await runFile(file)
return return
} }
prelude.$.script.path = resolve(command)
await runFile(command) await runFile(command)
} }

View File

@ -211,16 +211,37 @@ export class Compiler {
} }
case terms.DotGet: { case terms.DotGet: {
// DotGet is parsed into a nested tree because it's hard to parse it into a flat one.
// However, we want a flat tree - so we're going to pretend like we are getting one from the parser.
//
// This: DotGet(config, DotGet(script, name))
// Becomes: DotGet(config, script, name)
const { objectName, property } = getDotGetParts(node, input) const { objectName, property } = getDotGetParts(node, input)
const instructions: ProgramItem[] = [] const instructions: ProgramItem[] = []
instructions.push(['TRY_LOAD', objectName]) instructions.push(['TRY_LOAD', objectName])
if (property.type.id === terms.ParenExpr) {
instructions.push(...this.#compileNode(property, input)) const flattenProperty = (prop: SyntaxNode): void => {
if (prop.type.id === terms.DotGet) {
const nestedParts = getDotGetParts(prop, input)
const nestedObjectValue = input.slice(nestedParts.object.from, nestedParts.object.to)
instructions.push(['PUSH', nestedObjectValue])
instructions.push(['DOT_GET'])
flattenProperty(nestedParts.property)
} else { } else {
const propertyValue = input.slice(property.from, property.to) if (prop.type.id === terms.ParenExpr) {
instructions.push(...this.#compileNode(prop, input))
} else {
const propertyValue = input.slice(prop.from, prop.to)
instructions.push(['PUSH', propertyValue]) instructions.push(['PUSH', propertyValue])
} }
instructions.push(['DOT_GET']) instructions.push(['DOT_GET'])
}
}
flattenProperty(property)
return instructions return instructions
} }

View File

@ -294,6 +294,43 @@ describe('dot get', () => {
test('use parens expr with dot-get', () => { test('use parens expr with dot-get', () => {
expect(`a = 1; arr = array 'a' 'b' 'c'; arr.(1 + a)`).toEvaluateTo('c', { array }) expect(`a = 1; arr = array 'a' 'b' 'c'; arr.(1 + a)`).toEvaluateTo('c', { array })
}) })
test('chained dot get: two levels', () => {
expect(`obj = [inner=[value=42]]; obj.inner.value`).toEvaluateTo(42)
})
test('chained dot get: three levels', () => {
expect(`obj = [a=[b=[c=123]]]; obj.a.b.c`).toEvaluateTo(123)
})
test('chained dot get: four levels', () => {
expect(`obj = [w=[x=[y=[z='deep']]]]; obj.w.x.y.z`).toEvaluateTo('deep')
})
test('chained dot get with numeric index', () => {
expect(`obj = [items=[1 2 3]]; obj.items.0`).toEvaluateTo(1)
})
test('chained dot get in expression', () => {
expect(`config = [server=[port=3000]]; config.server.port + 1`).toEvaluateTo(3001)
})
test('chained dot get as function argument', () => {
const double = (x: number) => x * 2
expect(`obj = [val=[num=21]]; double obj.val.num`).toEvaluateTo(42, { double })
})
test('chained dot get in binary operation', () => {
expect(`a = [x=[y=10]]; b = [x=[y=20]]; a.x.y + b.x.y`).toEvaluateTo(30)
})
test('chained dot get with parens at end', () => {
expect(`idx = 1; obj = [items=[10 20 30]]; obj.items.(idx)`).toEvaluateTo(20)
})
test('mixed chained and simple dot get', () => {
expect(`obj = [a=1 b=[c=2]]; obj.a + obj.b.c`).toEvaluateTo(3)
})
}) })
describe('default params', () => { describe('default params', () => {

View File

@ -293,7 +293,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
) )
} }
if (object.type.id !== terms.IdentifierBeforeDot) { if (object.type.id !== terms.IdentifierBeforeDot && object.type.id !== terms.Dollar) {
throw new CompilerError( throw new CompilerError(
`DotGet object must be an IdentifierBeforeDot, got ${object.type.name}`, `DotGet object must be an IdentifierBeforeDot, got ${object.type.name}`,
object.from, object.from,
@ -301,9 +301,9 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
) )
} }
if (![terms.Identifier, terms.Number, terms.ParenExpr].includes(property.type.id)) { if (![terms.Identifier, terms.Number, terms.ParenExpr, terms.DotGet].includes(property.type.id)) {
throw new CompilerError( throw new CompilerError(
`DotGet property must be an Identifier or Number, got ${property.type.name}`, `DotGet property must be an Identifier, Number, ParenExpr, or DotGet, got ${property.type.name}`,
property.from, property.from,
property.to property.to
) )
@ -311,7 +311,7 @@ export const getDotGetParts = (node: SyntaxNode, input: string) => {
const objectName = input.slice(object.from, object.to) const objectName = input.slice(object.from, object.to)
return { objectName, property } return { object, objectName, property }
} }
export const getTryExprParts = (node: SyntaxNode, input: string) => { export const getTryExprParts = (node: SyntaxNode, input: string) => {

View File

@ -4,7 +4,7 @@ import { type Tree } from '@lezer/common'
import { Compiler } from '#compiler/compiler' import { Compiler } from '#compiler/compiler'
import { parser } from '#parser/shrimp' import { parser } from '#parser/shrimp'
import { globals as parserGlobals, setGlobals as setParserGlobals } from '#parser/tokenizer' import { globals as parserGlobals, setGlobals as setParserGlobals } from '#parser/tokenizer'
import { globals as shrimpGlobals } from '#prelude' import { globals as prelude } from '#prelude'
export { Compiler } from '#compiler/compiler' export { Compiler } from '#compiler/compiler'
export { parser } from '#parser/shrimp' export { parser } from '#parser/shrimp'
@ -19,7 +19,7 @@ export class Shrimp {
constructor(globals?: Record<string, any>) { constructor(globals?: Record<string, any>) {
const emptyBytecode = { instructions: [], constants: [], labels: new Map() } const emptyBytecode = { instructions: [], constants: [], labels: new Map() }
this.vm = new VM(emptyBytecode, Object.assign({}, shrimpGlobals, globals ?? {})) this.vm = new VM(emptyBytecode, Object.assign({}, prelude, globals ?? {}))
this.globals = globals this.globals = globals
} }
@ -53,7 +53,7 @@ export class Shrimp {
let bytecode let bytecode
if (typeof code === 'string') { if (typeof code === 'string') {
const compiler = new Compiler(code, Object.keys(Object.assign({}, shrimpGlobals, this.globals ?? {}, locals ?? {}))) const compiler = new Compiler(code, Object.keys(Object.assign({}, prelude, this.globals ?? {}, locals ?? {})))
bytecode = compiler.bytecode bytecode = compiler.bytecode
} else { } else {
bytecode = code bytecode = code
@ -79,7 +79,7 @@ export async function runCode(code: string, globals?: Record<string, any>): Prom
} }
export async function runBytecode(bytecode: Bytecode, globals?: Record<string, any>): Promise<any> { export async function runBytecode(bytecode: Bytecode, globals?: Record<string, any>): Promise<any> {
const vm = new VM(bytecode, Object.assign({}, shrimpGlobals, globals)) const vm = new VM(bytecode, Object.assign({}, prelude, globals))
await vm.run() await vm.run()
return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]!, vm) : null return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]!, vm) : null
} }
@ -90,7 +90,7 @@ export function compileFile(path: string, globals?: Record<string, any>): Byteco
} }
export function compileCode(code: string, globals?: Record<string, any>): Bytecode { export function compileCode(code: string, globals?: Record<string, any>): Bytecode {
const globalNames = [...Object.keys(shrimpGlobals), ...(globals ? Object.keys(globals) : [])] const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])]
const compiler = new Compiler(code, globalNames) const compiler = new Compiler(code, globalNames)
return compiler.bytecode return compiler.bytecode
} }
@ -102,7 +102,7 @@ export function parseFile(path: string, globals?: Record<string, any>): Tree {
export function parseCode(code: string, globals?: Record<string, any>): Tree { export function parseCode(code: string, globals?: Record<string, any>): Tree {
const oldGlobals = [...parserGlobals] const oldGlobals = [...parserGlobals]
const globalNames = [...Object.keys(shrimpGlobals), ...(globals ? Object.keys(globals) : [])] const globalNames = [...Object.keys(prelude), ...(globals ? Object.keys(globals) : [])]
setParserGlobals(globalNames) setParserGlobals(globalNames)
const result = parser.parse(code) const result = parser.parse(code)

View File

@ -29,6 +29,7 @@
rightParen { ")" } rightParen { ")" }
colon[closedBy="end", @name="colon"] { ":" } colon[closedBy="end", @name="colon"] { ":" }
Underscore { "_" } Underscore { "_" }
Dollar { "$" }
Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar Regex { "//" (![/\\\n[] | "\\" ![\n] | "[" (![\n\\\]] | "\\" ![\n])* "]")+ ("//" $[gimsuy]*)? } // Stolen from the lezer JavaScript grammar
"|"[@name=operator] "|"[@name=operator]
} }
@ -239,7 +240,8 @@ expression {
@skip {} { @skip {} {
DotGet { DotGet {
IdentifierBeforeDot dot (Number | Identifier | ParenExpr) IdentifierBeforeDot dot (DotGet | Number | Identifier | ParenExpr) |
Dollar dot (DotGet | Number | Identifier | ParenExpr)
} }
String { String {
@ -254,7 +256,7 @@ stringContent {
} }
Interpolation { Interpolation {
"$" Identifier | "$" FunctionCallOrIdentifier |
"$" ParenExpr "$" ParenExpr
} }

View File

@ -37,42 +37,43 @@ export const
Program = 35, Program = 35,
PipeExpr = 36, PipeExpr = 36,
WhileExpr = 38, WhileExpr = 38,
keyword = 83, keyword = 84,
ConditionalOp = 40, ConditionalOp = 40,
ParenExpr = 41, ParenExpr = 41,
FunctionCallWithNewlines = 42, FunctionCallWithNewlines = 42,
DotGet = 43, DotGet = 43,
Number = 44, Number = 44,
PositionalArg = 45, Dollar = 45,
FunctionDef = 46, PositionalArg = 46,
Params = 47, FunctionDef = 47,
NamedParam = 48, Params = 48,
NamedArgPrefix = 49, NamedParam = 49,
String = 50, NamedArgPrefix = 50,
StringFragment = 51, String = 51,
Interpolation = 52, StringFragment = 52,
EscapeSeq = 53, Interpolation = 53,
DoubleQuote = 54, FunctionCallOrIdentifier = 54,
Boolean = 55, EscapeSeq = 55,
Null = 56, DoubleQuote = 56,
colon = 57, Boolean = 57,
CatchExpr = 58, Null = 58,
Block = 60, colon = 59,
FinallyExpr = 61, CatchExpr = 60,
Underscore = 64, Block = 62,
NamedArg = 65, FinallyExpr = 63,
IfExpr = 66, Underscore = 66,
FunctionCall = 68, NamedArg = 67,
ElseIfExpr = 69, IfExpr = 68,
ElseExpr = 71, FunctionCall = 70,
FunctionCallOrIdentifier = 72, ElseIfExpr = 71,
BinOp = 73, ElseExpr = 73,
Regex = 74, BinOp = 74,
Dict = 75, Regex = 75,
Array = 76, Dict = 76,
FunctionCallWithBlock = 77, Array = 77,
TryExpr = 78, FunctionCallWithBlock = 78,
Throw = 80, TryExpr = 79,
Import = 82, Throw = 81,
CompoundAssign = 84, Import = 83,
Assign = 85 CompoundAssign = 85,
Assign = 86

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:78, null:112, catch:118, finally:124, end:126, if:134, else:140, try:158, throw:162, import:166} const spec_Identifier = {__proto__:null,while:78, null:116, catch:122, finally:128, end:130, if:138, else:144, try:160, throw:164, import:168}
export const parser = LRParser.deserialize({ export const parser = LRParser.deserialize({
version: 14, version: 14,
states: "=|QYQbOOO!mOpO'#DXO!rOSO'#D`OOQa'#D`'#D`O%mQcO'#DvO(mQcO'#EiOOQ`'#Ew'#EwO)WQRO'#DwO+]QcO'#EgO+vQbO'#DVOOQa'#Dy'#DyO.[QbO'#DzOOQa'#Ei'#EiO.cQcO'#EiO0aQcO'#EhO1fQcO'#EgO1sQRO'#ESOOQ`'#Eg'#EgO2[QbO'#EgO2cQQO'#EfOOQ`'#Ef'#EfOOQ`'#EU'#EUQYQbOOO2nQbO'#D[O2yQbO'#DpO3tQbO'#DSO4oQQO'#D|O3tQbO'#EOO4tQbO'#EQO4|ObO,59sO5XQbO'#DbO5aQWO'#DcOOOO'#Eo'#EoOOOO'#EZ'#EZO5uOSO,59zOOQa,59z,59zOOQ`'#DZ'#DZO6TQbO'#DoOOQ`'#Em'#EmOOQ`'#E^'#E^O6_QbO,5:^OOQa'#Eh'#EhO3tQbO,5:cO3tQbO,5:cO3tQbO,5:cO3tQbO,5:cO3tQbO,59pO3tQbO,59pO3tQbO,59pO3tQbO,59pOOQ`'#EW'#EWO+vQbO,59qO7XQcO'#DvO7`QcO'#EiO7gQRO,59qO7qQQO,59qO7vQQO,59qO8OQQO,59qO8ZQRO,59qO8sQRO,59qO8zQQO'#DQO9PQbO,5:fO9WQQO,5:eOOQa,5:f,5:fO9cQbO,5:fO9mQbO,5:oO9mQbO,5:nO:}QbO,5:gO;UQbO,59lOOQ`,5;Q,5;QO9mQbO'#EVOOQ`-E8S-E8SOOQ`'#EX'#EXO;pQbO'#D]O;{QbO'#D^OOQO'#EY'#EYO;sQQO'#D]O<aQQO,59vO<fQcO'#EhO=cQRO'#EvO>`QRO'#EvOOQO'#Ev'#EvO>gQQO,5:[O>lQRO,59nO>sQRO,59nO:}QbO,5:hO?RQcO,5:jO@aQcO,5:jO@}QcO,5:jOArQbO,5:lOOQ`'#Eb'#EbO4tQbO,5:lOOQa1G/_1G/_OOOO,59|,59|OOOO,59},59}OOOO-E8X-E8XOOQa1G/f1G/fOOQ`,5:Z,5:ZOOQ`-E8[-E8[OOQa1G/}1G/}OCkQcO1G/}OCuQcO1G/}OETQcO1G/}OE_QcO1G/}OElQcO1G/}OOQa1G/[1G/[OF}QcO1G/[OGUQcO1G/[OG]QcO1G/[OH[QcO1G/[OGdQcO1G/[OOQ`-E8U-E8UOHrQRO1G/]OH|QQO1G/]OIRQQO1G/]OIZQQO1G/]OIfQRO1G/]OImQRO1G/]OItQbO,59rOJOQQO1G/]OOQa1G/]1G/]OJWQQO1G0POOQa1G0Q1G0QOJcQbO1G0QOOQO'#E`'#E`OJWQQO1G0POOQa1G0P1G0POOQ`'#Ea'#EaOJcQbO1G0QOJmQbO1G0ZOKXQbO1G0YOKsQbO'#DjOLUQbO'#DjOLiQbO1G0ROOQ`-E8T-E8TOOQ`,5:q,5:qOOQ`-E8V-E8VOLtQQO,59wOOQO,59x,59xOOQO-E8W-E8WOL|QbO1G/bO:}QbO1G/vO:}QbO1G/YOMTQbO1G0SOM`QbO1G0WOM}QbO1G0WOOQ`-E8`-E8`ONUQQO7+$wOOQa7+$w7+$wON^QQO1G/^ONfQQO7+%kOOQa7+%k7+%kONqQbO7+%lOOQa7+%l7+%lOOQO-E8^-E8^OOQ`-E8_-E8_OOQ`'#E['#E[ON{QQO'#E[O! TQbO'#EuOOQ`,5:U,5:UO! hQbO'#DhO! mQQO'#DkOOQ`7+%m7+%mO! rQbO7+%mO! wQbO7+%mO!!PQbO7+$|O!!_QbO7+$|O!!oQbO7+%bO!!wQbO7+$tOOQ`7+%n7+%nO!!|QbO7+%nO!#RQbO7+%nO!#ZQbO7+%rOOQa<<Hc<<HcO!#xQbO7+$xO!$VQQO7+$xOOQa<<IV<<IVOOQa<<IW<<IWOOQ`,5:v,5:vOOQ`-E8Y-E8YO!$_QQO,5:SO:}QbO,5:VOOQ`<<IX<<IXO!$dQbO<<IXOOQ`<<Hh<<HhO!$iQbO<<HhO!$nQbO<<HhO!$vQbO<<HhOOQ`'#E_'#E_O!%RQbO<<H|O!%ZQbO'#DuOOQ`<<H|<<H|O!%cQbO<<H|OOQ`<<H`<<H`OOQ`<<IY<<IYO!%hQbO<<IYOOQO,5:w,5:wO!%mQbO<<HdOOQO-E8Z-E8ZO:}QbO1G/nOOQ`1G/q1G/qOOQ`AN>sAN>sOOQ`AN>SAN>SO!%zQbOAN>SO!&PQbOAN>SOOQ`-E8]-E8]OOQ`AN>hAN>hO!&XQbOAN>hO2yQbO,5:_O:}QbO,5:aOOQ`AN>tAN>tPItQbO'#EWOOQ`7+%Y7+%YOOQ`G23nG23nO!&^QbOG23nP!%^QbO'#DsOOQ`G24SG24SO!&cQQO1G/yOOQ`1G/{1G/{OOQ`LD)YLD)YO:}QbO7+%eOOQ`<<IP<<IP", states: ">`QYQbOOO!pOpO'#DXO%eQcO'#DdO%{OSO'#DaOOQa'#Da'#DaO(vQcO'#EjOOQ`'#Ex'#ExO)aQRO'#DxO+fQcO'#EhO,PQbO'#DVOOQa'#Dz'#DzO.kQbO'#D{OOQa'#Ej'#EjO.rQcO'#EjO0pQcO'#EiO1uQcO'#EhO2SQRO'#ETOOQ`'#Eh'#EhO2kQbO'#EhO2rQQO'#EgOOQ`'#Eg'#EgOOQ`'#EV'#EVQYQbOOO2}QbO'#D]O3YQbO'#DrO4WQbO'#DSO5UQQO'#D}O4WQbO'#EPO5ZQbO'#ERO5cObO,59sOOQ`'#D['#D[O5tQbO'#DqOOQ`'#En'#EnOOQ`'#E_'#E_O6OQbO,5:`OOQa'#Ei'#EiO6xQbO'#DcO7WQWO'#DeOOOO'#Ep'#EpOOOO'#E['#E[O7lOSO,59{OOQa,59{,59{O4WQbO,5:dO4WQbO,5:dO4WQbO,5:dO4WQbO,5:dO4WQbO,59pO4WQbO,59pO4WQbO,59pO4WQbO,59pOOQ`'#EX'#EXO,PQbO,59qO7zQcO'#DdO8RQcO'#EjO8YQRO,59qO8dQQO,59qO8iQQO,59qO8qQQO,59qO8|QRO,59qO9fQRO,59qO9mQQO'#DQO9rQbO,5:gO9yQQO,5:fOOQa,5:g,5:gO:UQbO,5:gO:`QbO,5:pO:`QbO,5:oO;sQbO,5:hO;zQbO,59lOOQ`,5;R,5;RO:`QbO'#EWOOQ`-E8T-E8TOOQ`'#EY'#EYO<fQbO'#D^O<qQbO'#D_OOQO'#EZ'#EZO<iQQO'#D^O=VQQO,59wO=[QcO'#EiO>XQRO'#EwO?UQRO'#EwOOQO'#Ew'#EwO?]QQO,5:^O?bQRO,59nO?iQRO,59nO;sQbO,5:iO?wQcO,5:kOAVQcO,5:kOAsQcO,5:kOBhQbO,5:mOOQ`'#Ec'#EcO5ZQbO,5:mOOQa1G/_1G/_OOQ`,5:],5:]OOQ`-E8]-E8]OOOO'#Dd'#DdOOOO,59},59}OOOO,5:P,5:POOOO-E8Y-E8YOOQa1G/g1G/gOOQa1G0O1G0OODaQcO1G0OODkQcO1G0OOEyQcO1G0OOFTQcO1G0OOFbQcO1G0OOOQa1G/[1G/[OGsQcO1G/[OGzQcO1G/[OHRQcO1G/[OIQQcO1G/[OHYQcO1G/[OOQ`-E8V-E8VOIhQRO1G/]OIrQQO1G/]OIwQQO1G/]OJPQQO1G/]OJ[QRO1G/]OJcQRO1G/]OJjQbO,59rOJtQQO1G/]OOQa1G/]1G/]OJ|QQO1G0QOOQa1G0R1G0ROKXQbO1G0ROOQO'#Ea'#EaOJ|QQO1G0QOOQa1G0Q1G0QOOQ`'#Eb'#EbOKXQbO1G0ROKcQbO1G0[OK}QbO1G0ZOLiQbO'#DlOLzQbO'#DlOM_QbO1G0SOOQ`-E8U-E8UOOQ`,5:r,5:rOOQ`-E8W-E8WOMjQQO,59xOOQO,59y,59yOOQO-E8X-E8XOMrQbO1G/cO;sQbO1G/xO;sQbO1G/YOMyQbO1G0TONUQbO1G0XONsQbO1G0XOOQ`-E8a-E8aONzQQO7+$wOOQa7+$w7+$wO! SQQO1G/^O! [QQO7+%lOOQa7+%l7+%lO! gQbO7+%mOOQa7+%m7+%mOOQO-E8_-E8_OOQ`-E8`-E8`OOQ`'#E]'#E]O! qQQO'#E]O! yQbO'#EvOOQ`,5:W,5:WO!!^QbO'#DjO!!cQQO'#DmOOQ`7+%n7+%nO!!hQbO7+%nO!!mQbO7+%nO!!uQbO7+$}O!#TQbO7+$}O!#eQbO7+%dO!#mQbO7+$tOOQ`7+%o7+%oO!#rQbO7+%oO!#wQbO7+%oO!$PQbO7+%sOOQa<<Hc<<HcO!$nQbO7+$xO!${QQO7+$xOOQa<<IW<<IWOOQa<<IX<<IXOOQ`,5:w,5:wOOQ`-E8Z-E8ZO!%TQQO,5:UO;sQbO,5:XOOQ`<<IY<<IYO!%YQbO<<IYOOQ`<<Hi<<HiO!%_QbO<<HiO!%dQbO<<HiO!%lQbO<<HiOOQ`'#E`'#E`O!%wQbO<<IOO!&PQbO'#DwOOQ`<<IO<<IOO!&XQbO<<IOOOQ`<<H`<<H`OOQ`<<IZ<<IZO!&^QbO<<IZOOQO,5:x,5:xO!&cQbO<<HdOOQO-E8[-E8[O;sQbO1G/pOOQ`1G/s1G/sOOQ`AN>tAN>tOOQ`AN>TAN>TO!&pQbOAN>TO!&uQbOAN>TOOQ`-E8^-E8^OOQ`AN>jAN>jO!&}QbOAN>jO3YQbO,5:aO;sQbO,5:cOOQ`AN>uAN>uPJjQbO'#EXOOQ`7+%[7+%[OOQ`G23oG23oO!'SQbOG23oP!&SQbO'#DuOOQ`G24UG24UO!'XQQO1G/{OOQ`1G/}1G/}OOQ`LD)ZLD)ZO;sQbO7+%gOOQ`<<IR<<IRO!'^ObO,59sO!'oOpO'#DX",
stateData: "!&k~O#XOSrOS~OlSOm`On[OoPOpROqgOwiO|[O!WRO!X[O!Y[O!ehO!l[O!qjO!skO!ulO#^XO#_dO#bQO#mYO#nZO~O#`mO~O!TpO#bsO#dnO#eoO~OlyOn[OoPOpROqgO|[O!RuO!WRO!X[O!Y[O!btO!l[O#^XO#bQO#mYO#nZOP#[XQ#[XR#[XS#[XT#[XU#[XW#[XX#[XY#[XZ#[X[#[X]#[X^#[Xd#[Xe#[Xf#[Xg#[Xh#[Xi#[Xj#[Xu!jX!Z!jX#l!jX~O#_!jX#p!jX!]!jX!`!jX!a!jX!h!jX~P#QOlyOn[OoPOpROqgO|[O!RuO!WRO!X[O!Y[O!btO!l[O#^XO#bQO#mYO#nZOP#]XQ#]XR#]XS#]XT#]XU#]XW#]XX#]XY#]XZ#]X[#]X]#]X^#]Xd#]Xe#]Xf#]Xg#]Xh#]Xi#]Xj#]Xu#]X#l#]X~O#_#]X#p#]X!Z#]X!]#]X!`#]X!a#]X!h#]X~P&TOP{OQ{OR|OS|OT!POU!QOW!OOX!OOY!OOZ!OO[!OO]!OO^zOd}Oe}Of}Og}Oh}Oi}Oj!RO~OP{OQ{OR|OS|Od}Oe}Of}Og}Oh}Oi}Ou#ZX~O#_#ZX#p#ZX!]#ZX!`#ZX!a#ZX#l#ZX!h#ZX~P*hOl!UOm`On[OoPOpROqgOwiO|[O!WRO!X[O!Y[O!ehO!l[O!qjO!skO!ulO#^XO#_!SO#bQO#mYO#nZO~OlyOn[OoPOpRO|[O!RuO!WRO!X[O!Y[O!l[O#^XO#_!SO#bQO#mYO#nZO~O#o!aO~P-ZOV!cO#_#]X#p#]X!]#]X!`#]X!a#]X!h#]X~P'VOP#[XQ#[XR#[XS#[XT#[XU#[XW#[XX#[XY#[XZ#[X[#[X]#[X^#[Xd#[Xe#[Xf#[Xg#[Xh#[Xi#[Xj#[Xu#ZX~O#_#ZX#p#ZX!]#ZX!`#ZX!a#ZX#l#ZX!h#ZX~P.|Ou#ZX#_#ZX#p#ZX!]#ZX!`#ZX!a#ZX#l#ZX!h#ZX~OT!POU!QOj!RO~P0zOV!cO_!dO`!dOa!dOb!dOc!dOk!dO~O!Z!eO~P0zOu!hO#_!gO#p!gO~Ol!jO!R!lO!Z!PP~Ol!pOn[OoPOpRO|[O!WRO!X[O!Y[O!l[O#^XO#bQO#mYO#nZO~OlyOn[OoPOpRO|[O!WRO!X[O!Y[O!l[O#^XO#bQO#mYO#nZO~O!Z!wO~Ol!jO!RuO~Ol#OO|#OO#^XO~Ol#PO#^XO~O#b#QO#d#QO#e#QO#f#QO#g#QO#h#QO~O!TpO#b#SO#dnO#eoO~OqgO!b#TO~P3tOqgO!RuO!btOu!fa!Z!fa#_!fa#p!fa#l!fa!]!fa!`!fa!a!fa!h!fa~P3tO#_!SO~P#QO#_!SO~P&TO#_!SO#l#lO~P*hO#l#lO~O#l#lOu#ZX~O!Z!eO#l#lOu#ZX~O#l#lO~P.|OT!POU!QOj!RO#_!SOu#ZX~O#l#lO~P8bOu!hO~O#o#nO~P-ZO!RuO#_#pO#o#rO~O#_#sO#o#nO~P3tOlSOm`On[OoPOpROqgOwiO|[O!WRO!X[O!Y[O!ehO!l[O!qjO!skO!ulO#^XO#bQO#mYO#nZO~O#_#xO~P9mOu!hO#_ta#pta#lta!]ta!`ta!ata!hta~Ol!jO!R!lO!Z!PX~OpRO|$OO!WRO!X$OO!Y$OO#bQO~O!Z$QO~OqgO!RuO!btOT#[XU#[XW#[XX#[XY#[XZ#[X[#[X]#[Xj#[X!Z#[X~P3tOT!POU!QOj!RO!Z#jX~OT!POU!QOW!OOX!OOY!OOZ!OO[!OO]!OOj!RO~O!Z#jX~P=qO!Z$RO~O!Z$SO~P=qOT!POU!QOj!RO!Z$SO~Ou!ra#_!ra#p!ra!]!ra!`!ra!a!ra#l!ra!h!ra~P)WOP{OQ{OR|OS|Od}Oe}Of}Og}Oh}Oi}O~Ou!ra#_!ra#p!ra!]!ra!`!ra!a!ra#l!ra!h!ra~P?oOT!POU!QOj!ROu!ra#_!ra#p!ra!]!ra!`!ra!a!ra#l!ra!h!ra~Ol!jO!RuOu!ta#_!ta#p!ta!]!ta!`!ta!a!ta#l!ta!h!ta~O^zOR!kiS!kid!kie!kif!kig!kih!kii!kiu!ki#_!ki#p!ki#l!ki!]!ki!`!ki!a!ki!h!ki~OP!kiQ!ki~PBdOP{OQ{O~PBdOP{OQ{Od!kie!kif!kig!kih!kii!kiu!ki#_!ki#p!ki#l!ki!]!ki!`!ki!a!ki!h!ki~OR!kiS!ki~PDPOR|OS|O^zO~PDPOR|OS|O~PDPOW!OOX!OOY!OOZ!OO[!OO]!OOTxijxiuxi#_xi#pxi#lxi!Zxi!]xi!`xi!axi!hxi~OU!QO~PEvOU!QO~PFYOUxi~PEvOT!POU!QOjxiuxi#_xi#pxi#lxi!Zxi!]xi!`xi!axi!hxi~OW!OOX!OOY!OOZ!OO[!OO]!OO~PGdO#_!SO#l$YO~P*hO#l$YO~O#l$YOu#ZX~O!Z!eO#l$YOu#ZX~O#l$YO~P.|O#l$YO~P8bOqgO!btO~P-ZO#_!SO#l$YO~O!RuO#_#pO#o$]O~O#_#sO#o$_O~P3tOu!hO#_!wi#p!wi!]!wi!`!wi!a!wi#l!wi!h!wi~Ou!hO#_!vi#p!vi!]!vi!`!vi!a!vi#l!vi!h!vi~Ou!hO!]!^X!`!^X!a!^X!h!^X~O#_$bO!]#iP!`#iP!a#iP!h#iP~P9mO!]$fO!`$gO!a$hO~O!R!lO!Z!Pa~O#_$lO~P9mO!]$fO!`$gO!a$oO~O!RuOu!ti#_!ti#p!ti!]!ti!`!ti!a!ti#l!ti!h!ti~Ol!jO~PM`O#_!SO#l$sO~O#_!SO#lzi~O!RuO#_#pO#o$vO~O#_#sO#o$wO~P3tOu!hO#_$xO~O#_$bO!]#iX!`#iX!a#iX!h#iX~P9mOl$zO~O!Z${O~O!a$|O~O!`$gO!a$|O~Ou!hO!]$fO!`$gO!a%OO~O#_$bO!]#iP!`#iP!a#iP~P9mO!a%VO!h%UO~O!a%XO~O!a%YO~O!`$gO!a%YO~O!RuOu!tq#_!tq#p!tq!]!tq!`!tq!a!tq#l!tq!h!tq~OqgO!btO#lzq~P-ZO#_!SO#lzq~O!Z%_O~O!a%aO~O!a%bO~O!`$gO!a%bO~O!]$fO!`$gO!a%bO~O!a%fO!h%UO~O!Z%iO!e%hO~O!a%fO~O!a%jO~OqgO!btO#lzy~P-ZO!a%mO~O!`$gO!a%mO~O!a%pO~O!a%sO~O!Z%tO~O|!l~", stateData: "!'w~O#YOSrOS~OlQOm`On[OoPOpSOqgOwiO|[O}PO!YSO!Z[O![[O!ghO!m[O!rjO!tkO!vlO#_XO#`dO#cRO#nYO#oZO~O#amO~OlsOn[OoPOpSOqgO|[O}PO!SoO!YSO!Z[O![[O!dnO!m[O#_XO#cRO#nYO#oZOP#]XQ#]XR#]XS#]XT#]XU#]XW#]XX#]XY#]XZ#]X[#]X]#]X^#]Xd#]Xe#]Xf#]Xg#]Xh#]Xi#]Xj#]Xu!WX!]!WX#m!WX~O#`!WX#q!WX!_!WX!b!WX!c!WX!j!WX~P!uO!UvO#cyO#etO#fuO~OlsOn[OoPOpSOqgO|[O}PO!SoO!YSO!Z[O![[O!dnO!m[O#_XO#cRO#nYO#oZOP#^XQ#^XR#^XS#^XT#^XU#^XW#^XX#^XY#^XZ#^X[#^X]#^X^#^Xd#^Xe#^Xf#^Xg#^Xh#^Xi#^Xj#^Xu#^X#m#^X~O#`#^X#q#^X!]#^X!_#^X!b#^X!c#^X!j#^X~P&ZOP{OQ{OR|OS|OT!POU!QOW!OOX!OOY!OOZ!OO[!OO]!OO^zOd}Oe}Of}Og}Oh}Oi}Oj!RO~OP{OQ{OR|OS|Od}Oe}Of}Og}Oh}Oi}Ou#[X~O#`#[X#q#[X!_#[X!b#[X!c#[X#m#[X!j#[X~P*qOl!UOm`On[OoPOpSOqgOwiO|[O}PO!YSO!Z[O![[O!ghO!m[O!rjO!tkO!vlO#_XO#`!SO#cRO#nYO#oZO~OlsOn[OoPOpSO|[O}PO!SoO!YSO!Z[O![[O!m[O#_XO#`!SO#cRO#nYO#oZO~O#p!aO~P-gOV!cO#`#^X#q#^X!_#^X!b#^X!c#^X!j#^X~P'`OP#]XQ#]XR#]XS#]XT#]XU#]XW#]XX#]XY#]XZ#]X[#]X]#]X^#]Xd#]Xe#]Xf#]Xg#]Xh#]Xi#]Xj#]Xu#[X~O#`#[X#q#[X!_#[X!b#[X!c#[X#m#[X!j#[X~P/]Ou#[X#`#[X#q#[X!_#[X!b#[X!c#[X#m#[X!j#[X~OT!POU!QOj!RO~P1ZOV!cO_!dO`!dOa!dOb!dOc!dOk!dO~O!]!eO~P1ZOu!hO#`!gO#q!gO~Ol!jO!S!lO!]!QP~Ol!pOn[OoPOpSO|[O}PO!YSO!Z[O![[O!m[O#_XO#cRO#nYO#oZO~OlsOn[OoPOpSO|[O}PO!YSO!Z[O![[O!m[O#_XO#cRO#nYO#oZO~O!]!wO~Ol!jO!SoO~Ol#OOoPO|#OO}PO#_XO~OqgO!d#PO~P4WOqgO!SoO!dnOu!ha!]!ha#`!ha#q!ha#m!ha!_!ha!b!ha!c!ha!j!ha~P4WOl#ROo%xO}%xO#_XO~O#c#TO#e#TO#f#TO#g#TO#h#TO#i#TO~O!UvO#c#VO#etO#fuO~O#`!SO~P!uO#`!SO~P&ZO#`!SO#m#mO~P*qO#m#mO~O#m#mOu#[X~O!]!eO#m#mOu#[X~O#m#mO~P/]OT!POU!QOj!RO#`!SOu#[X~O#m#mO~P9TOu!hO~O#p#oO~P-gO!SoO#`#qO#p#sO~O#`#tO#p#oO~P4WOlQOm`On[OoPOpSOqgOwiO|[O}PO!YSO!Z[O![[O!ghO!m[O!rjO!tkO!vlO#_XO#cRO#nYO#oZO~O#`#yO~P:`Ou!hO#`ta#qta#mta!_ta!bta!cta!jta~Ol!jO!S!lO!]!QX~OpSO|$PO!YSO!Z$PO![$PO#cRO~O!]$RO~OqgO!SoO!dnOT#]XU#]XW#]XX#]XY#]XZ#]X[#]X]#]Xj#]X!]#]X~P4WOT!POU!QOj!RO!]#kX~OT!POU!QOW!OOX!OOY!OOZ!OO[!OO]!OOj!RO~O!]#kX~P>gO!]$SO~O!]$TO~P>gOT!POU!QOj!RO!]$TO~Ou!sa#`!sa#q!sa!_!sa!b!sa!c!sa#m!sa!j!sa~P)aOP{OQ{OR|OS|Od}Oe}Of}Og}Oh}Oi}O~Ou!sa#`!sa#q!sa!_!sa!b!sa!c!sa#m!sa!j!sa~P@eOT!POU!QOj!ROu!sa#`!sa#q!sa!_!sa!b!sa!c!sa#m!sa!j!sa~Ol!jO!SoOu!ua#`!ua#q!ua!_!ua!b!ua!c!ua#m!ua!j!ua~O^zOR!liS!lid!lie!lif!lig!lih!lii!liu!li#`!li#q!li#m!li!_!li!b!li!c!li!j!li~OP!liQ!li~PCYOP{OQ{O~PCYOP{OQ{Od!lie!lif!lig!lih!lii!liu!li#`!li#q!li#m!li!_!li!b!li!c!li!j!li~OR!liS!li~PDuOR|OS|O^zO~PDuOR|OS|O~PDuOW!OOX!OOY!OOZ!OO[!OO]!OOTxijxiuxi#`xi#qxi#mxi!]xi!_xi!bxi!cxi!jxi~OU!QO~PFlOU!QO~PGOOUxi~PFlOT!POU!QOjxiuxi#`xi#qxi#mxi!]xi!_xi!bxi!cxi!jxi~OW!OOX!OOY!OOZ!OO[!OO]!OO~PHYO#`!SO#m$ZO~P*qO#m$ZO~O#m$ZOu#[X~O!]!eO#m$ZOu#[X~O#m$ZO~P/]O#m$ZO~P9TOqgO!dnO~P-gO#`!SO#m$ZO~O!SoO#`#qO#p$^O~O#`#tO#p$`O~P4WOu!hO#`!xi#q!xi!_!xi!b!xi!c!xi#m!xi!j!xi~Ou!hO#`!wi#q!wi!_!wi!b!wi!c!wi#m!wi!j!wi~Ou!hO!_!`X!b!`X!c!`X!j!`X~O#`$cO!_#jP!b#jP!c#jP!j#jP~P:`O!_$gO!b$hO!c$iO~O!S!lO!]!Qa~O#`$mO~P:`O!_$gO!b$hO!c$pO~O!SoOu!ui#`!ui#q!ui!_!ui!b!ui!c!ui#m!ui!j!ui~Ol!jO~PNUO#`!SO#m$tO~O#`!SO#mzi~O!SoO#`#qO#p$wO~O#`#tO#p$xO~P4WOu!hO#`$yO~O#`$cO!_#jX!b#jX!c#jX!j#jX~P:`Ol${O~O!]$|O~O!c$}O~O!b$hO!c$}O~Ou!hO!_$gO!b$hO!c%PO~O#`$cO!_#jP!b#jP!c#jP~P:`O!c%WO!j%VO~O!c%YO~O!c%ZO~O!b$hO!c%ZO~O!SoOu!uq#`!uq#q!uq!_!uq!b!uq!c!uq#m!uq!j!uq~OqgO!dnO#mzq~P-gO#`!SO#mzq~O!]%`O~O!c%bO~O!c%cO~O!b$hO!c%cO~O!_$gO!b$hO!c%cO~O!c%gO!j%VO~O!]%jO!g%iO~O!c%gO~O!c%kO~OqgO!dnO#mzy~P-gO!c%nO~O!b$hO!c%nO~O!c%qO~O!c%tO~O!]%uO~Ol#OOo%xO|#OO}%xO#_XO~O#a%wO~O|!m~",
goto: "9Z#lPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#mP$WP$m%k&y'PP(Z(g)a)dP)jP*q*qPPPP*uP+R+kPPP,R#mP,s-^P-b-h-}P.t/x$W$WP$WP$WP$W$W1O1U1b2U2d2n2t2{3R3]3c3m3wPPP4V4Z5O6tPPP8OP8`PPPPP8d8j8praOf!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tQ!YXR#f!TwaOXf!T!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tr_Of!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tQ!]XS!qh%hQ!viQ!zkQ#]!QQ#_!PQ#b!RR#i!TvTOfh!c!d!e!h!w#x$Q$R$S$d$l${%_%h%i%t!W[STZikuxz{|}!O!P!Q!R!U!V!_!b!p#j#o#t$^$t%]%kS!VX!TQ#OmR#PnQ!XXR#e!TrSOf!c!d!e!h!w#x$Q$R$S$d$l${%_%i%t!WySTZikuxz{|}!O!P!Q!R!U!V!_!b!p#j#o#t$^$t%]%kS!UX!TT!ph%hevSTx!U!V!p#j$t%]%kraOf!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tdtSTx!U!V!p#j$t%]%kQ!YXQ#TuR#f!TR!ogX!mg!k!n#}#S[OSTXZfhikuxz{|}!O!P!Q!R!T!U!V!_!b!c!d!e!h!p!w#j#o#t#x$Q$R$S$^$d$l$t${%]%_%h%i%k%tR$O!lTpQrQ$j#yQ$q$TQ%Q$kR%d%RQ#y!eQ$T!wQ$m$RQ$n$SQ%`${Q%l%_Q%r%iR%u%tQ$i#yQ$p$TQ$}$jQ%P$kQ%Z$qS%c%Q%RR%n%ddvSTx!U!V!p#j$t%]%kQ!`Z[!|l!{!}$U$V$rQ#m!_X#p!`#m#q$[vUOXf!T!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tT!sh%hT%S$m%TQ%W$mR%g%TwUOXf!T!c!d!e!h!w#x$Q$R$S$d$l${%_%i%trWOf!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tQ!WXQ!ykQ#V{Q#Y|Q#[}R#d!T#T[OSTXZfhikuxz{|}!O!P!Q!R!T!U!V!_!b!c!d!e!h!p!w#j#o#t#x$Q$R$S$^$d$l$t${%]%_%h%i%k%t![[STZhikuxz{|}!O!P!Q!R!U!V!_!b!p#j#o#t$^$t%]%h%kw]OXf!T!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tQfOR!if^!fc!^#u#v#w$c$kR#z!fQ!TXQ!_Z`#c!T!_#j#k$X$t%]%kS#j!U!VS#k!W!]S$X#d#iQ$t$ZR%]$uQ!kgQ!{lU#|!k!{$VR$V!}Q!ngQ#}!kT$P!n#}QrQR#RrS$d#x$lR$y$dQ$u$ZR%^$uYxST!U!V!pR#UxQ%T$mR%e%TQ#q!`Q$[#mT$`#q$[Q#t!bQ$^#oT$a#t$^Q!}lQ$U!{U$W!}$U$rR$r$VTeOfScOfS!^X!TQ#u!cQ#v!d`#w!e!w$R$S${%_%i%tQ#{!hU$c#x$d$lR$k$QvVOXf!T!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tdtSTx!U!V!p#j$t%]%kQ!bZS!rh%hQ!uiQ!xkQ#TuQ#VzQ#W{Q#X|Q#Z}Q#]!OQ#^!PQ#`!QQ#a!RQ#o!_X#s!b#o#t$^r^Of!c!d!e!h!w#x$Q$R$S$d$l${%_%i%t![ySTZhikuxz{|}!O!P!Q!R!U!V!_!b!p#j#o#t$^$t%]%h%kQ![XR#h!T[wSTx!U!V!pQ$Z#jV%[$t%]%kTqQrQ$e#xR%R$lQ!thR%q%hrbOf!c!d!e!h!w#x$Q$R$S$d$l${%_%i%tQ!ZXR#g!T", goto: "9g#mPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP#nP$XP$n%l&{'RPP(d(p)j)mP)sP*z+O*zPPPP+hP+t,^PPP,t#nP-f.PP.T.ZP/Q0U$X$XP$XP$XP$X$X1[1b1n2b2p2z3Q3X3_3i3o3y4TPPP4c4g5[7QPPP8[P8lPPPPP8p8v8|raOf!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uQ!YXR#g!TwaOXf!T!c!d!e!h!w#y$R$S$T$e$m$|%`%j%ur_Of!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uQ!]XS!qh%iQ!viQ!zkQ#^!QQ#`!PQ#c!RR#j!TvTOfh!c!d!e!h!w#y$R$S$T$e$m$|%`%i%j%u!W[QTZikorz{|}!O!P!Q!R!U!V!_!b!p#k#p#u$_$u%^%lS!VX!TS#Om%wR#StQ!XXR#f!TrQOf!c!d!e!h!w#y$R$S$T$e$m$|%`%j%u!WsQTZikorz{|}!O!P!Q!R!U!V!_!b!p#k#p#u$_$u%^%lS!UX!TS!ph%iS#Om%wR#RtepQTr!U!V!p#k$u%^%lraOf!c!d!e!h!w#y$R$S$T$e$m$|%`%j%udnQTr!U!V!p#k$u%^%lQ!YXQ#PoR#g!TR!ogX!mg!k!n$O#S[OQTXZfhikorz{|}!O!P!Q!R!T!U!V!_!b!c!d!e!h!p!w#k#p#u#y$R$S$T$_$e$m$u$|%^%`%i%j%l%uR$P!lTvRxvUOXf!T!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uR#StQ$k#zQ$r$UQ%R$lR%e%SQ#z!eQ$U!wQ$n$SQ$o$TQ%a$|Q%m%`Q%s%jR%v%uQ$j#zQ$q$UQ%O$kQ%Q$lQ%[$rS%d%R%SR%o%edpQTr!U!V!p#k$u%^%lQ!`Z[!|l!{!}$V$W$sQ#n!_X#q!`#n#r$]vUOXf!T!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uT!sh%iT%T$n%UQ%X$nR%h%UrWOf!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uQ!WXQ!ykQ#W{Q#Z|Q#]}R#e!T#T[OQTXZfhikorz{|}!O!P!Q!R!T!U!V!_!b!c!d!e!h!p!w#k#p#u#y$R$S$T$_$e$m$u$|%^%`%i%j%l%u![[QTZhikorz{|}!O!P!Q!R!U!V!_!b!p#k#p#u$_$u%^%i%lw]OXf!T!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uQfOR!if^!fc!^#v#w#x$d$lR#{!fQ!TXQ!_Z`#d!T!_#k#l$Y$u%^%lS#k!U!VS#l!W!]S$Y#e#jQ$u$[R%^$vQ!kgQ!{lU#}!k!{$WR$W!}Q!ngQ$O!kT$Q!n$OQxRR#UxS$e#y$mR$z$eQ$v$[R%_$vYrQT!U!V!pR#QrQ%U$nR%f%UQ#r!`Q$]#nT$a#r$]Q#u!bQ$_#pT$b#u$_Q!}lQ$V!{U$X!}$V$sR$s$WTeOfScOfS!^X!TQ#v!cQ#w!d`#x!e!w$S$T$|%`%j%uQ#|!hU$d#y$e$mR$l$RvVOXf!T!c!d!e!h!w#y$R$S$T$e$m$|%`%j%udnQTr!U!V!p#k$u%^%lQ!bZS!rh%iQ!uiQ!xkQ#PoQ#WzQ#X{Q#Y|Q#[}Q#^!OQ#_!PQ#a!QQ#b!RQ#p!_X#t!b#p#u$_r^Of!c!d!e!h!w#y$R$S$T$e$m$|%`%j%u![sQTZhikorz{|}!O!P!Q!R!U!V!_!b!p#k#p#u$_$u%^%i%lQ![XR#i!T[qQTr!U!V!pQ$[#kV%]$u%^%lTwRxQ$f#yR%S$mQ!thR%r%irbOf!c!d!e!h!w#y$R$S$T$e$m$|%`%j%uQ!ZXR#h!T",
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 CurlyString Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation EscapeSeq DoubleQuote 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 Import 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 CurlyString Do Comment Program PipeExpr operator WhileExpr keyword ConditionalOp ParenExpr FunctionCall DotGet Number Dollar PositionalArg FunctionDef Params NamedParam NamedArgPrefix String StringFragment Interpolation FunctionCallOrIdentifier EscapeSeq DoubleQuote Boolean Null colon CatchExpr keyword Block FinallyExpr keyword keyword Underscore NamedArg IfExpr keyword FunctionCall ElseIfExpr keyword ElseExpr BinOp Regex Dict Array FunctionCallWithBlock TryExpr keyword Throw keyword Import keyword CompoundAssign Assign",
maxTerm: 124, maxTerm: 125,
context: trackScope, context: trackScope,
nodeProps: [ nodeProps: [
["closedBy", 57,"end"] ["closedBy", 59,"end"]
], ],
propSources: [highlighting], propSources: [highlighting],
skippedNodes: [0,34], skippedNodes: [0,34],
repeatNodeCount: 13, repeatNodeCount: 13,
tokenData: "Lq~R!OOX$RXY$pYZ%ZZp$Rpq$pqr$Rrs%tst'ztu)cuw$Rwx)hxy)myz*Wz{$R{|*q|}$R}!O*q!O!P$R!P!Q4^!Q!R+c!R![.W![!]<y!]!^%Z!^!}$R!}#O=d#O#P?Y#P#Q?_#Q#R$R#R#S?x#S#T$R#T#Y@c#Y#ZA}#Z#b@c#b#cGk#c#f@c#f#gHn#g#h@c#h#iIq#i#o@c#o#p$R#p#qLR#q;'S$R;'S;=`$j<%l~$R~O$R~~LlS$WU!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RS$mP;=`<%l$R^$wU!TS#XYOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU%bU!TS#_QOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU%yZ!TSOr%trs&lst%ttu'Vuw%twx'Vx#O%t#O#P'V#P;'S%t;'S;=`'t<%lO%tU&sU!WQ!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RQ'YTOr'Vrs'is;'S'V;'S;=`'n<%lO'VQ'nO!WQQ'qP;=`<%l'VU'wP;=`<%l%t^(RZrY!TSOY'zYZ$RZt'ztu(tuw'zwx(tx#O'z#O#P(t#P;'S'z;'S;=`)]<%lO'zY(ySrYOY(tZ;'S(t;'S;=`)V<%lO(tY)YP;=`<%l(t^)`P;=`<%l'z~)hO#d~~)mO#b~U)tU!TS#^QOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU*_U!TS#lQOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU*vX!TSOt$Ruw$Rx!Q$R!Q!R+c!R![.W![#O$R#P;'S$R;'S;=`$j<%lO$RU+jb!TS|QOt$Ruw$Rx!O$R!O!P,r!P!Q$R!Q![.W![#O$R#P#R$R#R#S/T#S#U$R#U#V/r#V#c$R#c#d1W#d#l$R#l#m2f#m;'S$R;'S;=`$j<%lO$RU,wW!TSOt$Ruw$Rx!Q$R!Q![-a![#O$R#P;'S$R;'S;=`$j<%lO$RU-hY!TS|QOt$Ruw$Rx!Q$R!Q![-a![#O$R#P#R$R#R#S,r#S;'S$R;'S;=`$j<%lO$RU._[!TS|QOt$Ruw$Rx!O$R!O!P,r!P!Q$R!Q![.W![#O$R#P#R$R#R#S/T#S;'S$R;'S;=`$j<%lO$RU/YW!TSOt$Ruw$Rx!Q$R!Q![.W![#O$R#P;'S$R;'S;=`$j<%lO$RU/wX!TSOt$Ruw$Rx!Q$R!Q!R0d!R!S0d!S#O$R#P;'S$R;'S;=`$j<%lO$RU0kX!TS|QOt$Ruw$Rx!Q$R!Q!R0d!R!S0d!S#O$R#P;'S$R;'S;=`$j<%lO$RU1]W!TSOt$Ruw$Rx!Q$R!Q!Y1u!Y#O$R#P;'S$R;'S;=`$j<%lO$RU1|W!TS|QOt$Ruw$Rx!Q$R!Q!Y1u!Y#O$R#P;'S$R;'S;=`$j<%lO$RU2k[!TSOt$Ruw$Rx!Q$R!Q![3a![!c$R!c!i3a!i#O$R#P#T$R#T#Z3a#Z;'S$R;'S;=`$j<%lO$RU3h[!TS|QOt$Ruw$Rx!Q$R!Q![3a![!c$R!c!i3a!i#O$R#P#T$R#T#Z3a#Z;'S$R;'S;=`$j<%lO$RU4cW!TSOt$Ruw$Rx!P$R!P!Q4{!Q#O$R#P;'S$R;'S;=`$j<%lO$RU5Q^!TSOY5|YZ$RZt5|tu7Puw5|wx7Px!P5|!P!Q$R!Q!}5|!}#O;r#O#P9_#P;'S5|;'S;=`<s<%lO5|U6T^!TS!lQOY5|YZ$RZt5|tu7Puw5|wx7Px!P5|!P!Q9t!Q!}5|!}#O;r#O#P9_#P;'S5|;'S;=`<s<%lO5|Q7UX!lQOY7PZ!P7P!P!Q7q!Q!}7P!}#O8`#O#P9_#P;'S7P;'S;=`9n<%lO7PQ7tP!P!Q7wQ7|U!lQ#Z#[7w#]#^7w#a#b7w#g#h7w#i#j7w#m#n7wQ8cVOY8`Z#O8`#O#P8x#P#Q7P#Q;'S8`;'S;=`9X<%lO8`Q8{SOY8`Z;'S8`;'S;=`9X<%lO8`Q9[P;=`<%l8`Q9bSOY7PZ;'S7P;'S;=`9n<%lO7PQ9qP;=`<%l7PU9yW!TSOt$Ruw$Rx!P$R!P!Q:c!Q#O$R#P;'S$R;'S;=`$j<%lO$RU:jb!TS!lQOt$Ruw$Rx#O$R#P#Z$R#Z#[:c#[#]$R#]#^:c#^#a$R#a#b:c#b#g$R#g#h:c#h#i$R#i#j:c#j#m$R#m#n:c#n;'S$R;'S;=`$j<%lO$RU;w[!TSOY;rYZ$RZt;rtu8`uw;rwx8`x#O;r#O#P8x#P#Q5|#Q;'S;r;'S;=`<m<%lO;rU<pP;=`<%l;rU<vP;=`<%l5|U=QU!TS!ZQOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU=kW#nQ!TSOt$Ruw$Rx!_$R!_!`>T!`#O$R#P;'S$R;'S;=`$j<%lO$RU>YV!TSOt$Ruw$Rx#O$R#P#Q>o#Q;'S$R;'S;=`$j<%lO$RU>vU#mQ!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$R~?_O#e~U?fU#oQ!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU@PU!TS!bQOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU@h^!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#o@c#o;'S$R;'S;=`$j<%lO$RUAkU!RQ!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RUBS_!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#UCR#U#o@c#o;'S$R;'S;=`$j<%lO$RUCW`!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#`@c#`#aDY#a#o@c#o;'S$R;'S;=`$j<%lO$RUD_`!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#g@c#g#hEa#h#o@c#o;'S$R;'S;=`$j<%lO$RUEf`!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#X@c#X#YFh#Y#o@c#o;'S$R;'S;=`$j<%lO$RUFo^!XQ!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#o@c#o;'S$R;'S;=`$j<%lO$R^Gr^#fW!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#o@c#o;'S$R;'S;=`$j<%lO$R^Hu^#hW!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#o@c#o;'S$R;'S;=`$j<%lO$R^Ix`#gW!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#f@c#f#gJz#g#o@c#o;'S$R;'S;=`$j<%lO$RUKP`!TSOt$Ruw$Rx}$R}!O@c!O!Q$R!Q![@c![!_$R!_!`Ad!`#O$R#P#T$R#T#i@c#i#jEa#j#o@c#o;'S$R;'S;=`$j<%lO$RULYUuQ!TSOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$R~LqO#p~", tokenData: "Ls~R!OOX$RXY$pYZ%ZZp$Rpq$pqr$Rrs%tst'ztu)cuw$Rwx)jxy)oyz*Yz{$R{|*s|}$R}!O*s!O!P$R!P!Q4`!Q!R+e!R![.Y![!]<{!]!^%Z!^!}$R!}#O=f#O#P?[#P#Q?a#Q#R$R#R#S?z#S#T$R#T#Y@e#Y#ZBP#Z#b@e#b#cGm#c#f@e#f#gHp#g#h@e#h#iIs#i#o@e#o#p$R#p#qLT#q;'S$R;'S;=`$j<%l~$R~O$R~~LnS$WU!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RS$mP;=`<%l$R^$wU!US#YYOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU%bU!US#`QOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU%yZ!USOr%trs&lst%ttu'Vuw%twx'Vx#O%t#O#P'V#P;'S%t;'S;=`'t<%lO%tU&sU!YQ!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RQ'YTOr'Vrs'is;'S'V;'S;=`'n<%lO'VQ'nO!YQQ'qP;=`<%l'VU'wP;=`<%l%t^(RZrY!USOY'zYZ$RZt'ztu(tuw'zwx(tx#O'z#O#P(t#P;'S'z;'S;=`)]<%lO'zY(ySrYOY(tZ;'S(t;'S;=`)V<%lO(tY)YP;=`<%l(t^)`P;=`<%l'z^)jO#e[}Q~)oO#c~U)vU!US#_QOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU*aU!US#mQOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU*xX!USOt$Ruw$Rx!Q$R!Q!R+e!R![.Y![#O$R#P;'S$R;'S;=`$j<%lO$RU+lb!US|QOt$Ruw$Rx!O$R!O!P,t!P!Q$R!Q![.Y![#O$R#P#R$R#R#S/V#S#U$R#U#V/t#V#c$R#c#d1Y#d#l$R#l#m2h#m;'S$R;'S;=`$j<%lO$RU,yW!USOt$Ruw$Rx!Q$R!Q![-c![#O$R#P;'S$R;'S;=`$j<%lO$RU-jY!US|QOt$Ruw$Rx!Q$R!Q![-c![#O$R#P#R$R#R#S,t#S;'S$R;'S;=`$j<%lO$RU.a[!US|QOt$Ruw$Rx!O$R!O!P,t!P!Q$R!Q![.Y![#O$R#P#R$R#R#S/V#S;'S$R;'S;=`$j<%lO$RU/[W!USOt$Ruw$Rx!Q$R!Q![.Y![#O$R#P;'S$R;'S;=`$j<%lO$RU/yX!USOt$Ruw$Rx!Q$R!Q!R0f!R!S0f!S#O$R#P;'S$R;'S;=`$j<%lO$RU0mX!US|QOt$Ruw$Rx!Q$R!Q!R0f!R!S0f!S#O$R#P;'S$R;'S;=`$j<%lO$RU1_W!USOt$Ruw$Rx!Q$R!Q!Y1w!Y#O$R#P;'S$R;'S;=`$j<%lO$RU2OW!US|QOt$Ruw$Rx!Q$R!Q!Y1w!Y#O$R#P;'S$R;'S;=`$j<%lO$RU2m[!USOt$Ruw$Rx!Q$R!Q![3c![!c$R!c!i3c!i#O$R#P#T$R#T#Z3c#Z;'S$R;'S;=`$j<%lO$RU3j[!US|QOt$Ruw$Rx!Q$R!Q![3c![!c$R!c!i3c!i#O$R#P#T$R#T#Z3c#Z;'S$R;'S;=`$j<%lO$RU4eW!USOt$Ruw$Rx!P$R!P!Q4}!Q#O$R#P;'S$R;'S;=`$j<%lO$RU5S^!USOY6OYZ$RZt6Otu7Ruw6Owx7Rx!P6O!P!Q$R!Q!}6O!}#O;t#O#P9a#P;'S6O;'S;=`<u<%lO6OU6V^!US!mQOY6OYZ$RZt6Otu7Ruw6Owx7Rx!P6O!P!Q9v!Q!}6O!}#O;t#O#P9a#P;'S6O;'S;=`<u<%lO6OQ7WX!mQOY7RZ!P7R!P!Q7s!Q!}7R!}#O8b#O#P9a#P;'S7R;'S;=`9p<%lO7RQ7vP!P!Q7yQ8OU!mQ#Z#[7y#]#^7y#a#b7y#g#h7y#i#j7y#m#n7yQ8eVOY8bZ#O8b#O#P8z#P#Q7R#Q;'S8b;'S;=`9Z<%lO8bQ8}SOY8bZ;'S8b;'S;=`9Z<%lO8bQ9^P;=`<%l8bQ9dSOY7RZ;'S7R;'S;=`9p<%lO7RQ9sP;=`<%l7RU9{W!USOt$Ruw$Rx!P$R!P!Q:e!Q#O$R#P;'S$R;'S;=`$j<%lO$RU:lb!US!mQOt$Ruw$Rx#O$R#P#Z$R#Z#[:e#[#]$R#]#^:e#^#a$R#a#b:e#b#g$R#g#h:e#h#i$R#i#j:e#j#m$R#m#n:e#n;'S$R;'S;=`$j<%lO$RU;y[!USOY;tYZ$RZt;ttu8buw;twx8bx#O;t#O#P8z#P#Q6O#Q;'S;t;'S;=`<o<%lO;tU<rP;=`<%l;tU<xP;=`<%l6OU=SU!US!]QOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU=mW#oQ!USOt$Ruw$Rx!_$R!_!`>V!`#O$R#P;'S$R;'S;=`$j<%lO$RU>[V!USOt$Ruw$Rx#O$R#P#Q>q#Q;'S$R;'S;=`$j<%lO$RU>xU#nQ!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$R~?aO#f~U?hU#pQ!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU@RU!US!dQOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RU@j^!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#o@e#o;'S$R;'S;=`$j<%lO$RUAmU!SQ!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$RUBU_!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#UCT#U#o@e#o;'S$R;'S;=`$j<%lO$RUCY`!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#`@e#`#aD[#a#o@e#o;'S$R;'S;=`$j<%lO$RUDa`!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#g@e#g#hEc#h#o@e#o;'S$R;'S;=`$j<%lO$RUEh`!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#X@e#X#YFj#Y#o@e#o;'S$R;'S;=`$j<%lO$RUFq^!ZQ!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#o@e#o;'S$R;'S;=`$j<%lO$R^Gt^#gW!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#o@e#o;'S$R;'S;=`$j<%lO$R^Hw^#iW!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#o@e#o;'S$R;'S;=`$j<%lO$R^Iz`#hW!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#f@e#f#gJ|#g#o@e#o;'S$R;'S;=`$j<%lO$RUKR`!USOt$Ruw$Rx}$R}!O@e!O!Q$R!Q![@e![!_$R!_!`Af!`#O$R#P#T$R#T#i@e#i#jEc#j#o@e#o;'S$R;'S;=`$j<%lO$RUL[UuQ!USOt$Ruw$Rx#O$R#P;'S$R;'S;=`$j<%lO$R~LsO#q~",
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#`~~", 11)], tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#a~~", 11)],
topRules: {"Program":[0,35]}, topRules: {"Program":[0,35]},
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}], 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: 2370 tokenPrec: 2428
}) })

View File

@ -298,4 +298,163 @@ end`).toMatchTree(`
Number 2 Number 2
`) `)
}) })
// NOTE: these are parsed as DotGet(meta, DotGet(script, name)) because that's easiest,
// but the compiler flattens them
test('chained dot get: meta.script.name', () => {
expect('meta = 42; meta.script.name').toMatchTree(`
Assign
AssignableIdentifier meta
Eq =
Number 42
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot meta
DotGet
IdentifierBeforeDot script
Identifier name
`)
})
test('chained dot get: a.b.c.d', () => {
expect('a = 1; a.b.c.d').toMatchTree(`
Assign
AssignableIdentifier a
Eq =
Number 1
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot a
DotGet
IdentifierBeforeDot b
DotGet
IdentifierBeforeDot c
Identifier d
`)
})
test('chained dot get in function call', () => {
expect('config = 1; echo config.db.host').toMatchTree(`
Assign
AssignableIdentifier config
Eq =
Number 1
FunctionCall
Identifier echo
PositionalArg
DotGet
IdentifierBeforeDot config
DotGet
IdentifierBeforeDot db
Identifier host
`)
})
test('chained dot get with numeric index at end', () => {
expect('obj = 1; obj.items.0').toMatchTree(`
Assign
AssignableIdentifier obj
Eq =
Number 1
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
DotGet
IdentifierBeforeDot items
Number 0
`)
})
test('chained dot get with ParenExpr at end', () => {
expect('obj = 1; obj.items.(i)').toMatchTree(`
Assign
AssignableIdentifier obj
Eq =
Number 1
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
DotGet
IdentifierBeforeDot items
ParenExpr
FunctionCallOrIdentifier
Identifier i
`)
})
test('not in scope remains Word with chained dots', () => {
expect('readme.md.bak').toMatchTree(`Word readme.md.bak`)
})
test('chained dot get in nested functions', () => {
expect(`do cfg:
do inner:
cfg.db.host
end
end`).toMatchTree(`
FunctionDef
Do do
Params
Identifier cfg
colon :
FunctionDef
Do do
Params
Identifier inner
colon :
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot cfg
DotGet
IdentifierBeforeDot db
Identifier host
keyword end
keyword end
`)
})
test('mixed simple and chained dot get', () => {
expect('obj = 1; obj.a; obj.b.c').toMatchTree(`
Assign
AssignableIdentifier obj
Eq =
Number 1
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
Identifier a
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot obj
DotGet
IdentifierBeforeDot b
Identifier c
`)
})
test.skip('chained numeric dot get: row.2.1.b', () => {
expect('row = []; row.2.1').toMatchTree(`
Assign
AssignableIdentifier row
Eq =
Array []
FunctionCallOrIdentifier
DotGet
IdentifierBeforeDot row
DotGet
Number 2
DotGet
Number 1
Identifier b
`)
test('parses $.pid just fine', () => {
expect(`$.pid`).toMatchTree(`
FunctionCallOrIdentifier
DotGet
Dollar $
Identifier pid
`)
})
})
}) })

View File

@ -8,6 +8,7 @@ describe('string interpolation', () => {
String String
StringFragment ${'hello '} StringFragment ${'hello '}
Interpolation Interpolation
FunctionCallOrIdentifier
Identifier name Identifier name
`) `)
}) })
@ -44,6 +45,7 @@ describe('string interpolation', () => {
String String
StringFragment x/ StringFragment x/
Interpolation Interpolation
FunctionCallOrIdentifier
Identifier y Identifier y
StringFragment /z StringFragment /z
`) `)
@ -122,6 +124,7 @@ describe('string escape sequences', () => {
String String
StringFragment value: StringFragment value:
Interpolation Interpolation
FunctionCallOrIdentifier
Identifier x Identifier x
EscapeSeq \\n EscapeSeq \\n
`) `)

View File

@ -1,5 +1,5 @@
import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr' import { ExternalTokenizer, InputStream, Stack } from '@lezer/lr'
import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot, Do, CurlyString } from './shrimp.terms' import { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot, Do, CurlyString, DotGet } from './shrimp.terms'
// doobie doobie do (we need the `do` keyword to know when we're defining params) // doobie doobie do (we need the `do` keyword to know when we're defining params)
export function specializeKeyword(ident: string) { export function specializeKeyword(ident: string) {
@ -187,11 +187,21 @@ const checkForDotGet = (input: InputStream, stack: Stack, pos: number): number |
const identifierText = buildIdentifierText(input, pos) const identifierText = buildIdentifierText(input, pos)
const context = stack.context as { scope: { has(name: string): boolean } } | undefined const context = stack.context as { scope: { has(name: string): boolean } } | undefined
// If identifier is in scope, this is property access (e.g., obj.prop) // Check if identifier is in scope (lexical scope or globals)
// If not in scope, it should be consumed as a Word (e.g., file.txt) const inScope = context?.scope.has(identifierText) || globals.includes(identifierText)
return context?.scope.has(identifierText) || globals.includes(identifierText)
? IdentifierBeforeDot // property access
: null if (inScope) return IdentifierBeforeDot
// Not in scope - check if we're inside a DotGet chain
// Inside the @skip {} block where DotGet is defined, Word cannot be shifted
// but Identifier can be. This tells us we're at the RHS of a DotGet.
const canShiftIdentifier = stack.canShift(Identifier)
const canShiftWord = stack.canShift(Word)
const inDotGetChain = canShiftIdentifier && !canShiftWord
// continue if we're inside a DotGet
return inDotGetChain ? IdentifierBeforeDot : null
} }
// Decide between AssignableIdentifier and Identifier using grammar state + peek-ahead // Decide between AssignableIdentifier and Identifier using grammar state + peek-ahead

View File

@ -1,5 +1,6 @@
// The prelude creates all the builtin Shrimp functions. // The prelude creates all the builtin Shrimp functions.
import { join, resolve } from 'path'
import { import {
type Value, type VM, toValue, type Value, type VM, toValue,
extractParamInfo, isWrapped, getOriginalFunction, extractParamInfo, isWrapped, getOriginalFunction,
@ -22,6 +23,20 @@ export const globals = {
math, math,
str, str,
// shrimp runtime info
$: {
args: Bun.argv.slice(3),
argv: Bun.argv.slice(1),
env: process.env,
pid: process.pid,
cwd: process.env.PWD,
script: {
name: Bun.argv[2] || '(shrimp)',
path: resolve(join('.', Bun.argv[2] ?? ''))
},
},
// hello // hello
echo: (...args: any[]) => { echo: (...args: any[]) => {
console.log(...args.map(a => { console.log(...args.map(a => {
@ -66,7 +81,6 @@ export const globals = {
}, },
// env // env
args: Bun.argv.slice(1),
exit: (num: number) => process.exit(num ?? 0), exit: (num: number) => process.exit(num ?? 0),
// type predicates // type predicates

View File

@ -80,15 +80,15 @@ describe('introspection', () => {
describe('environment', () => { describe('environment', () => {
test('args is an array', async () => { test('args is an array', async () => {
await expect(`array? args`).toEvaluateTo(true, globals) await expect(`array? $.args`).toEvaluateTo(true, globals)
}) })
test('args can be accessed', async () => { test('args can be accessed', async () => {
await expect(`type args`).toEvaluateTo('array', globals) await expect(`type $.args`).toEvaluateTo('array', globals)
}) })
test('', async () => { test('argv includes more than just the args', async () => {
await expect(`list.first args | str.ends-with? 'shrimp.test.ts'`).toEvaluateTo(true) await expect(`list.first $.argv | str.ends-with? 'shrimp.test.ts'`).toEvaluateTo(true)
}) })
}) })
@ -103,3 +103,38 @@ describe('ref', () => {
expect(`rnd = ref 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') expect(`rnd = ref math.random; ref rnd | type`).toEvaluateTo('native')
}) })
describe('$ global dictionary', () => {
test('$.args is an array', async () => {
await expect(`$.args | array?`).toEvaluateTo(true, globals)
})
test('$.args can be accessed', async () => {
await expect(`$.args | type`).toEvaluateTo('array', globals)
})
test('$.script.name is a string', async () => {
await expect(`$.script.name | string?`).toEvaluateTo(true, globals)
})
test('$.script.path is a string', async () => {
await expect(`$.script.path | string?`).toEvaluateTo(true, globals)
})
test('$.env is a dict', async () => {
await expect(`$.env | dict?`).toEvaluateTo(true, globals)
})
test('$.pid is a number', async () => {
await expect(`$.pid | number?`).toEvaluateTo(true, globals)
await expect(`$.pid > 0`).toEvaluateTo(true, globals)
})
test('$.cwd is a string', async () => {
await expect(`$.cwd | string?`).toEvaluateTo(true, globals)
})
test('$.cwd returns current working directory', async () => {
await expect(`$.cwd`).toEvaluateTo(process.cwd(), globals)
})
})