Compare commits
15 Commits
ef4184726d
...
7e69356f79
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e69356f79 | |||
| 9863f46f38 | |||
| 45f31d0678 | |||
| 49a6320fef | |||
| 7da437212d | |||
| 740379d7b2 | |||
| 19c4fb5033 | |||
| f57452ece2 | |||
| 4590d66105 | |||
| 3aa40ae2c2 | |||
| da0af799d8 | |||
| 9f45252522 | |||
| bae0da31c2 | |||
| 4258503c0e | |||
| d4a772e88b |
4
bin/repl
4
bin/repl
|
|
@ -145,7 +145,7 @@ async function repl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const compiler = new Compiler(trimmed, Object.keys(globals))
|
const compiler = new Compiler(trimmed, [...Object.keys(globals), ...vm.vars()])
|
||||||
|
|
||||||
// Save VM state before appending bytecode, in case execution fails
|
// Save VM state before appending bytecode, in case execution fails
|
||||||
const savedInstructions = [...vm.instructions]
|
const savedInstructions = [...vm.instructions]
|
||||||
|
|
@ -235,7 +235,7 @@ async function loadFile(filePath: string): Promise<{ vm: VM; codeHistory: string
|
||||||
if (!trimmed) continue
|
if (!trimmed) continue
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const compiler = new Compiler(trimmed)
|
const compiler = new Compiler(trimmed, [...Object.keys(globals), ...vm.vars()])
|
||||||
vm.appendBytecode(compiler.bytecode)
|
vm.appendBytecode(compiler.bytecode)
|
||||||
await vm.continue()
|
await vm.continue()
|
||||||
codeHistory.push(trimmed)
|
codeHistory.push(trimmed)
|
||||||
|
|
|
||||||
47
bin/shrimp
47
bin/shrimp
|
|
@ -1,50 +1,14 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { Compiler } from '../src/compiler/compiler'
|
import { colors } from '../src/prelude'
|
||||||
import { colors, globals } from '../src/prelude'
|
|
||||||
import { parser } from '../src/parser/shrimp'
|
|
||||||
import { treeToString } from '../src/utils/tree'
|
import { treeToString } from '../src/utils/tree'
|
||||||
import { VM, fromValue, bytecodeToString } from 'reefvm'
|
import { runFile, compileFile, parseCode } from '../src'
|
||||||
|
import { bytecodeToString } from 'reefvm'
|
||||||
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
|
import { readFileSync, writeFileSync, mkdirSync } from 'fs'
|
||||||
import { randomUUID } from 'crypto'
|
import { randomUUID } from 'crypto'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
|
||||||
async function runFile(filePath: string) {
|
|
||||||
try {
|
|
||||||
const code = readFileSync(filePath, 'utf-8')
|
|
||||||
const compiler = new Compiler(code, Object.keys(globals))
|
|
||||||
const vm = new VM(compiler.bytecode, globals)
|
|
||||||
await vm.run()
|
|
||||||
return vm.stack.length ? fromValue(vm.stack[vm.stack.length - 1]) : null
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function compileFile(filePath: string) {
|
|
||||||
try {
|
|
||||||
const code = readFileSync(filePath, 'utf-8')
|
|
||||||
const compiler = new Compiler(code)
|
|
||||||
return bytecodeToString(compiler.bytecode)
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseFile(filePath: string) {
|
|
||||||
try {
|
|
||||||
const code = readFileSync(filePath, 'utf-8')
|
|
||||||
const tree = parser.parse(code)
|
|
||||||
return treeToString(tree, code)
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHelp() {
|
function showHelp() {
|
||||||
console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell.
|
console.log(`${colors.bright}${colors.magenta}🦐 Shrimp${colors.reset} is a scripting language in a shell.
|
||||||
|
|
||||||
|
|
@ -112,7 +76,7 @@ async function main() {
|
||||||
console.log(`${colors.bright}usage: shrimp bytecode <file>${colors.reset}`)
|
console.log(`${colors.bright}usage: shrimp bytecode <file>${colors.reset}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
console.log(await compileFile(file))
|
console.log(bytecodeToString(compileFile(file)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,7 +86,8 @@ async function main() {
|
||||||
console.log(`${colors.bright}usage: shrimp parse <file>${colors.reset}`)
|
console.log(`${colors.bright}usage: shrimp parse <file>${colors.reset}`)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
console.log(await parseFile(file))
|
const input = readFileSync(file, 'utf-8')
|
||||||
|
console.log(treeToString(parseCode(input), input))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
8
bun.lock
8
bun.lock
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
"@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
|
||||||
|
|
||||||
|
|
@ -52,7 +52,7 @@
|
||||||
|
|
||||||
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.0.15", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-qtAXMNGG4R0UGGI8zWrqm2B7BdXqx48vunJXBPzfDOHPA5WkRUZdTSbE7TFwO4jLhYqSE23YMWsM9NhE6ovobw=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
|
||||||
|
|
||||||
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
|
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
|
||||||
|
|
||||||
|
|
@ -62,11 +62,11 @@
|
||||||
|
|
||||||
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
|
"hono": ["hono@4.10.4", "", {}, "sha512-YG/fo7zlU3KwrBL5vDpWKisLYiM+nVstBQqfr7gCPbSYURnNEP9BDxEMz8KfsDR9JX0lJWDRNc6nXX31v7ZEyg=="],
|
||||||
|
|
||||||
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#bffb83a5280a4d74e424c4e0f4fbd46f790227a3", { "peerDependencies": { "typescript": "^5" } }, "bffb83a5280a4d74e424c4e0f4fbd46f790227a3"],
|
"reefvm": ["reefvm@git+https://git.nose.space/defunkt/reefvm#d7a971db24aea5ddcaae2c18ce9f10dab793db19", { "peerDependencies": { "typescript": "^5" } }, "d7a971db24aea5ddcaae2c18ce9f10dab793db19"],
|
||||||
|
|
||||||
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
|
"style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="],
|
||||||
|
|
||||||
"tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="],
|
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,9 @@ export class Compiler {
|
||||||
bytecode: Bytecode
|
bytecode: Bytecode
|
||||||
pipeCounter = 0
|
pipeCounter = 0
|
||||||
|
|
||||||
constructor(public input: string, globals?: string[]) {
|
constructor(public input: string, globals?: string[] | Record<string, any>) {
|
||||||
try {
|
try {
|
||||||
if (globals) setGlobals(globals)
|
if (globals) setGlobals(Array.isArray(globals) ? globals : Object.keys(globals))
|
||||||
const cst = parser.parse(input)
|
const cst = parser.parse(input)
|
||||||
const errors = checkTreeForErrors(cst)
|
const errors = checkTreeForErrors(cst)
|
||||||
|
|
||||||
|
|
|
||||||
71
src/index.ts
71
src/index.ts
|
|
@ -1,11 +1,17 @@
|
||||||
import { readFileSync } from 'fs'
|
import { readFileSync } from 'fs'
|
||||||
import { VM, fromValue, type Bytecode } from 'reefvm'
|
import { VM, fromValue, toValue, isValue, type Bytecode } from 'reefvm'
|
||||||
|
import { type Tree } from '@lezer/common'
|
||||||
import { Compiler } from '#compiler/compiler'
|
import { Compiler } from '#compiler/compiler'
|
||||||
import { globals as shrimpGlobals, colors } from '#prelude'
|
import { parser } from '#parser/shrimp'
|
||||||
|
import { globals as parserGlobals, setGlobals as setParserGlobals } from '#parser/tokenizer'
|
||||||
|
import { globals as shrimpGlobals } from '#prelude'
|
||||||
|
|
||||||
export { Compiler } from '#compiler/compiler'
|
export { Compiler } from '#compiler/compiler'
|
||||||
export { parser } from '#parser/shrimp'
|
export { parser } from '#parser/shrimp'
|
||||||
export { globals } from '#prelude'
|
export { globals as prelude } from '#prelude'
|
||||||
|
export type { Tree } from '@lezer/common'
|
||||||
|
export { type Value, type Bytecode } from 'reefvm'
|
||||||
|
export { toValue, fromValue, isValue, Scope, VM, bytecodeToString } from 'reefvm'
|
||||||
|
|
||||||
export class Shrimp {
|
export class Shrimp {
|
||||||
vm: VM
|
vm: VM
|
||||||
|
|
@ -17,6 +23,32 @@ export class Shrimp {
|
||||||
this.globals = globals
|
this.globals = globals
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(name: string): any {
|
||||||
|
const value = this.vm.scope.get(name)
|
||||||
|
return value ? fromValue(value, this.vm) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
set(name: string, value: any) {
|
||||||
|
this.vm.scope.set(name, toValue(value, this.vm))
|
||||||
|
}
|
||||||
|
|
||||||
|
has(name: string): boolean {
|
||||||
|
return this.vm.scope.has(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
async call(name: string, ...args: any[]): Promise<any> {
|
||||||
|
const result = await this.vm.call(name, ...args)
|
||||||
|
return isValue(result) ? fromValue(result, this.vm) : result
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(code: string): Tree {
|
||||||
|
return parseCode(code, this.globals)
|
||||||
|
}
|
||||||
|
|
||||||
|
compile(code: string): Bytecode {
|
||||||
|
return compileCode(code, this.globals)
|
||||||
|
}
|
||||||
|
|
||||||
async run(code: string | Bytecode, locals?: Record<string, any>): Promise<any> {
|
async run(code: string | Bytecode, locals?: Record<string, any>): Promise<any> {
|
||||||
let bytecode
|
let bytecode
|
||||||
|
|
||||||
|
|
@ -32,13 +64,9 @@ export class Shrimp {
|
||||||
await this.vm.continue()
|
await this.vm.continue()
|
||||||
if (locals) this.vm.popScope()
|
if (locals) this.vm.popScope()
|
||||||
|
|
||||||
return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!) : null
|
return this.vm.stack.length ? fromValue(this.vm.stack.at(-1)!, this.vm) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
get(name: string): any {
|
|
||||||
const value = this.vm.scope.get(name)
|
|
||||||
return value ? fromValue(value) : null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runFile(path: string, globals?: Record<string, any>): Promise<any> {
|
export async function runFile(path: string, globals?: Record<string, any>): Promise<any> {
|
||||||
|
|
@ -51,14 +79,9 @@ 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> {
|
||||||
try {
|
const vm = new VM(bytecode, Object.assign({}, shrimpGlobals, globals))
|
||||||
const vm = new VM(bytecode, Object.assign({}, shrimpGlobals, 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]!) : null
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`${colors.red}Error:${colors.reset} ${error.message}`)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compileFile(path: string, globals?: Record<string, any>): Bytecode {
|
export function compileFile(path: string, globals?: Record<string, any>): Bytecode {
|
||||||
|
|
@ -71,3 +94,19 @@ export function compileCode(code: string, globals?: Record<string, any>): Byteco
|
||||||
const compiler = new Compiler(code, globalNames)
|
const compiler = new Compiler(code, globalNames)
|
||||||
return compiler.bytecode
|
return compiler.bytecode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function parseFile(path: string, globals?: Record<string, any>): Tree {
|
||||||
|
const code = readFileSync(path, 'utf-8')
|
||||||
|
return parseCode(code, globals)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseCode(code: string, globals?: Record<string, any>): Tree {
|
||||||
|
const oldGlobals = [...parserGlobals]
|
||||||
|
const globalNames = [...Object.keys(shrimpGlobals), ...(globals ? Object.keys(globals) : [])]
|
||||||
|
|
||||||
|
setParserGlobals(globalNames)
|
||||||
|
const result = parser.parse(code)
|
||||||
|
setParserGlobals(oldGlobals)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
@ -12,13 +12,13 @@
|
||||||
@precedence { Number Regex }
|
@precedence { Number Regex }
|
||||||
|
|
||||||
StringFragment { !['\\$]+ }
|
StringFragment { !['\\$]+ }
|
||||||
NamedArgPrefix { $[a-z-]+ "=" }
|
NamedArgPrefix { $[a-z] $[a-z0-9-]* "=" }
|
||||||
Number { ("-" | "+")? $[0-9]+ ("_"? $[0-9]+)* ('.' $[0-9]+ ("_"? $[0-9]+)*)? }
|
Number { ("-" | "+")? $[0-9]+ ("_"? $[0-9]+)* ('.' $[0-9]+ ("_"? $[0-9]+)*)? }
|
||||||
Boolean { "true" | "false" }
|
Boolean { "true" | "false" }
|
||||||
newlineOrSemicolon { "\n" | ";" }
|
newlineOrSemicolon { "\n" | ";" }
|
||||||
eof { @eof }
|
eof { @eof }
|
||||||
space { " " | "\t" }
|
space { " " | "\t" }
|
||||||
Comment { "#" " " ![\n]* }
|
Comment { "#" ![\n]* }
|
||||||
leftParen { "(" }
|
leftParen { "(" }
|
||||||
rightParen { ")" }
|
rightParen { ")" }
|
||||||
colon[closedBy="end", @name="colon"] { ":" }
|
colon[closedBy="end", @name="colon"] { ":" }
|
||||||
|
|
@ -47,7 +47,8 @@ null { @specialize[@name=Null]<Identifier, "null"> }
|
||||||
comparison @left,
|
comparison @left,
|
||||||
multiplicative @left,
|
multiplicative @left,
|
||||||
additive @left,
|
additive @left,
|
||||||
call
|
call,
|
||||||
|
functionWithNewlines
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item {
|
||||||
|
|
@ -188,7 +189,21 @@ BinOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
ParenExpr {
|
ParenExpr {
|
||||||
leftParen (IfExpr | ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr | FunctionDef) rightParen
|
leftParen newlineOrSemicolon* (
|
||||||
|
FunctionCallWithNewlines |
|
||||||
|
IfExpr |
|
||||||
|
ambiguousFunctionCall |
|
||||||
|
BinOp newlineOrSemicolon* |
|
||||||
|
expressionWithoutIdentifier |
|
||||||
|
ConditionalOp newlineOrSemicolon* |
|
||||||
|
PipeExpr |
|
||||||
|
FunctionDef
|
||||||
|
)
|
||||||
|
rightParen
|
||||||
|
}
|
||||||
|
|
||||||
|
FunctionCallWithNewlines[@name=FunctionCall] {
|
||||||
|
(DotGet | Identifier | ParenExpr) newlineOrSemicolon+ arg !functionWithNewlines (newlineOrSemicolon+ arg)* newlineOrSemicolon*
|
||||||
}
|
}
|
||||||
|
|
||||||
expression {
|
expression {
|
||||||
|
|
|
||||||
|
|
@ -28,39 +28,40 @@ export const
|
||||||
Program = 26,
|
Program = 26,
|
||||||
PipeExpr = 27,
|
PipeExpr = 27,
|
||||||
WhileExpr = 29,
|
WhileExpr = 29,
|
||||||
keyword = 70,
|
keyword = 71,
|
||||||
ConditionalOp = 31,
|
ConditionalOp = 31,
|
||||||
ParenExpr = 32,
|
ParenExpr = 32,
|
||||||
IfExpr = 33,
|
FunctionCallWithNewlines = 33,
|
||||||
FunctionCall = 35,
|
DotGet = 34,
|
||||||
DotGet = 36,
|
Number = 35,
|
||||||
Number = 37,
|
PositionalArg = 36,
|
||||||
PositionalArg = 38,
|
FunctionDef = 37,
|
||||||
FunctionDef = 39,
|
Params = 38,
|
||||||
Params = 40,
|
NamedParam = 39,
|
||||||
NamedParam = 41,
|
NamedArgPrefix = 40,
|
||||||
NamedArgPrefix = 42,
|
String = 41,
|
||||||
String = 43,
|
StringFragment = 42,
|
||||||
StringFragment = 44,
|
Interpolation = 43,
|
||||||
Interpolation = 45,
|
EscapeSeq = 44,
|
||||||
EscapeSeq = 46,
|
Boolean = 45,
|
||||||
Boolean = 47,
|
Null = 46,
|
||||||
Null = 48,
|
colon = 47,
|
||||||
colon = 49,
|
CatchExpr = 48,
|
||||||
CatchExpr = 50,
|
Block = 50,
|
||||||
Block = 52,
|
FinallyExpr = 51,
|
||||||
FinallyExpr = 53,
|
Underscore = 54,
|
||||||
Underscore = 56,
|
NamedArg = 55,
|
||||||
NamedArg = 57,
|
IfExpr = 56,
|
||||||
ElseIfExpr = 58,
|
FunctionCall = 58,
|
||||||
ElseExpr = 60,
|
ElseIfExpr = 59,
|
||||||
FunctionCallOrIdentifier = 61,
|
ElseExpr = 61,
|
||||||
BinOp = 62,
|
FunctionCallOrIdentifier = 62,
|
||||||
Regex = 63,
|
BinOp = 63,
|
||||||
Dict = 64,
|
Regex = 64,
|
||||||
Array = 65,
|
Dict = 65,
|
||||||
FunctionCallWithBlock = 66,
|
Array = 66,
|
||||||
TryExpr = 67,
|
FunctionCallWithBlock = 67,
|
||||||
Throw = 69,
|
TryExpr = 68,
|
||||||
CompoundAssign = 71,
|
Throw = 70,
|
||||||
Assign = 72
|
CompoundAssign = 72,
|
||||||
|
Assign = 73
|
||||||
|
|
|
||||||
|
|
@ -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:60, if:68, null:96, catch:102, finally:108, end:110, else:118, try:136, throw:140}
|
const spec_Identifier = {__proto__:null,while:60, null:92, catch:98, finally:104, end:106, if:114, else:120, try:138, throw:142}
|
||||||
export const parser = LRParser.deserialize({
|
export const parser = LRParser.deserialize({
|
||||||
version: 14,
|
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",
|
states: "<OQYQbOOO!dOpO'#DOO!iOSO'#DVO$wQcO'#DlO'[QcO'#E[OOQ`'#Ej'#EjO'uQRO'#DmO)[QcO'#EYO)lQbO'#C|OOQa'#Do'#DoO+qQbO'#DpOOQa'#E['#E[O+xQcO'#E[O,cQcO'#EZO-`QcO'#EYO-jQRO'#DvOOQ`'#EY'#EYO.OQbO'#EYO.VQQO'#EXOOQ`'#EX'#EXOOQ`'#Dx'#DxQYQbOOO.bQbO'#DRO.mQbO'#DfO/bQbO'#CyO0VQQO'#DrO/bQbO'#DtO0[ObO,59jO0gQbO'#DXO0oQWO'#DYOOOO'#Eb'#EbOOOO'#D}'#D}O1TOSO,59qOOQa,59q,59qOOQ`'#DQ'#DQO1cQbO'#DeOOQ`'#E`'#E`OOQ`'#EQ'#EQO1mQbO,5:SOOQa'#EZ'#EZO/bQbO,5:XO/bQbO,5:XO/bQbO,5:XO/bQbO,59gO/bQbO,59gO/bQbO,59gOOQ`'#Dz'#DzO)lQbO,59hO2gQcO'#DlO2nQcO'#E[O3ZQRO,59hO3bQQO,59hO3gQQO,59hO3oQQO,59hO4xQRO,59hO5PQRO,59hO5bQQO'#CwO5gQbO,5:[O5nQQO,5:ZOOQa,5:[,5:[O5yQbO,5:[O6TQbO,5:cO6TQbO,5:bO7[QbO,5:]O7cQbO,59cOOQ`,5:s,5:sO6TQbO'#DyOOQ`-E7v-E7vOOQ`'#D{'#D{O7}QbO'#DSO8YQbO'#DTOOQO'#D|'#D|O8QQQO'#DSO8hQQO,59mO8mQcO'#EZO9gQRO'#EiO:^QRO'#EiOOQO'#Ei'#EiO:eQQO,5:QO:jQRO,59eO:qQRO,59eO7[QbO,5:^O:|QcO,5:`O<UQcO,5:`O<fQcO,5:`OOQa1G/U1G/UOOOO,59s,59sOOOO,59t,59tOOOO-E7{-E7{OOQa1G/]1G/]OOQ`,5:P,5:POOQ`-E8O-E8OOOQa1G/s1G/sO=eQcO1G/sO=oQcO1G/sO=yQcO1G/sOOQa1G/R1G/RO?uQcO1G/RO?|QcO1G/RO@TQcO1G/ROOQ`-E7x-E7xO@[QRO1G/SO@cQQO1G/SO@hQQO1G/SO@pQQO1G/SO@{QRO1G/SOASQRO1G/SOAeQbO,59iOAoQQO1G/SOOQa1G/S1G/SOAwQQO1G/uOOQa1G/v1G/vOBSQbO1G/vOOQO'#ES'#ESOAwQQO1G/uOOQa1G/u1G/uOOQ`'#ET'#ETOBSQbO1G/vOB^QbO1G/}OBxQbO1G/|OCdQbO'#D`OCuQbO'#D`ODYQbO1G/wOOQ`-E7w-E7wOOQ`,5:e,5:eOOQ`-E7y-E7yODeQQO,59nOOQO,59o,59oOOQO-E7z-E7zODmQbO1G/XO7[QbO1G/lO7[QbO1G/PODtQbO1G/xOEPQQO7+$nOOQa7+$n7+$nOEXQQO1G/TOEaQQO7+%aOOQa7+%a7+%aOElQbO7+%bOOQa7+%b7+%bOOQO-E8Q-E8QOOQ`-E8R-E8ROOQ`'#EO'#EOOEvQQO'#EOOFOQbO'#EhOOQ`,59z,59zOFcQbO'#D^OFhQQO'#DaOOQ`7+%c7+%cOFmQbO7+%cOFrQbO7+%cOFzQbO7+$sOGYQbO7+$sOGjQbO7+%WOGrQbO7+$kOOQ`7+%d7+%dOGwQbO7+%dOG|QbO7+%dOOQa<<HY<<HYOHUQbO7+$oOHcQQO7+$oOOQa<<H{<<H{OOQa<<H|<<H|OOQ`,5:j,5:jOOQ`-E7|-E7|OHkQQO,59xO7[QbO,59{OOQ`<<H}<<H}OHpQbO<<H}OOQ`<<H_<<H_OHuQbO<<H_OHzQbO<<H_OISQbO<<H_OOQ`'#ER'#EROI_QbO<<HrOIgQbO'#DkOOQ`<<Hr<<HrOIoQbO<<HrOOQ`<<HV<<HVOOQ`<<IO<<IOOItQbO<<IOOOQO,5:k,5:kOIyQbO<<HZOOQO-E7}-E7}O7[QbO1G/dOOQ`1G/g1G/gOOQ`AN>iAN>iOOQ`AN=yAN=yOJWQbOAN=yOJ]QbOAN=yOOQ`-E8P-E8POOQ`AN>^AN>^OJeQbOAN>^O.mQbO,5:TO7[QbO,5:VOOQ`AN>jAN>jPAeQbO'#DzOOQ`7+%O7+%OOOQ`G23eG23eOJjQbOG23ePIjQbO'#DiOOQ`G23xG23xOJoQQO1G/oOOQ`1G/q1G/qOOQ`LD)PLD)PO7[QbO7+%ZOOQ`<<Hu<<Hu",
|
||||||
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~",
|
stateData: "Jw~O!zOSiOS~OdROe_OfZOgPOhfOnhOsZO}ZO!OZO!ZgO!bZO!giO!ijO#PWO#QcO#TQO#`XO#aYO~O#RkO~OznO#TqO#VlO#WmO~OdwOfZOgPOhfOsZOxsO}ZO!OZO!WrO!bZO#PWO#TQO#`XO#aYOP!}XQ!}XR!}XS!}XT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X^!}Xl!`X!P!`X#_!`X~O#Q!`X#c!`X!R!`X!U!`X!V!`X!^!`X~P!wOdwOfZOgPOhfOsZOxsO}ZO!OZO!WrO!bZO#PWO#TQO#`XO#aYOP#OXQ#OXR#OXS#OXT#OXU#OXW#OXX#OXY#OXZ#OX[#OX]#OX^#OXl#OX#_#OX~O#Q#OX#c#OX!P#OX!R#OX!U#OX!V#OX!^#OX~P%_OPyOQyORzOSzOT|OU}OW{OX{OY{OZ{O[{O]{O^xO~Ol!|X#Q!|X#c!|X!R!|X!U!|X!V!|X#_!|X!^!|X~OPyOQyORzOSzO~P(pOd!QOe_OfZOgPOhfOnhOsZO}ZO!OZO!ZgO!bZO!giO!ijO#PWO#Q!OO#TQO#`XO#aYO~OdwOfZOgPOsZOxsO}ZO!OZO!bZO#PWO#Q!OO#TQO#`XO#aYO~O#b!]O~P*vOV!_O#Q#OX#c#OX!R#OX!U#OX!V#OX!^#OX~P&ZOP!}XQ!}XR!}XS!}XT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X^!}X~P(pOT|OU}O~P(pOV!_O_!`O`!`Oa!`Ob!`Oc!`O~O!P!aO~P(pOl!dO#Q!cO#c!cO~Od!fOx!hO!PvP~Od!lOfZOgPOsZO}ZO!OZO!bZO#PWO#TQO#`XO#aYO~OdwOfZOgPOsZO}ZO!OZO!bZO#PWO#TQO#`XO#aYO~O!P!sO~Od!wOs!wO#PWO~Od!xO#PWO~O#T!yO#V!yO#W!yO#X!yO#Y!yO#Z!yO~OznO#T!{O#VlO#WmO~OhfO!W!|O~P/bOhfOxsO!WrOl![a!P![a#Q![a#c![a#_![a!R![a!U![a!V![a!^![a~P/bO#Q!OO~P!wO#Q!OO~P%_OPyOQyORzOSzO#Q!OOl!|X~O#_#aO~P2uO#_#aO~O#_#aOl!|X~O!P!aO#_#aOl!|X~OP!}XQ!}XR!}XS!}XT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X^!}Xl!|X~O#_#aO~P3zOT|OU}O#Q!OO#_#aOl!|X~Ol!dO~O#b#cO~P*vOxsO#Q#eO#b#gO~O#Q#hO#b#cO~P/bOdROe_OfZOgPOhfOnhOsZO}ZO!OZO!ZgO!bZO!giO!ijO#PWO#TQO#`XO#aYO~O#Q#mO~P6TOl!dO#Qka#cka#_ka!Rka!Uka!Vka!^ka~Od!fOx!hO!PvX~Os#sO}#sO!O#sO#TQO~O!P#uO~OhfOxsO!WrOT!}XU!}XW!}XX!}XY!}XZ!}X[!}X]!}X!P!}X~P/bOT|OU}O!P#]X~OT|OU}OW{OX{OY{OZ{O[{O]{O~O!P#]X~P9rO!P#vO~O!P#wO~P9rOT|OU}O!P#wO~Ol!ha#Q!ha#c!ha!R!ha!U!ha!V!ha#_!ha!^!ha~P'uOl!ha#Q!ha#c!ha!R!ha!U!ha!V!ha#_!ha!^!ha~OPyOQyORzOSzO~P;jOT|OU}O~P;jO^xOR!aiS!ail!ai#Q!ai#c!ai#_!ai!R!ai!U!ai!V!ai!^!ai~OP!aiQ!ai~P<pOPyOQyO~P<pOPyOQyOR!aiS!ail!ai#Q!ai#c!ai#_!ai!R!ai!U!ai!V!ai!^!ai~OW{OX{OY{OZ{O[{O]{OToiloi#Qoi#coi#_oi!Poi!Roi!Uoi!Voi!^oi~OU}O~P>qOU}O~P?TOUoi~P>qO#_#zO~P2uO#_#zO~O#_#zOl!|X~O!P!aO#_#zOl!|X~O#_#zO~P3zOT|OU}O#Q!OO#_#zOl!|X~OhfO!WrO~P*vO#Q!OO#_#zO~OxsO#Q#eO#b#}O~O#Q#hO#b$PO~P/bOl!dO#Q!ki#c!ki!R!ki!U!ki!V!ki#_!ki!^!ki~Ol!dO#Q!ji#c!ji!R!ji!U!ji!V!ji#_!ji!^!ji~Ol!dO!R!SX!U!SX!V!SX!^!SX~O#Q$SO!R#[P!U#[P!V#[P!^#[P~P6TO!R$WO!U$XO!V$YO~Ox!hO!Pva~O#Q$^O~P6TO!R$WO!U$XO!V$aO~O#Q!OO#_$dO~O#Q!OO#_qi~OxsO#Q#eO#b$gO~O#Q#hO#b$hO~P/bOl!dO#Q$iO~O#Q$SO!R#[X!U#[X!V#[X!^#[X~P6TOd$kO~O!P$lO~O!V$mO~O!U$XO!V$mO~Ol!dO!R$WO!U$XO!V$oO~O#Q$SO!R#[P!U#[P!V#[P~P6TO!V$vO!^$uO~O!V$xO~O!V$yO~O!U$XO!V$yO~OhfO!WrO#_qq~P*vO#Q!OO#_qq~O!P%OO~O!V%QO~O!V%RO~O!U$XO!V%RO~O!R$WO!U$XO!V%RO~O!V%VO!^$uO~O!P%YO!Z%XO~O!V%VO~O!V%ZO~OhfO!WrO#_qy~P*vO!V%^O~O!U$XO!V%^O~O!V%aO~O!V%dO~O!P%eO~Os!b~",
|
||||||
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",
|
goto: "7t#_PPPPPPPPPPPPPPPPPPPPPPPPPPP#`P#yP$`%Z&g&mP'u(R({)OP)UP*Z*ZPPP*_P*k+TPPP+k#`P,T,nP,r,x-_P.R/T#y#yP#yP#y#y0X0_0k1_1e1o1u1|2S2^2d2nPPP2x2|3q5aPPP6iP6yPPPPP6}7T7Zr`Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!UWR#Z!Pw`OWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%er^Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!XWS!mg%XQ!rhQ!vjQ#S}Q#U|R#^!PvSOeg!_!`!a!d!s#m#u#v#w$U$^$l%O%X%Y%e!SZRSYhjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%[S!RW!PQ!wkR!xlQ!TWR#Y!PrROe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%e!SwRSYhjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%[S!QW!PT!lg%XetRSv!Q!R!l#_$e$|%[r`Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%edrRSv!Q!R!l#_$e$|%[Q!UWQ!|sR#Z!PR!kfX!if!g!j#r#OZORSWYeghjsvxyz{|}!P!Q!R!Z!^!_!`!a!d!l!s#_#d#i#m#u#v#w$O$U$^$e$l$|%O%X%Y%[%eR#s!hTnQpQ$[#nQ$c#xQ$q$]R%T$rQ#n!aQ#x!sQ$_#vQ$`#wQ%P$lQ%]%OQ%c%YR%f%eQ$Z#nQ$b#xQ$n$[Q$p$]Q$z$cS%S$q$rR%_%TdtRSv!Q!R!l#_$e$|%[Q![YQ#b!ZX#e![#b#f#|vTOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eT!og%XT$s$_$tQ$w$_R%W$twTOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%erVOe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!SWQ!ujQ#OyQ#RzR#X!P#PZORSWYeghjsvxyz{|}!P!Q!R!Z!^!_!`!a!d!l!s#_#d#i#m#u#v#w$O$U$^$e$l$|%O%X%Y%[%e!WZRSYghjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%X%[w[OWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQeOR!ee^!bb!Y#j#k#l$T$]R#o!bQ!PWQ!ZY`#W!P!Z#_#`#y$e$|%[S#_!Q!RS#`!S!XS#y#X#^Q$e#{R$|$fQ!gfR#q!gQ!jfQ#r!gT#t!j#rQpQR!zpS$U#m$^R$j$UQ$f#{R$}$fYvRS!Q!R!lR!}vQ$t$_R%U$tQ#f![Q#|#bT$Q#f#|Q#i!^Q$O#dT$R#i$OTdOeSbOeS!YW!PQ#j!_Q#k!``#l!a!s#v#w$l%O%Y%eQ#p!dU$T#m$U$^R$]#uvUOWe!P!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%edrRSv!Q!R!l#_$e$|%[Q!^YS!ng%XQ!qhQ!tjQ!|sQ#OxQ#PyQ#QzQ#S{Q#T|Q#V}Q#d!ZX#h!^#d#i$Or]Oe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%e!WwRSYghjsvxyz{|}!Q!R!Z!^!l#_#d#i$O$e$|%X%[Q!WWR#]!P[uRSv!Q!R!lQ#{#_V${$e$|%[ToQpQ$V#mR$r$^Q!pgR%b%XraOe!_!`!a!d!s#m#u#v#w$U$^$l%O%Y%eQ!VWR#[!P",
|
||||||
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",
|
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 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: 109,
|
maxTerm: 111,
|
||||||
context: trackScope,
|
context: trackScope,
|
||||||
nodeProps: [
|
nodeProps: [
|
||||||
["closedBy", 49,"end"]
|
["closedBy", 47,"end"]
|
||||||
],
|
],
|
||||||
propSources: [highlighting],
|
propSources: [highlighting],
|
||||||
skippedNodes: [0,25],
|
skippedNodes: [0,25],
|
||||||
repeatNodeCount: 11,
|
repeatNodeCount: 12,
|
||||||
tokenData: "DY~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'tuw#{wx'yxy(Oyz(iz{#{{|)S|}#{}!O,S!O!P#{!P!Q.i!Q![)q![!]7U!]!^%T!^!}#{!}#O7o#O#P9e#P#Q9j#Q#R#{#R#S:T#S#T#{#T#Y-T#Y#Z:n#Z#b-T#b#c?l#c#f-T#f#g@i#g#h-T#h#iAf#i#o-T#o#p#{#p#qCj#q;'S#{;'S;=`$d<%l~#{~O#{~~DTS$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#{^%sW|SOp#{pq&]qt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^&dZiY|SOY&]YZ#{Zt&]tu'Vuw&]wx'Vx#O&]#O#P'V#P;'S&];'S;=`'n<%lO&]Y'[SiYOY'VZ;'S'V;'S;=`'h<%lO'VY'kP;=`<%l'V^'qP;=`<%l&]~'yO#T~~(OO#R~U(VU|S!}QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(pU|S#]QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U)XW|SOt#{uw#{x!Q#{!Q![)q![#O#{#P;'S#{;'S;=`$d<%lO#{U)x[|SuQOt#{uw#{x!O#{!O!P*n!P!Q#{!Q![)q![#O#{#P#R#{#R#S)S#S;'S#{;'S;=`$d<%lO#{U*sW|SOt#{uw#{x!Q#{!Q![+]![#O#{#P;'S#{;'S;=`$d<%lO#{U+dY|SuQOt#{uw#{x!Q#{!Q![+]![#O#{#P#R#{#R#S*n#S;'S#{;'S;=`$d<%lO#{U,X^|SOt#{uw#{x}#{}!O-T!O!Q#{!Q![)q![!_#{!_!`.O!`#O#{#P#T#{#T#o-T#o;'S#{;'S;=`$d<%lO#{U-Y[|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#o-T#o;'S#{;'S;=`$d<%lO#{U.VUzQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U.nW|SOt#{uw#{x!P#{!P!Q/W!Q#O#{#P;'S#{;'S;=`$d<%lO#{U/]^|SOY0XYZ#{Zt0Xtu1[uw0Xwx1[x!P0X!P!Q#{!Q!}0X!}#O5}#O#P3j#P;'S0X;'S;=`7O<%lO0XU0`^|S!aQOY0XYZ#{Zt0Xtu1[uw0Xwx1[x!P0X!P!Q4P!Q!}0X!}#O5}#O#P3j#P;'S0X;'S;=`7O<%lO0XQ1aX!aQOY1[Z!P1[!P!Q1|!Q!}1[!}#O2k#O#P3j#P;'S1[;'S;=`3y<%lO1[Q2PP!P!Q2SQ2XU!aQ#Z#[2S#]#^2S#a#b2S#g#h2S#i#j2S#m#n2SQ2nVOY2kZ#O2k#O#P3T#P#Q1[#Q;'S2k;'S;=`3d<%lO2kQ3WSOY2kZ;'S2k;'S;=`3d<%lO2kQ3gP;=`<%l2kQ3mSOY1[Z;'S1[;'S;=`3y<%lO1[Q3|P;=`<%l1[U4UW|SOt#{uw#{x!P#{!P!Q4n!Q#O#{#P;'S#{;'S;=`$d<%lO#{U4ub|S!aQOt#{uw#{x#O#{#P#Z#{#Z#[4n#[#]#{#]#^4n#^#a#{#a#b4n#b#g#{#g#h4n#h#i#{#i#j4n#j#m#{#m#n4n#n;'S#{;'S;=`$d<%lO#{U6S[|SOY5}YZ#{Zt5}tu2kuw5}wx2kx#O5}#O#P3T#P#Q0X#Q;'S5};'S;=`6x<%lO5}U6{P;=`<%l5}U7RP;=`<%l0XU7]U|S!RQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7vW#_Q|SOt#{uw#{x!_#{!_!`8`!`#O#{#P;'S#{;'S;=`$d<%lO#{U8eV|SOt#{uw#{x#O#{#P#Q8z#Q;'S#{;'S;=`$d<%lO#{U9RU#^Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~9jO#U~U9qU#`Q|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:[U|S!YQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U:s]|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#U;l#U#o-T#o;'S#{;'S;=`$d<%lO#{U;q^|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#`-T#`#a<m#a#o-T#o;'S#{;'S;=`$d<%lO#{U<r^|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#g-T#g#h=n#h#o-T#o;'S#{;'S;=`$d<%lO#{U=s^|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#X-T#X#Y>o#Y#o-T#o;'S#{;'S;=`$d<%lO#{U>v[!PQ|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#o-T#o;'S#{;'S;=`$d<%lO#{^?s[#VW|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#o-T#o;'S#{;'S;=`$d<%lO#{^@p[#XW|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#o-T#o;'S#{;'S;=`$d<%lO#{^Am^#WW|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#f-T#f#gBi#g#o-T#o;'S#{;'S;=`$d<%lO#{UBn^|SOt#{uw#{x}#{}!O-T!O!_#{!_!`.O!`#O#{#P#T#{#T#i-T#i#j=n#j#o-T#o;'S#{;'S;=`$d<%lO#{UCqUlQ|SOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~DYO#a~",
|
tokenData: "Cx~R|OX#{XY$jYZ%TZp#{pq$jqs#{st%ntu'Vuw#{wx'[xy'ayz'zz{#{{|(e|}#{}!O(e!O!P#{!P!Q+e!Q![)S![!]4Q!]!^%T!^!}#{!}#O4k#O#P6a#P#Q6f#Q#R#{#R#S7P#S#T#{#T#Y7j#Y#Z9U#Z#b7j#b#c>r#c#f7j#f#g?u#g#h7j#h#i@x#i#o7j#o#p#{#p#qCY#q;'S#{;'S;=`$d<%l~#{~O#{~~CsS$QUzSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{^$qUzS!zYOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U%[UzS#QQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{^%uZiYzSOY%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#V~~'aO#T~U'hUzS#PQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(RUzS#_QOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U(jWzSOt#{uw#{x!Q#{!Q![)S![#O#{#P;'S#{;'S;=`$d<%lO#{U)Z[zSsQOt#{uw#{x!O#{!O!P*P!P!Q#{!Q![)S![#O#{#P#R#{#R#S(e#S;'S#{;'S;=`$d<%lO#{U*UWzSOt#{uw#{x!Q#{!Q![*n![#O#{#P;'S#{;'S;=`$d<%lO#{U*uYzSsQOt#{uw#{x!Q#{!Q![*n![#O#{#P#R#{#R#S*P#S;'S#{;'S;=`$d<%lO#{U+jWzSOt#{uw#{x!P#{!P!Q,S!Q#O#{#P;'S#{;'S;=`$d<%lO#{U,X^zSOY-TYZ#{Zt-Ttu.Wuw-Twx.Wx!P-T!P!Q#{!Q!}-T!}#O2y#O#P0f#P;'S-T;'S;=`3z<%lO-TU-[^zS!bQOY-TYZ#{Zt-Ttu.Wuw-Twx.Wx!P-T!P!Q0{!Q!}-T!}#O2y#O#P0f#P;'S-T;'S;=`3z<%lO-TQ.]X!bQOY.WZ!P.W!P!Q.x!Q!}.W!}#O/g#O#P0f#P;'S.W;'S;=`0u<%lO.WQ.{P!P!Q/OQ/TU!bQ#Z#[/O#]#^/O#a#b/O#g#h/O#i#j/O#m#n/OQ/jVOY/gZ#O/g#O#P0P#P#Q.W#Q;'S/g;'S;=`0`<%lO/gQ0SSOY/gZ;'S/g;'S;=`0`<%lO/gQ0cP;=`<%l/gQ0iSOY.WZ;'S.W;'S;=`0u<%lO.WQ0xP;=`<%l.WU1QWzSOt#{uw#{x!P#{!P!Q1j!Q#O#{#P;'S#{;'S;=`$d<%lO#{U1qbzS!bQOt#{uw#{x#O#{#P#Z#{#Z#[1j#[#]#{#]#^1j#^#a#{#a#b1j#b#g#{#g#h1j#h#i#{#i#j1j#j#m#{#m#n1j#n;'S#{;'S;=`$d<%lO#{U3O[zSOY2yYZ#{Zt2ytu/guw2ywx/gx#O2y#O#P0P#P#Q-T#Q;'S2y;'S;=`3t<%lO2yU3wP;=`<%l2yU3}P;=`<%l-TU4XUzS!PQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4rW#aQzSOt#{uw#{x!_#{!_!`5[!`#O#{#P;'S#{;'S;=`$d<%lO#{U5aVzSOt#{uw#{x#O#{#P#Q5v#Q;'S#{;'S;=`$d<%lO#{U5}U#`QzSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~6fO#W~U6mU#bQzSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7WUzS!WQOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U7o^zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#o7j#o;'S#{;'S;=`$d<%lO#{U8rUxQzSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U9Z_zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#U:Y#U#o7j#o;'S#{;'S;=`$d<%lO#{U:_`zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#`7j#`#a;a#a#o7j#o;'S#{;'S;=`$d<%lO#{U;f`zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#g7j#g#h<h#h#o7j#o;'S#{;'S;=`$d<%lO#{U<m`zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#X7j#X#Y=o#Y#o7j#o;'S#{;'S;=`$d<%lO#{U=v^}QzSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#o7j#o;'S#{;'S;=`$d<%lO#{^>y^#XWzSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#o7j#o;'S#{;'S;=`$d<%lO#{^?|^#ZWzSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#o7j#o;'S#{;'S;=`$d<%lO#{^AP`#YWzSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#f7j#f#gBR#g#o7j#o;'S#{;'S;=`$d<%lO#{UBW`zSOt#{uw#{x}#{}!O7j!O!Q#{!Q![7j![!_#{!_!`8k!`#O#{#P#T#{#T#i7j#i#j<h#j#o7j#o;'S#{;'S;=`$d<%lO#{UCaUlQzSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~CxO#c~",
|
||||||
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#P~~", 11)],
|
tokenizers: [operatorTokenizer, 1, 2, 3, tokenizer, new LocalTokenGroup("[~RP!O!PU~ZO#R~~", 11)],
|
||||||
topRules: {"Program":[0,26]},
|
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}],
|
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
|
tokenPrec: 1922
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,78 @@ describe('Parentheses', () => {
|
||||||
PositionalArg
|
PositionalArg
|
||||||
Number 3`)
|
Number 3`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('function call with named args on multiple lines in parens', () => {
|
||||||
|
expect(`(tail
|
||||||
|
arg1=true
|
||||||
|
arg2=30
|
||||||
|
)`).toMatchTree(`
|
||||||
|
ParenExpr
|
||||||
|
FunctionCall
|
||||||
|
Identifier tail
|
||||||
|
NamedArg
|
||||||
|
NamedArgPrefix arg1=
|
||||||
|
Boolean true
|
||||||
|
NamedArg
|
||||||
|
NamedArgPrefix arg2=
|
||||||
|
Number 30
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(`(
|
||||||
|
tail
|
||||||
|
arg1=true
|
||||||
|
arg2=30
|
||||||
|
)`).toMatchTree(`
|
||||||
|
ParenExpr
|
||||||
|
FunctionCall
|
||||||
|
Identifier tail
|
||||||
|
NamedArg
|
||||||
|
NamedArgPrefix arg1=
|
||||||
|
Boolean true
|
||||||
|
NamedArg
|
||||||
|
NamedArgPrefix arg2=
|
||||||
|
Number 30
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('binop with newlines in parens', () => {
|
||||||
|
expect(`(
|
||||||
|
1 + 2
|
||||||
|
)`).toMatchTree(`
|
||||||
|
ParenExpr
|
||||||
|
BinOp
|
||||||
|
Number 1
|
||||||
|
Plus +
|
||||||
|
Number 2`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('comparison with newlines in parens', () => {
|
||||||
|
expect(`(
|
||||||
|
1 < 2
|
||||||
|
)`).toMatchTree(`
|
||||||
|
ParenExpr
|
||||||
|
ConditionalOp
|
||||||
|
Number 1
|
||||||
|
Lt <
|
||||||
|
Number 2`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('function call with multiple identifiers on separate lines in parens', () => {
|
||||||
|
expect(`(echo
|
||||||
|
arg1
|
||||||
|
arg2
|
||||||
|
arg3
|
||||||
|
)`).toMatchTree(`
|
||||||
|
ParenExpr
|
||||||
|
FunctionCall
|
||||||
|
Identifier echo
|
||||||
|
PositionalArg
|
||||||
|
Identifier arg1
|
||||||
|
PositionalArg
|
||||||
|
Identifier arg2
|
||||||
|
PositionalArg
|
||||||
|
Identifier arg3`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Number literals', () => {
|
describe('Number literals', () => {
|
||||||
|
|
@ -679,7 +751,7 @@ describe('Comments', () => {
|
||||||
test('are greedy', () => {
|
test('are greedy', () => {
|
||||||
expect(`
|
expect(`
|
||||||
x = 5 # one banana
|
x = 5 # one banana
|
||||||
y = 2 # two bananas`).toMatchTree(`
|
y = 2 #two bananas`).toMatchTree(`
|
||||||
Assign
|
Assign
|
||||||
AssignableIdentifier x
|
AssignableIdentifier x
|
||||||
Eq =
|
Eq =
|
||||||
|
|
@ -689,7 +761,7 @@ y = 2 # two bananas`).toMatchTree(`
|
||||||
AssignableIdentifier y
|
AssignableIdentifier y
|
||||||
Eq =
|
Eq =
|
||||||
Number 2
|
Number 2
|
||||||
Comment # two bananas`)
|
Comment #two bananas`)
|
||||||
|
|
||||||
expect(`
|
expect(`
|
||||||
# some comment
|
# some comment
|
||||||
|
|
@ -710,11 +782,11 @@ basename = 5 # very astute
|
||||||
})
|
})
|
||||||
|
|
||||||
test('words with # are not considered comments', () => {
|
test('words with # are not considered comments', () => {
|
||||||
expect('find #hashtag-file.txt').toMatchTree(`
|
expect('find my#hashtag-file.txt').toMatchTree(`
|
||||||
FunctionCall
|
FunctionCall
|
||||||
Identifier find
|
Identifier find
|
||||||
PositionalArg
|
PositionalArg
|
||||||
Word #hashtag-file.txt`)
|
Word my#hashtag-file.txt`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('hastags in strings are not comments', () => {
|
test('hastags in strings are not comments', () => {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ export function specializeKeyword(ident: string) {
|
||||||
|
|
||||||
// tell the dotGet searcher about builtin globals
|
// tell the dotGet searcher about builtin globals
|
||||||
export const globals: string[] = []
|
export const globals: string[] = []
|
||||||
export const setGlobals = (newGlobals: string[]) => {
|
export const setGlobals = (newGlobals: string[] | Record<string, any>) => {
|
||||||
globals.length = 0
|
globals.length = 0
|
||||||
globals.push(...newGlobals)
|
globals.push(...(Array.isArray(newGlobals) ? newGlobals : Object.keys(newGlobals)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF.
|
// The only chars that can't be words are whitespace, apostrophes, closing parens, and EOF.
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from 'reefvm'
|
} from 'reefvm'
|
||||||
|
|
||||||
import { dict } from './dict'
|
import { dict } from './dict'
|
||||||
|
import { json } from './json'
|
||||||
import { load } from './load'
|
import { load } from './load'
|
||||||
import { list } from './list'
|
import { list } from './list'
|
||||||
import { math } from './math'
|
import { math } from './math'
|
||||||
|
|
@ -13,6 +14,7 @@ import { str } from './str'
|
||||||
|
|
||||||
export const globals = {
|
export const globals = {
|
||||||
dict,
|
dict,
|
||||||
|
json,
|
||||||
load,
|
load,
|
||||||
list,
|
list,
|
||||||
math,
|
math,
|
||||||
|
|
@ -41,6 +43,10 @@ export const globals = {
|
||||||
return typeof v !== 'string' || this.scope.has(v)
|
return typeof v !== 'string' || this.scope.has(v)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// env
|
||||||
|
args: Bun.argv.slice(1),
|
||||||
|
exit: (num: number) => process.exit(num ?? 0),
|
||||||
|
|
||||||
// type predicates
|
// type predicates
|
||||||
'string?': (v: any) => toValue(v).type === 'string',
|
'string?': (v: any) => toValue(v).type === 'string',
|
||||||
'number?': (v: any) => toValue(v).type === 'number',
|
'number?': (v: any) => toValue(v).type === 'number',
|
||||||
|
|
|
||||||
7
src/prelude/json.ts
Normal file
7
src/prelude/json.ts
Normal 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
|
||||||
|
|
@ -14,6 +14,13 @@ export const list = {
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
},
|
},
|
||||||
|
reject: async (list: any[], cb: Function) => {
|
||||||
|
let acc: any[] = []
|
||||||
|
for (const value of list) {
|
||||||
|
if (!(await cb(value))) acc.push(value)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
reduce: async (list: any[], cb: Function, initial: any) => {
|
reduce: async (list: any[], cb: Function, initial: any) => {
|
||||||
let acc = initial
|
let acc = initial
|
||||||
for (const value of list) acc = await cb(acc, value)
|
for (const value of list) acc = await cb(acc, value)
|
||||||
|
|
@ -66,6 +73,13 @@ export const list = {
|
||||||
const realItems = items.map(item => item.value)
|
const realItems = items.map(item => item.value)
|
||||||
return toValue(realList.splice(realStart, realDeleteCount, ...realItems))
|
return toValue(realList.splice(realStart, realDeleteCount, ...realItems))
|
||||||
},
|
},
|
||||||
|
insert: (list: Value, index: Value, item: Value) => {
|
||||||
|
if (list.type !== 'array') return toNull()
|
||||||
|
const realList = list.value as any[]
|
||||||
|
const realIndex = index.value as number
|
||||||
|
realList.splice(realIndex, 0, item)
|
||||||
|
return toValue(realList.length)
|
||||||
|
},
|
||||||
|
|
||||||
// sequence operations
|
// sequence operations
|
||||||
reverse: (list: any[]) => list.slice().reverse(),
|
reverse: (list: any[]) => list.slice().reverse(),
|
||||||
|
|
@ -136,3 +150,4 @@ export const list = {
|
||||||
; (list.pop as any).raw = true
|
; (list.pop as any).raw = true
|
||||||
; (list.shift as any).raw = true
|
; (list.shift as any).raw = true
|
||||||
; (list.unshift as any).raw = true
|
; (list.unshift as any).raw = true
|
||||||
|
; (list.insert as any).raw = true
|
||||||
|
|
@ -77,3 +77,17 @@ describe('introspection', () => {
|
||||||
await expect(`describe 'hello'`).toEvaluateTo("#<string: \u001b[32m'hello\u001b[32m'\u001b[0m>", globals)
|
await expect(`describe 'hello'`).toEvaluateTo("#<string: \u001b[32m'hello\u001b[32m'\u001b[0m>", globals)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('environment', () => {
|
||||||
|
test('args is an array', async () => {
|
||||||
|
await expect(`array? args`).toEvaluateTo(true, globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('args can be accessed', async () => {
|
||||||
|
await expect(`type args`).toEvaluateTo('array', globals)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('', async () => {
|
||||||
|
await expect(`list.first args | str.ends-with? 'shrimp.test.ts'`).toEvaluateTo(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
|
||||||
84
src/prelude/tests/json.test.ts
Normal file
84
src/prelude/tests/json.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -66,6 +66,7 @@ describe('string operations', () => {
|
||||||
test('slice extracts substring', async () => {
|
test('slice extracts substring', async () => {
|
||||||
await expect(`str.slice 'hello' 1 3`).toEvaluateTo('el')
|
await expect(`str.slice 'hello' 1 3`).toEvaluateTo('el')
|
||||||
await expect(`str.slice 'hello' 2 null`).toEvaluateTo('llo')
|
await expect(`str.slice 'hello' 2 null`).toEvaluateTo('llo')
|
||||||
|
await expect(`str.slice 'hello' 2`).toEvaluateTo('llo')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('repeat repeats string', async () => {
|
test('repeat repeats string', async () => {
|
||||||
|
|
@ -193,6 +194,15 @@ describe('collections', () => {
|
||||||
`).toEvaluateTo([3, 4, 5])
|
`).toEvaluateTo([3, 4, 5])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('list.reject doesnt keep matching elements', async () => {
|
||||||
|
await expect(`
|
||||||
|
is-even = do x:
|
||||||
|
(x % 2) == 0
|
||||||
|
end
|
||||||
|
list.reject [1 2 3 4 5] is-even
|
||||||
|
`).toEvaluateTo([1, 3, 5])
|
||||||
|
})
|
||||||
|
|
||||||
test('list.reduce accumulates values', async () => {
|
test('list.reduce accumulates values', async () => {
|
||||||
await expect(`
|
await expect(`
|
||||||
add = do acc x:
|
add = do acc x:
|
||||||
|
|
@ -339,6 +349,22 @@ describe('collections', () => {
|
||||||
await expect(`arr = [1 2 3 4 5]; list.splice arr 3 2; arr`).toEvaluateTo([1, 2, 3])
|
await expect(`arr = [1 2 3 4 5]; list.splice arr 3 2; arr`).toEvaluateTo([1, 2, 3])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('list.insert adds element at index and mutates array', async () => {
|
||||||
|
await expect(`arr = [1 2 4 5]; list.insert arr 2 3; arr`).toEvaluateTo([1, 2, 3, 4, 5])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('list.insert returns array length', async () => {
|
||||||
|
await expect(`list.insert [1 2 4] 2 3`).toEvaluateTo(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('list.insert at start', async () => {
|
||||||
|
await expect(`arr = [2 3]; list.insert arr 0 1; arr`).toEvaluateTo([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('list.insert at end', async () => {
|
||||||
|
await expect(`arr = [1 2]; list.insert arr 2 99; arr`).toEvaluateTo([1, 2, 99])
|
||||||
|
})
|
||||||
|
|
||||||
test('list.sort with no callback sorts ascending', async () => {
|
test('list.sort with no callback sorts ascending', async () => {
|
||||||
await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5])
|
await expect(`list.sort [3 1 4 1 5] null`).toEvaluateTo([1, 1, 3, 4, 5])
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { describe } from 'bun:test'
|
import { describe } from 'bun:test'
|
||||||
import { expect, test } from 'bun:test'
|
import { expect, test } from 'bun:test'
|
||||||
import { Shrimp } from '..'
|
import { Shrimp, runCode, compileCode, parseCode, bytecodeToString } from '..'
|
||||||
|
|
||||||
describe('Shrimp', () => {
|
describe('Shrimp', () => {
|
||||||
test('allows running Shrimp code', async () => {
|
test('allows running Shrimp code', async () => {
|
||||||
|
|
@ -50,4 +50,403 @@ describe('Shrimp', () => {
|
||||||
await shrimp.run('abc = nothing')
|
await shrimp.run('abc = nothing')
|
||||||
expect(shrimp.get('abc')).toEqual('nothing')
|
expect(shrimp.get('abc')).toEqual('nothing')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('set()', () => {
|
||||||
|
test('allows setting variables', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
shrimp.set('foo', 42)
|
||||||
|
expect(shrimp.get('foo')).toEqual(42)
|
||||||
|
|
||||||
|
shrimp.set('bar', 'hello')
|
||||||
|
expect(shrimp.get('bar')).toEqual('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('set variables are accessible in code', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
shrimp.set('x', 10)
|
||||||
|
shrimp.set('y', 20)
|
||||||
|
|
||||||
|
const result = await shrimp.run('x + y')
|
||||||
|
expect(result).toEqual(30)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('allows setting functions', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
shrimp.set('double', (n: number) => n * 2)
|
||||||
|
|
||||||
|
const result = await shrimp.run('double 21')
|
||||||
|
expect(result).toEqual(42)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('overwrites existing variables', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('x = 100')
|
||||||
|
expect(shrimp.get('x')).toEqual(100)
|
||||||
|
|
||||||
|
shrimp.set('x', 200)
|
||||||
|
expect(shrimp.get('x')).toEqual(200)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('has()', () => {
|
||||||
|
test('returns true for existing variables', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('x = 5')
|
||||||
|
expect(shrimp.has('x')).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns false for non-existing variables', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
expect(shrimp.has('nonexistent')).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns true for globals', () => {
|
||||||
|
const shrimp = new Shrimp({ myGlobal: 42 })
|
||||||
|
|
||||||
|
expect(shrimp.has('myGlobal')).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns true for prelude functions', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
expect(shrimp.has('echo')).toEqual(true)
|
||||||
|
expect(shrimp.has('type')).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('call()', () => {
|
||||||
|
test('calls Shrimp functions with positional args', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run(`add = do x y:
|
||||||
|
x + y
|
||||||
|
end`)
|
||||||
|
|
||||||
|
const result = await shrimp.call('add', 5, 10)
|
||||||
|
expect(result).toEqual(15)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls Shrimp functions with named args', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run(`greet = do name:
|
||||||
|
str.join [ 'Hello ' name ] ''
|
||||||
|
end`)
|
||||||
|
|
||||||
|
const result = await shrimp.call('greet', { name: 'World' })
|
||||||
|
expect(result).toEqual('Hello World')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls native functions', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
shrimp.set('multiply', (a: number, b: number) => a * b)
|
||||||
|
|
||||||
|
const result = await shrimp.call('multiply', 6, 7)
|
||||||
|
expect(result).toEqual(42)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls prelude functions', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const result = await shrimp.call('type', 42)
|
||||||
|
expect(result).toEqual('number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('calls async functions', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
shrimp.set('fetchData', async () => {
|
||||||
|
return await Promise.resolve('async data')
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = await shrimp.call('fetchData')
|
||||||
|
expect(result).toEqual('async data')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('compile()', () => {
|
||||||
|
test('compiles code to bytecode', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const bytecode = shrimp.compile('x = 5')
|
||||||
|
|
||||||
|
expect(bytecode).toHaveProperty('instructions')
|
||||||
|
expect(bytecode).toHaveProperty('constants')
|
||||||
|
expect(bytecode).toHaveProperty('labels')
|
||||||
|
expect(bytecode.instructions.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('respects globals when compiling', () => {
|
||||||
|
const shrimp = new Shrimp({ customGlobal: 42 })
|
||||||
|
|
||||||
|
const bytecode = shrimp.compile('x = customGlobal')
|
||||||
|
expect(bytecode.instructions.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compiled bytecode can be run', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const bytecode = shrimp.compile('2 * 21')
|
||||||
|
const result = await shrimp.run(bytecode)
|
||||||
|
|
||||||
|
expect(result).toEqual(42)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('parse()', () => {
|
||||||
|
test('parses code to syntax tree', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const tree = shrimp.parse('x = 5')
|
||||||
|
|
||||||
|
expect(tree).toHaveProperty('length')
|
||||||
|
expect(tree).toHaveProperty('cursor')
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('respects globals when parsing', () => {
|
||||||
|
const shrimp = new Shrimp({ myVar: 42 })
|
||||||
|
|
||||||
|
const tree = shrimp.parse('x = myVar + 10')
|
||||||
|
|
||||||
|
// Should parse without errors
|
||||||
|
expect(tree).toHaveProperty('length')
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('parses function definitions', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const tree = shrimp.parse(`add = do x y:
|
||||||
|
x + y
|
||||||
|
end`)
|
||||||
|
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('get()', () => {
|
||||||
|
test('returns null for undefined variables', () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
expect(shrimp.get('undefined')).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns values from code execution', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('x = 42')
|
||||||
|
expect(shrimp.get('x')).toEqual(42)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns arrays', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('arr = [1 2 3]')
|
||||||
|
expect(shrimp.get('arr')).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns dicts', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('dict = [a=1 b=2]')
|
||||||
|
expect(shrimp.get('dict')).toEqual({ a: 1, b: 2 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('running bytecode directly', () => {
|
||||||
|
test('can run pre-compiled bytecode', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const bytecode = shrimp.compile('x = 100')
|
||||||
|
const result = await shrimp.run(bytecode)
|
||||||
|
|
||||||
|
expect(result).toEqual(100)
|
||||||
|
expect(shrimp.get('x')).toEqual(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('maintains state across bytecode runs', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
const bytecode1 = shrimp.compile('x = 10')
|
||||||
|
const bytecode2 = shrimp.compile('x + 5')
|
||||||
|
|
||||||
|
await shrimp.run(bytecode1)
|
||||||
|
const result = await shrimp.run(bytecode2)
|
||||||
|
|
||||||
|
expect(result).toEqual(15)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Functional API', () => {
|
||||||
|
describe('runCode()', () => {
|
||||||
|
test('runs code and returns result', async () => {
|
||||||
|
const result = await runCode('1 + 1')
|
||||||
|
expect(result).toEqual(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('works with globals', async () => {
|
||||||
|
const result = await runCode('greet', { greet: () => 'hello' })
|
||||||
|
expect(result).toEqual('hello')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('has access to prelude', async () => {
|
||||||
|
const result = await runCode('type 42')
|
||||||
|
expect(result).toEqual('number')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns null for empty code', async () => {
|
||||||
|
const result = await runCode('')
|
||||||
|
expect(result).toEqual(null)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('compileCode()', () => {
|
||||||
|
test('compiles code to bytecode', () => {
|
||||||
|
const bytecode = compileCode('x = 5')
|
||||||
|
|
||||||
|
expect(bytecode).toHaveProperty('instructions')
|
||||||
|
expect(bytecode).toHaveProperty('constants')
|
||||||
|
expect(bytecode.instructions.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('respects globals', () => {
|
||||||
|
const bytecode = compileCode('x = myGlobal', { myGlobal: 42 })
|
||||||
|
|
||||||
|
expect(bytecode.instructions.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compiled bytecode is usable', async () => {
|
||||||
|
const bytecode = compileCode('21 * 2')
|
||||||
|
const result = await runCode('21 * 2')
|
||||||
|
|
||||||
|
expect(result).toEqual(42)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('parseCode()', () => {
|
||||||
|
test('parses code to syntax tree', () => {
|
||||||
|
const tree = parseCode('x = 5')
|
||||||
|
|
||||||
|
expect(tree).toHaveProperty('length')
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('respects globals', () => {
|
||||||
|
const tree = parseCode('x = myGlobal', { myGlobal: 42 })
|
||||||
|
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles complex expressions', () => {
|
||||||
|
const tree = parseCode(`add = do x y:
|
||||||
|
x + y
|
||||||
|
end
|
||||||
|
result = add 5 10`)
|
||||||
|
|
||||||
|
expect(tree.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('bytecodeToString()', () => {
|
||||||
|
test('converts bytecode to human-readable format', () => {
|
||||||
|
const bytecode = compileCode('x = 42')
|
||||||
|
const str = bytecodeToString(bytecode)
|
||||||
|
|
||||||
|
expect(typeof str).toEqual('string')
|
||||||
|
expect(str.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows instructions', () => {
|
||||||
|
const bytecode = compileCode('1 + 1')
|
||||||
|
const str = bytecodeToString(bytecode)
|
||||||
|
|
||||||
|
// Should contain some opcodes
|
||||||
|
expect(str).toContain('PUSH')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Integration tests', () => {
|
||||||
|
test('complex REPL-like workflow', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
// Define a function
|
||||||
|
await shrimp.run(`double = do x:
|
||||||
|
x * 2
|
||||||
|
end`)
|
||||||
|
expect(shrimp.has('double')).toEqual(true)
|
||||||
|
|
||||||
|
// Use the function
|
||||||
|
const result1 = await shrimp.run('double 21')
|
||||||
|
expect(result1).toEqual(42)
|
||||||
|
|
||||||
|
// Call it from TypeScript
|
||||||
|
const result2 = await shrimp.call('double', 50)
|
||||||
|
expect(result2).toEqual(100)
|
||||||
|
|
||||||
|
// Define another function using the first
|
||||||
|
await shrimp.run(`quadruple = do x:
|
||||||
|
double (double x)
|
||||||
|
end`)
|
||||||
|
|
||||||
|
const result3 = await shrimp.run('quadruple 5')
|
||||||
|
expect(result3).toEqual(20)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mixing native and Shrimp functions', async () => {
|
||||||
|
const shrimp = new Shrimp({
|
||||||
|
log: (msg: string) => `Logged: ${msg}`,
|
||||||
|
multiply: (a: number, b: number) => a * b,
|
||||||
|
})
|
||||||
|
|
||||||
|
await shrimp.run(`greet = do name:
|
||||||
|
log name
|
||||||
|
end`)
|
||||||
|
|
||||||
|
const result1 = await shrimp.run('greet Alice')
|
||||||
|
expect(result1).toEqual('Logged: Alice')
|
||||||
|
|
||||||
|
await shrimp.run(`calc = do x:
|
||||||
|
multiply x 3
|
||||||
|
end`)
|
||||||
|
|
||||||
|
const result2 = await shrimp.run('calc 7')
|
||||||
|
expect(result2).toEqual(21)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('working with arrays and dicts', async () => {
|
||||||
|
const shrimp = new Shrimp()
|
||||||
|
|
||||||
|
await shrimp.run('nums = [1 2 3 4 5]')
|
||||||
|
expect(shrimp.get('nums')).toEqual([1, 2, 3, 4, 5])
|
||||||
|
|
||||||
|
await shrimp.run("config = [host='localhost' port=3000]")
|
||||||
|
expect(shrimp.get('config')).toEqual({ host: 'localhost', port: 3000 })
|
||||||
|
|
||||||
|
const result = await shrimp.run('length nums')
|
||||||
|
expect(result).toEqual(5)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compile once, run multiple times', async () => {
|
||||||
|
const bytecode = compileCode('x * 2')
|
||||||
|
|
||||||
|
const shrimp1 = new Shrimp()
|
||||||
|
shrimp1.set('x', 10)
|
||||||
|
const result1 = await shrimp1.run(bytecode)
|
||||||
|
expect(result1).toEqual(20)
|
||||||
|
|
||||||
|
const shrimp2 = new Shrimp()
|
||||||
|
shrimp2.set('x', 100)
|
||||||
|
const result2 = await shrimp2.run(bytecode)
|
||||||
|
expect(result2).toEqual(200)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user