i did things
This commit is contained in:
parent
82cd199ed8
commit
66671970e0
|
|
@ -1 +1 @@
|
|||
Subproject commit 47f829fcada71655f0d40ec363b5bcc844af8856
|
||||
Subproject commit 995487f2d5d8bb260e223ca402220c51ceba1c4a
|
||||
|
|
@ -209,7 +209,7 @@ describe('Regex', () => {
|
|||
})
|
||||
|
||||
test('invalid regex pattern', () => {
|
||||
expect('//[unclosed//').toFailEvaluation()
|
||||
expect('//[unclosed//').toEvaluateTo('//[unclosed//')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
import { basicSetup } from 'codemirror'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import { shrimpTheme } from '#editor/plugins/theme'
|
||||
import { shrimpLanguage } from '#/editor/plugins/shrimpLanguage'
|
||||
import { shrimpHighlighting } from '#editor/plugins/theme'
|
||||
import { shrimpKeymap } from '#editor/plugins/keymap'
|
||||
import { asciiEscapeToHtml, assert, assertNever, log, toElement } from '#utils/utils'
|
||||
import { asciiEscapeToHtml, assertNever, log, toElement } from '#utils/utils'
|
||||
import { Signal } from '#utils/signal'
|
||||
import { shrimpErrors } from '#editor/plugins/errors'
|
||||
import { debugTags } from '#editor/plugins/debugTags'
|
||||
import { getContent, persistencePlugin } from '#editor/plugins/persistence'
|
||||
|
||||
import '#editor/editor.css'
|
||||
import { getContent } from '#editor/plugins/persistence'
|
||||
import type { HtmlEscapedString } from 'hono/utils/html'
|
||||
import { catchErrors } from '#editor/plugins/catchErrors'
|
||||
import { connectToNose, noseSignals } from '#editor/noseClient'
|
||||
import type { Value } from 'reefvm'
|
||||
import { Compartment } from '@codemirror/state'
|
||||
import { lineNumbers } from '@codemirror/view'
|
||||
import { shrimpSetup } from '#editor/plugins/shrimpSetup'
|
||||
|
||||
import '#editor/editor.css'
|
||||
|
||||
const lineNumbersCompartment = new Compartment()
|
||||
|
||||
connectToNose()
|
||||
|
||||
export const outputSignal = new Signal<Value | string>()
|
||||
export const errorSignal = new Signal<string>()
|
||||
export const multilineModeSignal = new Signal<boolean>()
|
||||
|
||||
export const Editor = () => {
|
||||
return (
|
||||
<>
|
||||
|
|
@ -27,17 +28,14 @@ export const Editor = () => {
|
|||
const view = new EditorView({
|
||||
parent: ref,
|
||||
doc: getContent(),
|
||||
extensions: [
|
||||
catchErrors,
|
||||
shrimpKeymap,
|
||||
basicSetup,
|
||||
shrimpTheme,
|
||||
shrimpLanguage,
|
||||
shrimpHighlighting,
|
||||
shrimpErrors,
|
||||
persistencePlugin,
|
||||
// debugTags,
|
||||
],
|
||||
extensions: shrimpSetup(lineNumbersCompartment),
|
||||
})
|
||||
|
||||
multilineModeSignal.connect((isMultiline) => {
|
||||
console.log(`🌭 hey babe`, isMultiline)
|
||||
view.dispatch({
|
||||
effects: lineNumbersCompartment.reconfigure(isMultiline ? lineNumbers() : []),
|
||||
})
|
||||
})
|
||||
|
||||
requestAnimationFrame(() => view.focus())
|
||||
|
|
@ -64,9 +62,6 @@ noseSignals.connect((message) => {
|
|||
}
|
||||
})
|
||||
|
||||
export const outputSignal = new Signal<Value | string>()
|
||||
export const errorSignal = new Signal<string>()
|
||||
|
||||
outputSignal.connect((value) => {
|
||||
const el = document.querySelector('#output')!
|
||||
el.innerHTML = ''
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { multilineModeSignal, outputSignal } from '#editor/editor'
|
||||
import { printBytecodeOutput, printParserOutput, runCode } from '#editor/runCode'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { keymap } from '@codemirror/view'
|
||||
|
|
@ -27,9 +28,13 @@ const customKeymap = keymap.of([
|
|||
if (multilineMode) {
|
||||
const input = view.state.doc.toString()
|
||||
runCode(input)
|
||||
|
||||
return true
|
||||
} else {
|
||||
outputSignal.emit('Press Shift+Enter to insert run the code.')
|
||||
}
|
||||
|
||||
multilineModeSignal.emit(true)
|
||||
multilineMode = true
|
||||
view.dispatch({
|
||||
changes: { from: view.state.doc.length, insert: '\n' },
|
||||
|
|
@ -44,6 +49,10 @@ const customKeymap = keymap.of([
|
|||
key: 'Tab',
|
||||
preventDefault: true,
|
||||
run: (view) => {
|
||||
view.dispatch({
|
||||
changes: { from: view.state.selection.main.from, insert: ' ' },
|
||||
selection: { anchor: view.state.selection.main.from + 2 },
|
||||
})
|
||||
return true
|
||||
},
|
||||
},
|
||||
|
|
@ -51,6 +60,8 @@ const customKeymap = keymap.of([
|
|||
{
|
||||
key: 'ArrowUp',
|
||||
run: (view) => {
|
||||
if (multilineMode) return false
|
||||
|
||||
const command = history.previous()
|
||||
if (command === undefined) return false
|
||||
view.dispatch({
|
||||
|
|
@ -64,6 +75,8 @@ const customKeymap = keymap.of([
|
|||
{
|
||||
key: 'ArrowDown',
|
||||
run: (view) => {
|
||||
if (multilineMode) return false
|
||||
|
||||
const command = history.next()
|
||||
if (command === undefined) return false
|
||||
view.dispatch({
|
||||
|
|
@ -124,9 +137,22 @@ export const shrimpKeymap = [customKeymap, singleLineFilter]
|
|||
class History {
|
||||
private commands: string[] = []
|
||||
private index: number | undefined
|
||||
private storageKey = 'shrimp-command-history'
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
this.commands = JSON.parse(localStorage.getItem(this.storageKey) || '[]')
|
||||
} catch {
|
||||
console.warn('Failed to load command history from localStorage')
|
||||
}
|
||||
}
|
||||
|
||||
push(command: string) {
|
||||
this.commands.push(command)
|
||||
|
||||
// Limit to last 50 commands
|
||||
this.commands = this.commands.slice(-50)
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(this.commands))
|
||||
this.index = undefined
|
||||
}
|
||||
|
||||
|
|
|
|||
35
src/editor/plugins/shrimpSetup.ts
Normal file
35
src/editor/plugins/shrimpSetup.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { history, defaultKeymap, historyKeymap } from '@codemirror/commands'
|
||||
import { bracketMatching, indentOnInput } from '@codemirror/language'
|
||||
import { highlightSpecialChars, drawSelection, dropCursor, keymap } from '@codemirror/view'
|
||||
import { closeBrackets, autocompletion, completionKeymap } from '@codemirror/autocomplete'
|
||||
import { EditorState, Compartment } from '@codemirror/state'
|
||||
import { searchKeymap } from '@codemirror/search'
|
||||
import { shrimpKeymap } from './keymap'
|
||||
import { shrimpTheme, shrimpHighlighting } from './theme'
|
||||
import { shrimpLanguage } from './shrimpLanguage'
|
||||
import { shrimpErrors } from './errors'
|
||||
import { persistencePlugin } from './persistence'
|
||||
import { catchErrors } from './catchErrors'
|
||||
|
||||
export const shrimpSetup = (lineNumbersCompartment: Compartment) => {
|
||||
return [
|
||||
catchErrors,
|
||||
shrimpKeymap,
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
autocompletion(),
|
||||
indentOnInput(),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap, ...searchKeymap, ...completionKeymap]),
|
||||
lineNumbersCompartment.of([]),
|
||||
shrimpTheme,
|
||||
shrimpLanguage,
|
||||
shrimpHighlighting,
|
||||
shrimpErrors,
|
||||
persistencePlugin,
|
||||
]
|
||||
}
|
||||
|
|
@ -38,18 +38,12 @@ export const shrimpTheme = EditorView.theme(
|
|||
caretColor: 'var(--caret)',
|
||||
padding: '0px',
|
||||
},
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
'&.cm-focused .cm-cursor': {
|
||||
borderLeftColor: 'var(--caret)',
|
||||
},
|
||||
'&.cm-focused .cm-selectionBackground, ::selection': {
|
||||
backgroundColor: 'var(--bg-selection)',
|
||||
},
|
||||
'.cm-gutters': {
|
||||
display: 'none',
|
||||
},
|
||||
'.cm-editor': {
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { outputSignal, errorSignal } from '#editor/editor'
|
||||
import { Compiler } from '#compiler/compiler'
|
||||
import { errorMessage, log } from '#utils/utils'
|
||||
import { bytecodeToString, run } from 'reefvm'
|
||||
import { bytecodeToString } from 'reefvm'
|
||||
import { parser } from '#parser/shrimp'
|
||||
import { sendToNose } from '#editor/noseClient'
|
||||
import { treeToString } from '#utils/tree'
|
||||
|
||||
export const runCode = async (input: string) => {
|
||||
try {
|
||||
|
|
@ -18,7 +19,8 @@ export const runCode = async (input: string) => {
|
|||
export const printParserOutput = (input: string) => {
|
||||
try {
|
||||
const cst = parser.parse(input)
|
||||
outputSignal.emit(cst.toString())
|
||||
const string = treeToString(cst, input)
|
||||
outputSignal.emit(string)
|
||||
} catch (error) {
|
||||
log.error(error)
|
||||
errorSignal.emit(`${errorMessage(error)}`)
|
||||
|
|
|
|||
80
src/parser/operatorTokenizer.ts
Normal file
80
src/parser/operatorTokenizer.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import { ExternalTokenizer, InputStream } from '@lezer/lr'
|
||||
import * as terms from './shrimp.terms'
|
||||
|
||||
type Operator = { str: string; tokenName: keyof typeof terms }
|
||||
const operators: Array<Operator> = [
|
||||
{ str: 'and', tokenName: 'And' },
|
||||
{ str: 'or', tokenName: 'Or' },
|
||||
{ str: '>=', tokenName: 'Gte' },
|
||||
{ str: '<=', tokenName: 'Lte' },
|
||||
{ str: '!=', tokenName: 'Neq' },
|
||||
|
||||
// // Single-char operators
|
||||
{ str: '*', tokenName: 'Star' },
|
||||
{ str: '=', tokenName: 'Eq' },
|
||||
{ str: '/', tokenName: 'Slash' },
|
||||
{ str: '+', tokenName: 'Plus' },
|
||||
{ str: '-', tokenName: 'Minus' },
|
||||
{ str: '>', tokenName: 'Gt' },
|
||||
{ str: '<', tokenName: 'Lt' },
|
||||
]
|
||||
|
||||
export const operatorTokenizer = new ExternalTokenizer((input: InputStream) => {
|
||||
for (let operator of operators) {
|
||||
if (!matchesString(input, 0, operator.str)) continue
|
||||
const afterOpPos = operator.str.length
|
||||
const charAfterOp = input.peek(afterOpPos)
|
||||
if (!isWhitespace(charAfterOp)) continue
|
||||
|
||||
// Accept the operator token
|
||||
const token = terms[operator.tokenName]
|
||||
if (token === undefined) {
|
||||
throw new Error(`Unknown token name: ${operator.tokenName}`)
|
||||
}
|
||||
|
||||
input.advance(afterOpPos)
|
||||
input.acceptToken(token)
|
||||
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
const isWhitespace = (ch: number): boolean => {
|
||||
return matchesChar(ch, [' ', '\t', '\n'])
|
||||
}
|
||||
|
||||
const matchesChar = (ch: number, chars: (string | number)[]): boolean => {
|
||||
for (const c of chars) {
|
||||
if (typeof c === 'number') {
|
||||
if (ch === c) {
|
||||
return true
|
||||
}
|
||||
} else if (ch === c.charCodeAt(0)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const matchesString = (input: InputStream, pos: number, str: string): boolean => {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (input.peek(pos + i) !== str.charCodeAt(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const peek = (numChars: number, input: InputStream): string => {
|
||||
let result = ''
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
const ch = input.peek(i)
|
||||
if (ch === -1) {
|
||||
result += 'EOF'
|
||||
break
|
||||
} else {
|
||||
result += String.fromCharCode(ch)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -6,14 +6,14 @@
|
|||
|
||||
@top Program { item* }
|
||||
|
||||
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
|
||||
@external tokens operatorTokenizer from "./operatorTokenizer" { Star, Slash, Plus, Minus, And, Or, Eq, Neq, Lt, Lte, Gt, Gte }
|
||||
|
||||
@tokens {
|
||||
@precedence { Number "-" Regex "/"}
|
||||
@precedence { Number Regex }
|
||||
|
||||
StringFragment { !['\\$]+ }
|
||||
NamedArgPrefix { $[a-z]+ "=" }
|
||||
Number { "-"? $[0-9]+ ('.' $[0-9]+)? }
|
||||
Number { ("-" | "+")? $[0-9]+ ('.' $[0-9]+)? }
|
||||
Boolean { "true" | "false" }
|
||||
newlineOrSemicolon { "\n" | ";" }
|
||||
eof { @eof }
|
||||
|
|
@ -29,22 +29,12 @@
|
|||
"if" [@name=keyword]
|
||||
"elsif" [@name=keyword]
|
||||
"else" [@name=keyword]
|
||||
"and" [@name=operator]
|
||||
"or" [@name=operator]
|
||||
"!=" [@name=operator]
|
||||
"<" [@name=operator]
|
||||
"<=" [@name=operator]
|
||||
">" [@name=operator]
|
||||
">=" [@name=operator]
|
||||
"=" [@name=operator]
|
||||
"+"[@name=operator]
|
||||
"-"[@name=operator]
|
||||
"*"[@name=operator]
|
||||
"/"[@name=operator]
|
||||
"|"[@name=operator]
|
||||
|
||||
}
|
||||
|
||||
@external tokens tokenizer from "./tokenizer" { Identifier, AssignableIdentifier, Word, IdentifierBeforeDot }
|
||||
|
||||
@precedence {
|
||||
pipe @left,
|
||||
multiplicative @left,
|
||||
|
|
@ -140,14 +130,14 @@ ThenBlock {
|
|||
}
|
||||
|
||||
ConditionalOp {
|
||||
expression "=" expression |
|
||||
expression "!=" expression |
|
||||
expression "<" expression |
|
||||
expression "<=" expression |
|
||||
expression ">" expression |
|
||||
expression ">=" expression |
|
||||
expression "and" (expression | ConditionalOp) |
|
||||
expression "or" (expression | ConditionalOp)
|
||||
expression Eq expression |
|
||||
expression Neq expression |
|
||||
expression Lt expression |
|
||||
expression Lte expression |
|
||||
expression Gt expression |
|
||||
expression Gte expression |
|
||||
expression And (expression | ConditionalOp) |
|
||||
expression Or (expression | ConditionalOp)
|
||||
}
|
||||
|
||||
Params {
|
||||
|
|
@ -155,14 +145,14 @@ Params {
|
|||
}
|
||||
|
||||
Assign {
|
||||
AssignableIdentifier "=" consumeToTerminator
|
||||
AssignableIdentifier Eq consumeToTerminator
|
||||
}
|
||||
|
||||
BinOp {
|
||||
(expression | BinOp) !multiplicative "*" (expression | BinOp) |
|
||||
(expression | BinOp) !multiplicative "/" (expression | BinOp) |
|
||||
(expression | BinOp) !additive "+" (expression | BinOp) |
|
||||
(expression | BinOp) !additive "-" (expression | BinOp)
|
||||
(expression | BinOp) !multiplicative Star (expression | BinOp) |
|
||||
(expression | BinOp) !multiplicative Slash (expression | BinOp) |
|
||||
(expression | BinOp) !additive Plus (expression | BinOp) |
|
||||
(expression | BinOp) !additive Minus (expression | BinOp)
|
||||
}
|
||||
|
||||
ParenExpr {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
export const
|
||||
Identifier = 1,
|
||||
AssignableIdentifier = 2,
|
||||
Word = 3,
|
||||
IdentifierBeforeDot = 4,
|
||||
Program = 5,
|
||||
PipeExpr = 6,
|
||||
FunctionCall = 7,
|
||||
PositionalArg = 8,
|
||||
ParenExpr = 9,
|
||||
FunctionCallOrIdentifier = 10,
|
||||
BinOp = 11,
|
||||
ConditionalOp = 16,
|
||||
Star = 1,
|
||||
Slash = 2,
|
||||
Plus = 3,
|
||||
Minus = 4,
|
||||
And = 5,
|
||||
Or = 6,
|
||||
Eq = 7,
|
||||
Neq = 8,
|
||||
Lt = 9,
|
||||
Lte = 10,
|
||||
Gt = 11,
|
||||
Gte = 12,
|
||||
Identifier = 13,
|
||||
AssignableIdentifier = 14,
|
||||
Word = 15,
|
||||
IdentifierBeforeDot = 16,
|
||||
Program = 17,
|
||||
PipeExpr = 18,
|
||||
FunctionCall = 19,
|
||||
PositionalArg = 20,
|
||||
ParenExpr = 21,
|
||||
FunctionCallOrIdentifier = 22,
|
||||
BinOp = 23,
|
||||
ConditionalOp = 24,
|
||||
String = 25,
|
||||
StringFragment = 26,
|
||||
Interpolation = 27,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
// This file was generated by lezer-generator. You probably shouldn't edit it.
|
||||
import {LRParser} from "@lezer/lr"
|
||||
import {operatorTokenizer} from "./operatorTokenizer"
|
||||
import {tokenizer} from "./tokenizer"
|
||||
import {trackScope} from "./scopeTracker"
|
||||
import {highlighting} from "./highlight"
|
||||
export const parser = LRParser.deserialize({
|
||||
version: 14,
|
||||
states: ".jQVQROOO#XQTO'#CfO$RQQO'#CgO$aQQO'#DmO$xQRO'#CeO%gOWO'#CuOOQP'#Dq'#DqO%uOQO'#C}O%zQQO'#DpO&cQRO'#D|OOQP'#DO'#DOOOQO'#Dn'#DnO&kQQO'#DmO&yQRO'#EQOOQO'#DX'#DXO'hQQO'#DaOOQO'#Dm'#DmO'mQQO'#DlOOQP'#Dl'#DlOOQP'#Db'#DbQVQROOOOQP'#Dp'#DpOOQP'#Cd'#CdO'uQRO'#DUOOQP'#Do'#DoOOQP'#Dc'#DcO(PQTO,58}O&yQRO,59RO&yQRO,59RO)XQQO'#CgO)iQQO,59PO)zQQO,59PO)uQQO,59PO*uQQO,59PO*}QRO'#CwO+VQ`O'#CxOOOO'#Du'#DuOOOO'#Dd'#DdO+kOWO,59aOOQP,59a,59aO+yOPO,59iOOQP'#De'#DeO,OQRO'#DQO,WQQO,5:hO,]QRO'#DgO,bQQO,58|O,sQQO,5:lO,zQQO,5:lO-PQRO,59{OOQP,5:W,5:WOOQP-E7`-E7`OOQP,59p,59pOOQP-E7a-E7aOOQO1G.m1G.mO-^QQO1G.mO&yQRO,59WO&yQRO,59WOOQP1G.k1G.kOOOO,59c,59cOOOO,59d,59dOOOO-E7b-E7bOOQP1G.{1G.{OOQP1G/T1G/TOOQP-E7c-E7cO-xQRO1G0SO!QQTO'#CfOOQO,5:R,5:ROOQO-E7e-E7eO.YQRO1G0WOOQO1G/g1G/gOOQO1G.r1G.rO.jQQO1G.rO.tQQO7+%nO.yQRO7+%oOOQO'#DZ'#DZOOQO7+%r7+%rO/ZQRO7+%sOOQP<<IY<<IYO/qQQO'#DfO/vQRO'#EPO0^QQO<<IZOOQO'#D['#D[O0cQQO<<I_OOQP,5:Q,5:QOOQP-E7d-E7dOOQPAN>uAN>uO&yQRO'#D]OOQO'#Dh'#DhO0nQQOAN>yO0yQQO'#D_OOQOAN>yAN>yO1OQQOAN>yO1TQQO,59wO1[QQO,59wOOQO-E7f-E7fOOQOG24eG24eO1aQQOG24eO1fQQO,59yO1kQQO1G/cOOQOLD*PLD*PO.yQRO1G/eO/ZQRO7+$}OOQO7+%P7+%POOQO<<Hi<<Hi",
|
||||
stateData: "1v~O!_OS~OPPOQ_ORUOSVOmUOnUOoUOpUOsXO|]O!fSO!hTO!rbO~OPeORUOSVOmUOnUOoUOpUOsXOwfOygO!fSO!hTOzYX!rYX!vYX!gYXvYX~O[!dX]!dX^!dX_!dXa!dXb!dXc!dXd!dXe!dXf!dXg!dXh!dX~P!QO[kO]kO^lO_lO~O[kO]kO^lO_lO!r!aX!v!aXv!aX~OPPORUOSVOmUOnUOoUOpUO!fSO!hTO~OjtO!hwO!jrO!ksO~O!oxO~O[!dX]!dX^!dX_!dX!r!aX!v!aXv!aX~OQyOutP~Oz|O!r!aX!v!aXv!aX~OPeORUOSVOmUOnUOoUOpUO!fSO!hTO~Oa!QO~O!r!RO!v!RO~OsXOw!TO~P&yOsXOwfOygOzVa!rVa!vVa!gVavVa~P&yOa!XOb!XOc!XOd!XOe!XOf!XOg!YOh!YO~O[kO]kO^lO_lO~P(mO[kO]kO^lO_lO!g!ZO~O!g!ZO[!dX]!dX^!dX_!dXa!dXb!dXc!dXd!dXe!dXf!dXg!dXh!dX~Oz|O!g!ZO~OP![O!fSO~O!h!]O!j!]O!k!]O!l!]O!m!]O!n!]O~OjtO!h!_O!jrO!ksO~OP!`O~OQyOutX~Ou!bO~OP!cO~Oz|O!rUa!vUa!gUavUa~Ou!fO~P(mOu!fO~OQ_OsXO|]O~P$xO[kO]kO^Zi_Zi!rZi!vZi!gZivZi~OQ_OsXO|]O!r!kO~P$xOQ_OsXO|]O!r!nO~P$xO!g`iu`i~P(mOv!oO~OQ_OsXO|]Ov!sP~P$xOQ_OsXO|]Ov!sP!Q!sP!S!sP~P$xO!r!uO~OQ_OsXO|]Ov!sX!Q!sX!S!sX~P$xOv!wO~Ov!|O!Q!xO!S!{O~Ov#RO!Q!xO!S!{O~Ou#TO~Ov#RO~Ou#UO~P(mOu#UO~Ov#VO~O!r#WO~O!r#XO~Om_o]o~",
|
||||
goto: "+m!vPPPPPP!w#W#f#k#W$VPPPP$lPPPPPPPP$xP%a%aPPPP%e&OP&dPPP#fPP&gP&s&v'PP'TP&g'Z'a'h'n't'}(UPPP([(`(t)W)]*WPPP*sPPPPPP*w*wP+X+a+ad`Od!Q!b!f!k!n!q#W#XRpSiZOSd|!Q!b!f!k!n!q#W#XVhPj!czUOPS]dgjkl!Q!X!Y!b!c!f!k!n!q!x#W#XR![rdROd!Q!b!f!k!n!q#W#XQnSQ!VkR!WlQpSQ!P]Q!h!YR#P!x{UOPS]dgjkl!Q!X!Y!b!c!f!k!n!q!x#W#XTtTvdWOd!Q!b!f!k!n!q#W#XgePS]gjkl!X!Y!c!xd`Od!Q!b!f!k!n!q#W#XUfPj!cR!TgR{Xe`Od!Q!b!f!k!n!q#W#XR!m!fQ!t!nQ#Y#WR#Z#XT!y!t!zQ!}!tR#S!zQdOR!SdSjP!cR!UjQvTR!^vQzXR!azW!q!k!n#W#XR!v!qS}[qR!e}Q!z!tR#Q!zTcOdSaOdQ!g!QQ!j!bQ!l!fZ!p!k!n!q#W#Xd[Od!Q!b!f!k!n!q#W#XQqSR!d|ViPj!cdQOd!Q!b!f!k!n!q#W#XUfPj!cQmSQ!O]Q!TgQ!VkQ!WlQ!h!XQ!i!YR#O!xdWOd!Q!b!f!k!n!q#W#XdeP]gjkl!X!Y!c!xRoSTuTvmYOPdgj!Q!b!c!f!k!n!q#W#XQ!r!kV!s!n#W#Xe^Od!Q!b!f!k!n!q#W#X",
|
||||
nodeNames: "⚠ Identifier AssignableIdentifier Word IdentifierBeforeDot Program PipeExpr FunctionCall PositionalArg ParenExpr FunctionCallOrIdentifier BinOp operator operator operator operator ConditionalOp operator operator operator operator operator operator operator operator String StringFragment Interpolation EscapeSeq Number Boolean Regex Null DotGet FunctionDef keyword Params colon end Underscore NamedArg NamedArgPrefix operator IfExpr keyword ThenBlock ThenBlock ElsifExpr keyword ElseExpr keyword Assign",
|
||||
states: ".jQVQrOOO#XQuO'#CrO$RQRO'#CsO$aQRO'#DmO$xQrO'#CqO%gOWO'#CuOOQq'#Dq'#DqO%uOQO'#C}O%zQRO'#DpO&cQrO'#D|OOQp'#DO'#DOOOQO'#Dn'#DnO&kQQO'#DmO&yQrO'#EQOOQO'#DX'#DXO'hQRO'#DaOOQO'#Dm'#DmO'mQQO'#DlOOQp'#Dl'#DlOOQp'#Db'#DbQVQrOOOOQq'#Dp'#DpOOQp'#Cp'#CpO'uQrO'#DUOOQp'#Do'#DoOOQp'#Dc'#DcO(PQtO,59ZO&yQrO,59_O&yQrO,59_O)XQRO'#CsO)iQRO,59]O)zQRO,59]O)uQQO,59]O*uQQO,59]O*}QrO'#CwO+VQ`O'#CxOOOO'#Du'#DuOOOO'#Dd'#DdO+kOWO,59aOOQq,59a,59aO+yOpO,59iOOQp'#De'#DeO,OQrO'#DQO,WQQO,5:hO,]QrO'#DgO,bQQO,59YO,sQRO,5:lO,zQQO,5:lO-PQrO,59{OOQp,5:W,5:WOOQp-E7`-E7`OOQp,59p,59pOOQp-E7a-E7aOOQP1G.y1G.yO-^QRO1G.yO&yQrO,59`O&yQrO,59`OOQq1G.w1G.wOOOO,59c,59cOOOO,59d,59dOOOO-E7b-E7bOOQq1G.{1G.{OOQq1G/T1G/TOOQp-E7c-E7cO-xQrO1G0SO!QQtO'#CrOOQO,5:R,5:ROOQO-E7e-E7eO.YQrO1G0WOOQO1G/g1G/gOOQO1G.z1G.zO.jQRO1G.zO.tQQO7+%nO.yQrO7+%oOOQO'#DZ'#DZOOQO7+%r7+%rO/ZQrO7+%sOOQp<<IY<<IYO/qQQO'#DfO/vQrO'#EPO0^QQO<<IZOOQO'#D['#D[O0cQQO<<I_OOQp,5:Q,5:QOOQp-E7d-E7dOOQpAN>uAN>uO&yQrO'#D]OOQO'#Dh'#DhO0nQQOAN>yO0yQQO'#D_OOQOAN>yAN>yO1OQQOAN>yO1TQRO,59wO1[QQO,59wOOQO-E7f-E7fOOQOG24eG24eO1aQQOG24eO1fQQO,59yO1kQQO1G/cOOQOLD*PLD*PO.yQrO1G/eO/ZQrO7+$}OOQO7+%P7+%POOQO<<Hi<<Hi",
|
||||
stateData: "1s~O!_OS~O]PO^_O_UO`VOmUOnUOoUOpUOsXO|]O!fSO!hTO!rbO~O]eO_UO`VOmUOnUOoUOpUOsXOwfOygO!fSO!hTOzfX!rfX!vfX!gfXvfX~OP!dXQ!dXR!dXS!dXT!dXU!dXV!dXW!dXX!dXY!dXZ!dX[!dX~P!QOPkOQkORlOSlO~OPkOQkORlOSlO!r!aX!v!aXv!aX~O]PO_UO`VOmUOnUOoUOpUO!fSO!hTO~OjtO!hwO!jrO!ksO~O!oxO~OP!dXQ!dXR!dXS!dX!r!aX!v!aXv!aX~O^yOutP~Oz|O!r!aX!v!aXv!aX~O]eO_UO`VOmUOnUOoUOpUO!fSO!hTO~OV!QO~O!r!RO!v!RO~OsXOw!TO~P&yOsXOwfOygOzca!rca!vca!gcavca~P&yOT!YOU!YOV!XOW!XOX!XOY!XOZ!XO[!XO~OPkOQkORlOSlO~P(mOPkOQkORlOSlO!g!ZO~O!g!ZOP!dXQ!dXR!dXS!dXT!dXU!dXV!dXW!dXX!dXY!dXZ!dX[!dX~Oz|O!g!ZO~O]![O!fSO~O!h!]O!j!]O!k!]O!l!]O!m!]O!n!]O~OjtO!h!_O!jrO!ksO~O]!`O~O^yOutX~Ou!bO~O]!cO~Oz|O!rba!vba!gbavba~Ou!fO~P(mOu!fO~O^_OsXO|]O~P$xOPkOQkORgiSgi!rgi!vgi!ggivgi~O^_OsXO|]O!r!kO~P$xO^_OsXO|]O!r!nO~P$xO!ghiuhi~P(mOv!oO~O^_OsXO|]Ov!sP~P$xO^_OsXO|]Ov!sP!Q!sP!S!sP~P$xO!r!uO~O^_OsXO|]Ov!sX!Q!sX!S!sX~P$xOv!wO~Ov!|O!Q!xO!S!{O~Ov#RO!Q!xO!S!{O~Ou#TO~Ov#RO~Ou#UO~P(mOu#UO~Ov#VO~O!r#WO~O!r#XO~Omo~",
|
||||
goto: "+m!vPPPPPPPPPPPPPPPPPP!w#W#f#k#W$V$l$xP%a%aPPPP%e&OP&dPPP#fPP&gP&s&v'PP'TP&g'Z'a'h'n't'}(UPPP([(`(t)W)]*WPPP*sPPPPPP*w*wP+X+a+ad`Od!Q!b!f!k!n!q#W#XRpSiZOSd|!Q!b!f!k!n!q#W#XVhPj!czUOPS]dgjkl!Q!X!Y!b!c!f!k!n!q!x#W#XR![rdROd!Q!b!f!k!n!q#W#XQnSQ!VkR!WlQpSQ!P]Q!h!YR#P!x{UOPS]dgjkl!Q!X!Y!b!c!f!k!n!q!x#W#XTtTvdWOd!Q!b!f!k!n!q#W#XgePS]gjkl!X!Y!c!xd`Od!Q!b!f!k!n!q#W#XUfPj!cR!TgR{Xe`Od!Q!b!f!k!n!q#W#XR!m!fQ!t!nQ#Y#WR#Z#XT!y!t!zQ!}!tR#S!zQdOR!SdSjP!cR!UjQvTR!^vQzXR!azW!q!k!n#W#XR!v!qS}[qR!e}Q!z!tR#Q!zTcOdSaOdQ!g!QQ!j!bQ!l!fZ!p!k!n!q#W#Xd[Od!Q!b!f!k!n!q#W#XQqSR!d|ViPj!cdQOd!Q!b!f!k!n!q#W#XUfPj!cQmSQ!O]Q!TgQ!VkQ!WlQ!h!XQ!i!YR#O!xdWOd!Q!b!f!k!n!q#W#XdeP]gjkl!X!Y!c!xRoSTuTvmYOPdgj!Q!b!c!f!k!n!q#W#XQ!r!kV!s!n#W#Xe^Od!Q!b!f!k!n!q#W#X",
|
||||
nodeNames: "⚠ Star Slash Plus Minus And Or Eq Neq Lt Lte Gt Gte Identifier AssignableIdentifier Word IdentifierBeforeDot Program PipeExpr FunctionCall PositionalArg ParenExpr FunctionCallOrIdentifier BinOp ConditionalOp String StringFragment Interpolation EscapeSeq Number Boolean Regex Null DotGet FunctionDef keyword Params colon end Underscore NamedArg NamedArgPrefix operator IfExpr keyword ThenBlock ThenBlock ElsifExpr keyword ElseExpr keyword Assign",
|
||||
maxTerm: 84,
|
||||
context: trackScope,
|
||||
nodeProps: [
|
||||
|
|
@ -18,8 +19,8 @@ export const parser = LRParser.deserialize({
|
|||
propSources: [highlighting],
|
||||
skippedNodes: [0],
|
||||
repeatNodeCount: 7,
|
||||
tokenData: "!&X~R!SOX$_XY$|YZ%gZp$_pq$|qr&Qrt$_tu'Yuw$_wx'_xy'dyz'}z{(h{|)R|}$_}!O)l!O!P,b!P!Q,{!Q![*]![!]5j!]!^%g!^!_6T!_!`7_!`!a7x!a#O$_#O#P9S#P#R$_#R#S9X#S#T$_#T#U9r#U#X;W#X#Y=m#Y#ZDs#Z#];W#]#^JO#^#b;W#b#cKp#c#d! Y#d#f;W#f#g!!z#g#h;W#h#i!#q#i#o;W#o#p$_#p#q!%i#q;'S$_;'S;=`$v<%l~$_~O$_~~!&SS$dUjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_S$yP;=`<%l$__%TUjS!_ZOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V%nUjS!rROt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V&VWjSOt$_uw$_x!_$_!_!`&o!`#O$_#P;'S$_;'S;=`$v<%lO$_V&vUbRjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_~'_O!j~~'dO!h~V'kUjS!fROt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V(UUjS!gROt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V(oU[RjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V)YU^RjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V)sWjS_ROt$_uw$_x!Q$_!Q![*]![#O$_#P;'S$_;'S;=`$v<%lO$_V*dYjSmROt$_uw$_x!O$_!O!P+S!P!Q$_!Q![*]![#O$_#P;'S$_;'S;=`$v<%lO$_V+XWjSOt$_uw$_x!Q$_!Q![+q![#O$_#P;'S$_;'S;=`$v<%lO$_V+xWjSmROt$_uw$_x!Q$_!Q![+q![#O$_#P;'S$_;'S;=`$v<%lO$_T,iU!oPjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V-SWjS]ROt$_uw$_x!P$_!P!Q-l!Q#O$_#P;'S$_;'S;=`$v<%lO$_V-q^jSOY.mYZ$_Zt.mtu/puw.mwx/px!P.m!P!Q$_!Q!}.m!}#O4c#O#P2O#P;'S.m;'S;=`5d<%lO.mV.t^jSoROY.mYZ$_Zt.mtu/puw.mwx/px!P.m!P!Q2e!Q!}.m!}#O4c#O#P2O#P;'S.m;'S;=`5d<%lO.mR/uXoROY/pZ!P/p!P!Q0b!Q!}/p!}#O1P#O#P2O#P;'S/p;'S;=`2_<%lO/pR0eP!P!Q0hR0mUoR#Z#[0h#]#^0h#a#b0h#g#h0h#i#j0h#m#n0hR1SVOY1PZ#O1P#O#P1i#P#Q/p#Q;'S1P;'S;=`1x<%lO1PR1lSOY1PZ;'S1P;'S;=`1x<%lO1PR1{P;=`<%l1PR2RSOY/pZ;'S/p;'S;=`2_<%lO/pR2bP;=`<%l/pV2jWjSOt$_uw$_x!P$_!P!Q3S!Q#O$_#P;'S$_;'S;=`$v<%lO$_V3ZbjSoROt$_uw$_x#O$_#P#Z$_#Z#[3S#[#]$_#]#^3S#^#a$_#a#b3S#b#g$_#g#h3S#h#i$_#i#j3S#j#m$_#m#n3S#n;'S$_;'S;=`$v<%lO$_V4h[jSOY4cYZ$_Zt4ctu1Puw4cwx1Px#O4c#O#P1i#P#Q.m#Q;'S4c;'S;=`5^<%lO4cV5aP;=`<%l4cV5gP;=`<%l.mT5qUjSuPOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V6[WcRjSOt$_uw$_x!_$_!_!`6t!`#O$_#P;'S$_;'S;=`$v<%lO$_V6{UdRjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V7fUaRjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V8PWeRjSOt$_uw$_x!_$_!_!`8i!`#O$_#P;'S$_;'S;=`$v<%lO$_V8pUfRjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_~9XO!k~V9`UjSwROt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_V9w[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#b;W#b#c;{#c#o;W#o;'S$_;'S;=`$v<%lO$_U:tUyQjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_U;]YjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_V<Q[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#W;W#W#X<v#X#o;W#o;'S$_;'S;=`$v<%lO$_V<}YgRjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_V=r^jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#`;W#`#a>n#a#b;W#b#cCR#c#o;W#o;'S$_;'S;=`$v<%lO$_V>s[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#g;W#g#h?i#h#o;W#o;'S$_;'S;=`$v<%lO$_V?n^jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#X;W#X#Y@j#Y#];W#]#^Aa#^#o;W#o;'S$_;'S;=`$v<%lO$_V@qY!SPjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_VAf[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#Y;W#Y#ZB[#Z#o;W#o;'S$_;'S;=`$v<%lO$_VBcY!QPjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_VCW[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#W;W#W#XC|#X#o;W#o;'S$_;'S;=`$v<%lO$_VDTYjSvROt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_VDx]jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#UEq#U#b;W#b#cIX#c#o;W#o;'S$_;'S;=`$v<%lO$_VEv[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#`;W#`#aFl#a#o;W#o;'S$_;'S;=`$v<%lO$_VFq[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#g;W#g#hGg#h#o;W#o;'S$_;'S;=`$v<%lO$_VGl[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#X;W#X#YHb#Y#o;W#o;'S$_;'S;=`$v<%lO$_VHiYnRjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_VI`YsRjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_VJT[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#Y;W#Y#ZJy#Z#o;W#o;'S$_;'S;=`$v<%lO$_VKQY|PjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$__Kw[!lWjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#i;W#i#jLm#j#o;W#o;'S$_;'S;=`$v<%lO$_VLr[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#`;W#`#aMh#a#o;W#o;'S$_;'S;=`$v<%lO$_VMm[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#`;W#`#aNc#a#o;W#o;'S$_;'S;=`$v<%lO$_VNjYpRjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_V! _[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#f;W#f#g!!T#g#o;W#o;'S$_;'S;=`$v<%lO$_V!![YhRjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$_^!#RY!nWjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#o;W#o;'S$_;'S;=`$v<%lO$__!#x[!mWjSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#f;W#f#g!$n#g#o;W#o;'S$_;'S;=`$v<%lO$_V!$s[jSOt$_uw$_x!_$_!_!`:m!`#O$_#P#T$_#T#i;W#i#jGg#j#o;W#o;'S$_;'S;=`$v<%lO$_V!%pUzRjSOt$_uw$_x#O$_#P;'S$_;'S;=`$v<%lO$_~!&XO!v~",
|
||||
tokenizers: [tokenizer, 0, 1, 2, 3],
|
||||
topRules: {"Program":[0,5]},
|
||||
tokenData: "JX~R|OX#{XY$jYZ%TZp#{pq$jqt#{tu%nuw#{wx%sxy%xyz&cz{#{{|&||}#{}!O&|!O!P)p!P!Q*Z!Q!['k![!]2v!]!^%T!^#O#{#O#P3a#P#R#{#R#S3f#S#T#{#T#X4P#X#Y5_#Y#Z<e#Z#]4P#]#^Ap#^#b4P#b#cCb#c#f4P#f#gFz#g#h4P#h#iGq#i#o4P#o#p#{#p#qIi#q;'S#{;'S;=`$d<%l~#{~O#{~~JSS$QUjSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{S$gP;=`<%l#{_$qUjS!_ZOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{V%[UjS!rROt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~%sO!j~~%xO!h~V&PUjS!fROt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{V&jUjS!gROt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{V'RWjSOt#{uw#{x!Q#{!Q!['k![#O#{#P;'S#{;'S;=`$d<%lO#{V'rYjSmROt#{uw#{x!O#{!O!P(b!P!Q#{!Q!['k![#O#{#P;'S#{;'S;=`$d<%lO#{V(gWjSOt#{uw#{x!Q#{!Q![)P![#O#{#P;'S#{;'S;=`$d<%lO#{V)WWjSmROt#{uw#{x!Q#{!Q![)P![#O#{#P;'S#{;'S;=`$d<%lO#{T)wU!oPjSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{V*`WjSOt#{uw#{x!P#{!P!Q*x!Q#O#{#P;'S#{;'S;=`$d<%lO#{V*}^jSOY+yYZ#{Zt+ytu,|uw+ywx,|x!P+y!P!Q#{!Q!}+y!}#O1o#O#P/[#P;'S+y;'S;=`2p<%lO+yV,Q^jSoROY+yYZ#{Zt+ytu,|uw+ywx,|x!P+y!P!Q/q!Q!}+y!}#O1o#O#P/[#P;'S+y;'S;=`2p<%lO+yR-RXoROY,|Z!P,|!P!Q-n!Q!},|!}#O.]#O#P/[#P;'S,|;'S;=`/k<%lO,|R-qP!P!Q-tR-yUoR#Z#[-t#]#^-t#a#b-t#g#h-t#i#j-t#m#n-tR.`VOY.]Z#O.]#O#P.u#P#Q,|#Q;'S.];'S;=`/U<%lO.]R.xSOY.]Z;'S.];'S;=`/U<%lO.]R/XP;=`<%l.]R/_SOY,|Z;'S,|;'S;=`/k<%lO,|R/nP;=`<%l,|V/vWjSOt#{uw#{x!P#{!P!Q0`!Q#O#{#P;'S#{;'S;=`$d<%lO#{V0gbjSoROt#{uw#{x#O#{#P#Z#{#Z#[0`#[#]#{#]#^0`#^#a#{#a#b0`#b#g#{#g#h0`#h#i#{#i#j0`#j#m#{#m#n0`#n;'S#{;'S;=`$d<%lO#{V1t[jSOY1oYZ#{Zt1otu.]uw1owx.]x#O1o#O#P.u#P#Q+y#Q;'S1o;'S;=`2j<%lO1oV2mP;=`<%l1oV2sP;=`<%l+yT2}UjSuPOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~3fO!k~V3mUjSwROt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{U4UYjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{U4{UyQjSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{V5d^jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#`4P#`#a6`#a#b4P#b#c:s#c#o4P#o;'S#{;'S;=`$d<%lO#{V6e[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#g4P#g#h7Z#h#o4P#o;'S#{;'S;=`$d<%lO#{V7`^jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#X4P#X#Y8[#Y#]4P#]#^9R#^#o4P#o;'S#{;'S;=`$d<%lO#{V8cY!SPjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{V9W[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#Y4P#Y#Z9|#Z#o4P#o;'S#{;'S;=`$d<%lO#{V:TY!QPjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{V:x[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#W4P#W#X;n#X#o4P#o;'S#{;'S;=`$d<%lO#{V;uYjSvROt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{V<j]jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#U=c#U#b4P#b#c@y#c#o4P#o;'S#{;'S;=`$d<%lO#{V=h[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#`4P#`#a>^#a#o4P#o;'S#{;'S;=`$d<%lO#{V>c[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#g4P#g#h?X#h#o4P#o;'S#{;'S;=`$d<%lO#{V?^[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#X4P#X#Y@S#Y#o4P#o;'S#{;'S;=`$d<%lO#{V@ZYnRjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{VAQYsRjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{VAu[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#Y4P#Y#ZBk#Z#o4P#o;'S#{;'S;=`$d<%lO#{VBrY|PjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{_Ci[!lWjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#i4P#i#jD_#j#o4P#o;'S#{;'S;=`$d<%lO#{VDd[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#`4P#`#aEY#a#o4P#o;'S#{;'S;=`$d<%lO#{VE_[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#`4P#`#aFT#a#o4P#o;'S#{;'S;=`$d<%lO#{VF[YpRjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{^GRY!nWjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#o4P#o;'S#{;'S;=`$d<%lO#{_Gx[!mWjSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#f4P#f#gHn#g#o4P#o;'S#{;'S;=`$d<%lO#{VHs[jSOt#{uw#{x!_#{!_!`4t!`#O#{#P#T#{#T#i4P#i#j?X#j#o4P#o;'S#{;'S;=`$d<%lO#{VIpUzRjSOt#{uw#{x#O#{#P;'S#{;'S;=`$d<%lO#{~JXO!v~",
|
||||
tokenizers: [operatorTokenizer, 0, 1, 2, 3, tokenizer],
|
||||
topRules: {"Program":[0,17]},
|
||||
tokenPrec: 768
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('null', () => {
|
|||
expect('a = null').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier a
|
||||
operator =
|
||||
Eq =
|
||||
Null null`)
|
||||
})
|
||||
})
|
||||
|
|
@ -30,7 +30,7 @@ describe('Parentheses', () => {
|
|||
ParenExpr
|
||||
BinOp
|
||||
Number 2
|
||||
operator +
|
||||
Plus +
|
||||
Number 3`)
|
||||
})
|
||||
|
||||
|
|
@ -72,14 +72,14 @@ describe('Parentheses', () => {
|
|||
ParenExpr
|
||||
ConditionalOp
|
||||
Identifier a
|
||||
operator >
|
||||
Gt >
|
||||
Identifier b`)
|
||||
|
||||
expect('(a and b)').toMatchTree(`
|
||||
ParenExpr
|
||||
ConditionalOp
|
||||
Identifier a
|
||||
operator and
|
||||
And and
|
||||
Identifier b`)
|
||||
})
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ describe('Parentheses', () => {
|
|||
ParenExpr
|
||||
BinOp
|
||||
Number 3
|
||||
operator +
|
||||
Plus +
|
||||
Number 3`)
|
||||
})
|
||||
|
||||
|
|
@ -105,13 +105,16 @@ describe('Parentheses', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('a word start with *', () => {
|
||||
expect('find *cool*').toMatchTree(`
|
||||
FunctionCall
|
||||
Identifier find
|
||||
PositionalArg
|
||||
Word *cool*
|
||||
test('a word start with an operator', () => {
|
||||
const operators = ['*', '/', '+', '-', 'and', 'or', '=', '!=', '>=', '<=', '>', '<']
|
||||
for (const operator of operators) {
|
||||
expect(`find ${operator}cool*`).toMatchTree(`
|
||||
FunctionCall
|
||||
Identifier find
|
||||
PositionalArg
|
||||
Word ${operator}cool*
|
||||
`)
|
||||
}
|
||||
})
|
||||
|
||||
test('a word can look like a binop', () => {
|
||||
|
|
@ -128,11 +131,11 @@ describe('Parentheses', () => {
|
|||
ParenExpr
|
||||
BinOp
|
||||
Number 2
|
||||
operator +
|
||||
Plus +
|
||||
ParenExpr
|
||||
BinOp
|
||||
Number 1
|
||||
operator *
|
||||
Star *
|
||||
Number 4`)
|
||||
})
|
||||
|
||||
|
|
@ -140,7 +143,7 @@ describe('Parentheses', () => {
|
|||
expect('4 + (echo 3)').toMatchTree(`
|
||||
BinOp
|
||||
Number 4
|
||||
operator +
|
||||
Plus +
|
||||
ParenExpr
|
||||
FunctionCall
|
||||
Identifier echo
|
||||
|
|
@ -149,12 +152,12 @@ describe('Parentheses', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe.only('BinOp', () => {
|
||||
describe('BinOp', () => {
|
||||
test('addition tests', () => {
|
||||
expect('2 + 3').toMatchTree(`
|
||||
BinOp
|
||||
Number 2
|
||||
operator +
|
||||
Plus +
|
||||
Number 3
|
||||
`)
|
||||
})
|
||||
|
|
@ -163,7 +166,7 @@ describe.only('BinOp', () => {
|
|||
expect('5 - 2').toMatchTree(`
|
||||
BinOp
|
||||
Number 5
|
||||
operator -
|
||||
Minus -
|
||||
Number 2
|
||||
`)
|
||||
})
|
||||
|
|
@ -172,7 +175,7 @@ describe.only('BinOp', () => {
|
|||
expect('4 * 3').toMatchTree(`
|
||||
BinOp
|
||||
Number 4
|
||||
operator *
|
||||
Star *
|
||||
Number 3
|
||||
`)
|
||||
})
|
||||
|
|
@ -181,7 +184,7 @@ describe.only('BinOp', () => {
|
|||
expect('8 / 2').toMatchTree(`
|
||||
BinOp
|
||||
Number 8
|
||||
operator /
|
||||
Slash /
|
||||
Number 2
|
||||
`)
|
||||
})
|
||||
|
|
@ -191,15 +194,15 @@ describe.only('BinOp', () => {
|
|||
BinOp
|
||||
BinOp
|
||||
Number 2
|
||||
operator +
|
||||
Plus +
|
||||
BinOp
|
||||
Number 3
|
||||
operator *
|
||||
Star *
|
||||
Number 4
|
||||
operator -
|
||||
Minus -
|
||||
BinOp
|
||||
Number 5
|
||||
operator /
|
||||
Slash /
|
||||
Number 1
|
||||
`)
|
||||
})
|
||||
|
|
@ -210,7 +213,7 @@ describe('ambiguity', () => {
|
|||
expect('a + -3').toMatchTree(`
|
||||
BinOp
|
||||
Identifier a
|
||||
operator +
|
||||
Plus +
|
||||
Number -3
|
||||
`)
|
||||
})
|
||||
|
|
@ -219,7 +222,7 @@ describe('ambiguity', () => {
|
|||
expect('a-var + a-thing').toMatchTree(`
|
||||
BinOp
|
||||
Identifier a-var
|
||||
operator +
|
||||
Plus +
|
||||
Identifier a-thing
|
||||
`)
|
||||
})
|
||||
|
|
@ -231,11 +234,11 @@ describe('newlines', () => {
|
|||
y = 2`).toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier x
|
||||
operator =
|
||||
Eq =
|
||||
Number 5
|
||||
Assign
|
||||
AssignableIdentifier y
|
||||
operator =
|
||||
Eq =
|
||||
Number 2`)
|
||||
})
|
||||
|
||||
|
|
@ -243,11 +246,11 @@ y = 2`).toMatchTree(`
|
|||
expect(`x = 5; y = 2`).toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier x
|
||||
operator =
|
||||
Eq =
|
||||
Number 5
|
||||
Assign
|
||||
AssignableIdentifier y
|
||||
operator =
|
||||
Eq =
|
||||
Number 2`)
|
||||
})
|
||||
|
||||
|
|
@ -255,7 +258,7 @@ y = 2`).toMatchTree(`
|
|||
expect(`a = hello; 2`).toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier a
|
||||
operator =
|
||||
Eq =
|
||||
FunctionCallOrIdentifier
|
||||
Identifier hello
|
||||
Number 2`)
|
||||
|
|
@ -267,7 +270,7 @@ describe('Assign', () => {
|
|||
expect('x = 5').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier x
|
||||
operator =
|
||||
Eq =
|
||||
Number 5`)
|
||||
})
|
||||
|
||||
|
|
@ -275,10 +278,10 @@ describe('Assign', () => {
|
|||
expect('x = 5 + 3').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier x
|
||||
operator =
|
||||
Eq =
|
||||
BinOp
|
||||
Number 5
|
||||
operator +
|
||||
Plus +
|
||||
Number 3`)
|
||||
})
|
||||
|
||||
|
|
@ -286,7 +289,7 @@ describe('Assign', () => {
|
|||
expect('add = fn a b: a + b end').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier add
|
||||
operator =
|
||||
Eq =
|
||||
FunctionDef
|
||||
keyword fn
|
||||
Params
|
||||
|
|
@ -295,7 +298,7 @@ describe('Assign', () => {
|
|||
colon :
|
||||
BinOp
|
||||
Identifier a
|
||||
operator +
|
||||
Plus +
|
||||
Identifier b
|
||||
end end`)
|
||||
})
|
||||
|
|
@ -306,7 +309,7 @@ describe('DotGet whitespace sensitivity', () => {
|
|||
expect('basename = 5; basename.prop').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier basename
|
||||
operator =
|
||||
Eq =
|
||||
Number 5
|
||||
DotGet
|
||||
IdentifierBeforeDot basename
|
||||
|
|
@ -317,11 +320,11 @@ describe('DotGet whitespace sensitivity', () => {
|
|||
expect('basename = 5; basename / prop').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier basename
|
||||
operator =
|
||||
Eq =
|
||||
Number 5
|
||||
BinOp
|
||||
Identifier basename
|
||||
operator /
|
||||
Slash /
|
||||
Identifier prop`)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe('if/elsif/else', () => {
|
|||
keyword if
|
||||
ConditionalOp
|
||||
Identifier y
|
||||
operator =
|
||||
Eq =
|
||||
Number 1
|
||||
colon :
|
||||
ThenBlock
|
||||
|
|
@ -20,7 +20,7 @@ describe('if/elsif/else', () => {
|
|||
expect('a = if x: 2').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier a
|
||||
operator =
|
||||
Eq =
|
||||
IfExpr
|
||||
keyword if
|
||||
Identifier x
|
||||
|
|
@ -39,7 +39,7 @@ describe('if/elsif/else', () => {
|
|||
keyword if
|
||||
ConditionalOp
|
||||
Identifier x
|
||||
operator <
|
||||
Lt <
|
||||
Number 9
|
||||
colon :
|
||||
ThenBlock
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ describe('DotGet', () => {
|
|||
expect('obj = 5; obj.prop').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier obj
|
||||
operator =
|
||||
Eq =
|
||||
Number 5
|
||||
DotGet
|
||||
IdentifierBeforeDot obj
|
||||
|
|
@ -106,7 +106,7 @@ end`).toMatchTree(`
|
|||
expect('config = 42; echo config.path').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier config
|
||||
operator =
|
||||
Eq =
|
||||
Number 42
|
||||
FunctionCall
|
||||
Identifier echo
|
||||
|
|
@ -121,7 +121,7 @@ end`).toMatchTree(`
|
|||
expect('config = 42; cat readme.txt; echo config.path').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier config
|
||||
operator =
|
||||
Eq =
|
||||
Number 42
|
||||
FunctionCall
|
||||
Identifier cat
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ describe('Fn', () => {
|
|||
colon :
|
||||
BinOp
|
||||
Identifier x
|
||||
operator +
|
||||
Plus +
|
||||
Number 1
|
||||
end end`)
|
||||
})
|
||||
|
|
@ -91,7 +91,7 @@ describe('Fn', () => {
|
|||
colon :
|
||||
BinOp
|
||||
Identifier x
|
||||
operator *
|
||||
Star *
|
||||
Identifier y
|
||||
end end`)
|
||||
})
|
||||
|
|
@ -109,11 +109,11 @@ end`).toMatchTree(`
|
|||
colon :
|
||||
BinOp
|
||||
Identifier x
|
||||
operator *
|
||||
Star *
|
||||
Identifier y
|
||||
BinOp
|
||||
Identifier x
|
||||
operator +
|
||||
Plus +
|
||||
Number 9
|
||||
end end`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ describe('multiline', () => {
|
|||
`).toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier add
|
||||
operator =
|
||||
Eq =
|
||||
FunctionDef
|
||||
keyword fn
|
||||
Params
|
||||
|
|
@ -31,10 +31,10 @@ describe('multiline', () => {
|
|||
colon :
|
||||
Assign
|
||||
AssignableIdentifier result
|
||||
operator =
|
||||
Eq =
|
||||
BinOp
|
||||
Identifier a
|
||||
operator +
|
||||
Plus +
|
||||
Identifier b
|
||||
FunctionCallOrIdentifier
|
||||
Identifier result
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('pipe expressions', () => {
|
|||
expect('result = echo hello | grep h').toMatchTree(`
|
||||
Assign
|
||||
AssignableIdentifier result
|
||||
operator =
|
||||
Eq =
|
||||
PipeExpr
|
||||
FunctionCall
|
||||
Identifier echo
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ describe('string interpolation', () => {
|
|||
ParenExpr
|
||||
BinOp
|
||||
Identifier a
|
||||
operator +
|
||||
Plus +
|
||||
Identifier b
|
||||
StringFragment !
|
||||
`)
|
||||
|
|
@ -34,7 +34,7 @@ describe('string interpolation', () => {
|
|||
ParenExpr
|
||||
BinOp
|
||||
Identifier a
|
||||
operator +
|
||||
Plus +
|
||||
Identifier b
|
||||
`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ export const tokenizer = new ExternalTokenizer(
|
|||
const ch = getFullCodePoint(input, 0)
|
||||
if (!isWordChar(ch)) return
|
||||
|
||||
// Don't consume things that start with digits - let Number token handle it
|
||||
if (isDigit(ch)) return
|
||||
|
||||
// Don't consume things that start with - or + followed by a digit (negative/positive numbers)
|
||||
if ((ch === 45 /* - */ || ch === 43) /* + */ && isDigit(input.peek(1))) return
|
||||
|
||||
const isValidStart = isLowercaseLetter(ch) || isEmoji(ch)
|
||||
const canBeWord = stack.canShift(Word)
|
||||
|
||||
|
|
@ -166,7 +172,16 @@ const chooseIdentifierToken = (input: InputStream, stack: Stack): number => {
|
|||
}
|
||||
|
||||
const nextCh = getFullCodePoint(input, peekPos)
|
||||
return nextCh === 61 /* = */ ? AssignableIdentifier : Identifier
|
||||
if (nextCh === 61 /* = */) {
|
||||
// Found '=', but check if it's followed by whitespace
|
||||
// If '=' is followed by non-whitespace (like '=cool*'), it won't be tokenized as Eq
|
||||
// In that case, this should be Identifier (for function call), not AssignableIdentifier
|
||||
const charAfterEquals = getFullCodePoint(input, peekPos + 1)
|
||||
if (isWhiteSpace(charAfterEquals) || charAfterEquals === -1 /* EOF */) {
|
||||
return AssignableIdentifier
|
||||
}
|
||||
}
|
||||
return Identifier
|
||||
}
|
||||
|
||||
// Character classification helpers
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { expect } from 'bun:test'
|
||||
import { Tree, TreeCursor } from '@lezer/common'
|
||||
import { parser } from '#parser/shrimp'
|
||||
import { $ } from 'bun'
|
||||
import { assert, assertNever, errorMessage } from '#utils/utils'
|
||||
import { assert, errorMessage } from '#utils/utils'
|
||||
import { Compiler } from '#compiler/compiler'
|
||||
import { run, VM, type Value } from 'reefvm'
|
||||
import { run, VM } from 'reefvm'
|
||||
import { treeToString, VMResultToValue } from '#utils/tree'
|
||||
|
||||
const regenerateParser = async () => {
|
||||
let generate = true
|
||||
|
|
@ -131,10 +131,11 @@ expect.extend({
|
|||
try {
|
||||
const compiler = new Compiler(received)
|
||||
const vm = new VM(compiler.bytecode)
|
||||
await vm.run()
|
||||
const value = await vm.run()
|
||||
|
||||
return {
|
||||
message: () => `Expected evaluation to fail, but it succeeded.`,
|
||||
message: () =>
|
||||
`Expected evaluation to fail, but it succeeded with ${JSON.stringify(value)}`,
|
||||
pass: false,
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -146,38 +147,6 @@ expect.extend({
|
|||
},
|
||||
})
|
||||
|
||||
const treeToString = (tree: Tree, input: string): string => {
|
||||
const lines: string[] = []
|
||||
|
||||
const addNode = (cursor: TreeCursor, depth: number) => {
|
||||
if (!cursor.name) return
|
||||
|
||||
const indent = ' '.repeat(depth)
|
||||
const text = input.slice(cursor.from, cursor.to)
|
||||
const nodeName = cursor.name // Save the node name before moving cursor
|
||||
|
||||
if (cursor.firstChild()) {
|
||||
lines.push(`${indent}${nodeName}`)
|
||||
do {
|
||||
addNode(cursor, depth + 1)
|
||||
} while (cursor.nextSibling())
|
||||
cursor.parent()
|
||||
} else {
|
||||
const cleanText = nodeName === 'String' ? text.slice(1, -1) : text
|
||||
lines.push(`${indent}${nodeName} ${cleanText}`)
|
||||
}
|
||||
}
|
||||
|
||||
const cursor = tree.cursor()
|
||||
if (cursor.firstChild()) {
|
||||
do {
|
||||
addNode(cursor, 0)
|
||||
} while (cursor.nextSibling())
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
const trimWhitespace = (str: string): string => {
|
||||
const lines = str.split('\n').filter((line) => line.trim().length > 0)
|
||||
const firstLine = lines[0]
|
||||
|
|
@ -196,29 +165,3 @@ const trimWhitespace = (str: string): string => {
|
|||
})
|
||||
.join('\n')
|
||||
}
|
||||
|
||||
const VMResultToValue = (result: Value): unknown => {
|
||||
if (
|
||||
result.type === 'number' ||
|
||||
result.type === 'boolean' ||
|
||||
result.type === 'string' ||
|
||||
result.type === 'regex'
|
||||
) {
|
||||
return result.value
|
||||
} else if (result.type === 'null') {
|
||||
return null
|
||||
} else if (result.type === 'array') {
|
||||
return result.value.map(VMResultToValue)
|
||||
} else if (result.type === 'dict') {
|
||||
const obj: Record<string, unknown> = {}
|
||||
for (const [key, val] of Object.entries(result.value)) {
|
||||
obj[key] = VMResultToValue(val)
|
||||
}
|
||||
|
||||
return obj
|
||||
} else if (result.type === 'function') {
|
||||
return Function
|
||||
} else {
|
||||
assertNever(result)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
src/utils/tree.ts
Normal file
61
src/utils/tree.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { Tree, TreeCursor } from '@lezer/common'
|
||||
import { assertNever } from '#utils/utils'
|
||||
import { type Value } from 'reefvm'
|
||||
|
||||
export const treeToString = (tree: Tree, input: string): string => {
|
||||
const lines: string[] = []
|
||||
|
||||
const addNode = (cursor: TreeCursor, depth: number) => {
|
||||
if (!cursor.name) return
|
||||
|
||||
const indent = ' '.repeat(depth)
|
||||
const text = input.slice(cursor.from, cursor.to)
|
||||
const nodeName = cursor.name // Save the node name before moving cursor
|
||||
|
||||
if (cursor.firstChild()) {
|
||||
lines.push(`${indent}${nodeName}`)
|
||||
do {
|
||||
addNode(cursor, depth + 1)
|
||||
} while (cursor.nextSibling())
|
||||
cursor.parent()
|
||||
} else {
|
||||
const cleanText = nodeName === 'String' ? text.slice(1, -1) : text
|
||||
lines.push(`${indent}${nodeName} ${cleanText}`)
|
||||
}
|
||||
}
|
||||
|
||||
const cursor = tree.cursor()
|
||||
if (cursor.firstChild()) {
|
||||
do {
|
||||
addNode(cursor, 0)
|
||||
} while (cursor.nextSibling())
|
||||
}
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
export const VMResultToValue = (result: Value): unknown => {
|
||||
if (
|
||||
result.type === 'number' ||
|
||||
result.type === 'boolean' ||
|
||||
result.type === 'string' ||
|
||||
result.type === 'regex'
|
||||
) {
|
||||
return result.value
|
||||
} else if (result.type === 'null') {
|
||||
return null
|
||||
} else if (result.type === 'array') {
|
||||
return result.value.map(VMResultToValue)
|
||||
} else if (result.type === 'dict') {
|
||||
const obj: Record<string, unknown> = {}
|
||||
for (const [key, val] of Object.entries(result.value)) {
|
||||
obj[key] = VMResultToValue(val)
|
||||
}
|
||||
|
||||
return obj
|
||||
} else if (result.type === 'function') {
|
||||
return Function
|
||||
} else {
|
||||
assertNever(result)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user