wip
This commit is contained in:
parent
1f147d2d26
commit
3a12d7baff
|
|
@ -70,6 +70,17 @@ describe('compiler', () => {
|
||||||
expect(`add = fn a b: a + b end; add 2 9`).toEvaluateTo(11)
|
expect(`add = fn a b: a + b end; add 2 9`).toEvaluateTo(11)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('function call with named args', () => {
|
||||||
|
expect(`minus = fn a b: a - b end; minus b=2 a=9`).toEvaluateTo(7)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('function call with named and positional args', () => {
|
||||||
|
expect(`minus = fn a b: a - b end; minus b=2 9`).toEvaluateTo(7)
|
||||||
|
expect(`minus = fn c d: a - b end; minus 90 b=20`).toEvaluateTo(70)
|
||||||
|
expect(`minus = fn e f: a - b end; minus a=900 200`).toEvaluateTo(700)
|
||||||
|
expect(`minus = fn g h: a - b end; minus 2000 a=9000`).toEvaluateTo(7000)
|
||||||
|
})
|
||||||
|
|
||||||
test('function call with no args', () => {
|
test('function call with no args', () => {
|
||||||
expect(`bloop = fn: 'bloop' end; bloop`).toEvaluateTo('bloop')
|
expect(`bloop = fn: 'bloop' end; bloop`).toEvaluateTo('bloop')
|
||||||
})
|
})
|
||||||
|
|
@ -136,7 +147,7 @@ describe('errors', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe.skip('multiline tests', () => {
|
describe('multiline tests', () => {
|
||||||
test('multiline function', () => {
|
test('multiline function', () => {
|
||||||
expect(`
|
expect(`
|
||||||
add = fn a b:
|
add = fn a b:
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,12 @@ import {
|
||||||
getFunctionDefParts,
|
getFunctionDefParts,
|
||||||
getIfExprParts,
|
getIfExprParts,
|
||||||
getNamedArgParts,
|
getNamedArgParts,
|
||||||
|
getPipeExprParts,
|
||||||
} from '#compiler/utils'
|
} from '#compiler/utils'
|
||||||
|
|
||||||
|
const DEBUG = false
|
||||||
|
// const DEBUG = true
|
||||||
|
|
||||||
type Label = `.${string}`
|
type Label = `.${string}`
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
instructions: ProgramItem[] = []
|
instructions: ProgramItem[] = []
|
||||||
|
|
@ -25,14 +29,21 @@ export class Compiler {
|
||||||
constructor(public input: string) {
|
constructor(public input: string) {
|
||||||
try {
|
try {
|
||||||
const cst = parser.parse(input)
|
const cst = parser.parse(input)
|
||||||
const errors = checkTreeForErrors(cst, input)
|
const errors = checkTreeForErrors(cst)
|
||||||
|
|
||||||
if (errors.length > 0) {
|
const firstError = errors[0]
|
||||||
throw new CompilerError(`Syntax errors found:\n${errors.join('\n')}`, 0, input.length)
|
if (firstError) {
|
||||||
|
throw firstError
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#compileCst(cst, input)
|
this.#compileCst(cst, input)
|
||||||
|
|
||||||
|
throw new CompilerError(
|
||||||
|
'I am a very long fake error to test scrolling and other things this is super long\nand has multiple lines\nand just keeps going and going and going',
|
||||||
|
20,
|
||||||
|
25
|
||||||
|
)
|
||||||
|
|
||||||
// Add the labels
|
// Add the labels
|
||||||
for (const [label, labelInstructions] of this.fnLabels) {
|
for (const [label, labelInstructions] of this.fnLabels) {
|
||||||
this.instructions.push([`${label}:`])
|
this.instructions.push([`${label}:`])
|
||||||
|
|
@ -40,7 +51,7 @@ export class Compiler {
|
||||||
this.instructions.push(['RETURN'])
|
this.instructions.push(['RETURN'])
|
||||||
}
|
}
|
||||||
|
|
||||||
// logInstructions(this.instructions)
|
if (DEBUG) logInstructions(this.instructions)
|
||||||
|
|
||||||
this.bytecode = toBytecode(this.instructions)
|
this.bytecode = toBytecode(this.instructions)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -67,6 +78,9 @@ export class Compiler {
|
||||||
|
|
||||||
#compileNode(node: SyntaxNode, input: string): ProgramItem[] {
|
#compileNode(node: SyntaxNode, input: string): ProgramItem[] {
|
||||||
const value = input.slice(node.from, node.to)
|
const value = input.slice(node.from, node.to)
|
||||||
|
|
||||||
|
if (DEBUG) console.log(`🫦 ${node.name}: ${value}`)
|
||||||
|
|
||||||
switch (node.type.id) {
|
switch (node.type.id) {
|
||||||
case terms.Number:
|
case terms.Number:
|
||||||
const number = Number(value)
|
const number = Number(value)
|
||||||
|
|
@ -289,123 +303,61 @@ export class Compiler {
|
||||||
}
|
}
|
||||||
|
|
||||||
case terms.PipeExpr: {
|
case terms.PipeExpr: {
|
||||||
const allChildren = getAllChildren(node)
|
const { pipedFunctionCall, pipeReceivers } = getPipeExprParts(node)
|
||||||
// Filter out the pipe operator nodes (they're just syntax)
|
if (!pipedFunctionCall || pipeReceivers.length === 0) {
|
||||||
const operands = allChildren.filter((child) => child.type.name !== 'operator')
|
|
||||||
if (operands.length < 2) {
|
|
||||||
throw new CompilerError('PipeExpr must have at least two operands', node.from, node.to)
|
throw new CompilerError('PipeExpr must have at least two operands', node.from, node.to)
|
||||||
}
|
}
|
||||||
|
|
||||||
const instructions: ProgramItem[] = []
|
const instructions: ProgramItem[] = []
|
||||||
|
instructions.push(...this.#compileNode(pipedFunctionCall, input))
|
||||||
|
|
||||||
// Compile first operand normally
|
pipeReceivers.forEach((pipeReceiver) => {
|
||||||
instructions.push(...this.#compileNode(operands[0]!, input))
|
instructions.push(['STORE', '_pipe_value'])
|
||||||
|
|
||||||
// For each subsequent operand, transform it to receive piped value as first arg
|
const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts(
|
||||||
for (let i = 1; i < operands.length; i++) {
|
pipeReceiver,
|
||||||
const operand = operands[i]!
|
input
|
||||||
|
)
|
||||||
|
|
||||||
// Result from previous stage is on stack
|
|
||||||
// We need to make it the first argument to the next call
|
|
||||||
|
|
||||||
if (operand.type.id === terms.FunctionCallOrIdentifier) {
|
|
||||||
// Simple identifier - emit TRY_CALL with piped value as single argument
|
|
||||||
const identifierNode = operand.getChild('Identifier')
|
|
||||||
if (!identifierNode) {
|
|
||||||
throw new CompilerError('FunctionCallOrIdentifier must have Identifier', operand.from, operand.to)
|
|
||||||
}
|
|
||||||
const fnName = input.slice(identifierNode.from, identifierNode.to)
|
|
||||||
|
|
||||||
// Stack has: [piped_value]
|
|
||||||
// Store piped value temporarily
|
|
||||||
instructions.push(['STORE', '__pipe_value'])
|
|
||||||
|
|
||||||
// Load function
|
|
||||||
instructions.push(['TRY_LOAD', fnName])
|
|
||||||
|
|
||||||
// Load piped value as first arg
|
|
||||||
instructions.push(['LOAD', '__pipe_value'])
|
|
||||||
|
|
||||||
// Call with 1 positional arg and 0 named args
|
|
||||||
instructions.push(['PUSH', 1])
|
|
||||||
instructions.push(['PUSH', 0])
|
|
||||||
instructions.push(['CALL'])
|
|
||||||
|
|
||||||
} else if (operand.type.id === terms.FunctionCall) {
|
|
||||||
// Function call with arguments - check for underscore placeholder
|
|
||||||
const { identifierNode, namedArgs, positionalArgs } = getFunctionCallParts(operand, input)
|
|
||||||
|
|
||||||
// Check if any positional arg is an underscore placeholder
|
|
||||||
let underscoreIndex = -1
|
|
||||||
for (let j = 0; j < positionalArgs.length; j++) {
|
|
||||||
const arg = positionalArgs[j]!
|
|
||||||
const argValue = input.slice(arg.from, arg.to)
|
|
||||||
if (argValue === '_') {
|
|
||||||
underscoreIndex = j
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underscoreIndex !== -1) {
|
|
||||||
// Underscore found - replace it with piped value at that position
|
|
||||||
// Store piped value temporarily
|
|
||||||
instructions.push(['STORE', '__pipe_value'])
|
|
||||||
|
|
||||||
// Load function
|
|
||||||
instructions.push(...this.#compileNode(identifierNode, input))
|
instructions.push(...this.#compileNode(identifierNode, input))
|
||||||
|
|
||||||
// Push positional args, replacing underscore with piped value
|
const isUnderscoreInPositionalArgs = positionalArgs.some(
|
||||||
for (let j = 0; j < positionalArgs.length; j++) {
|
(arg) => arg.type.id === terms.Underscore
|
||||||
if (j === underscoreIndex) {
|
)
|
||||||
instructions.push(['LOAD', '__pipe_value'])
|
const isUnderscoreInNamedArgs = namedArgs.some((arg) => {
|
||||||
} else {
|
const { valueNode } = getNamedArgParts(arg, input)
|
||||||
instructions.push(...this.#compileNode(positionalArgs[j]!, input))
|
return valueNode.type.id === terms.Underscore
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push named args
|
|
||||||
namedArgs.forEach((arg) => {
|
|
||||||
const { name, valueNode } = getNamedArgParts(arg, input)
|
|
||||||
instructions.push(['PUSH', name])
|
|
||||||
instructions.push(...this.#compileNode(valueNode, input))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Call with positionalArgs.length and namedArgs
|
const shouldPushPositionalArg = !isUnderscoreInPositionalArgs && !isUnderscoreInNamedArgs
|
||||||
instructions.push(['PUSH', positionalArgs.length])
|
|
||||||
instructions.push(['PUSH', namedArgs.length])
|
|
||||||
instructions.push(['CALL'])
|
|
||||||
} else {
|
|
||||||
// No underscore - piped value becomes first argument
|
|
||||||
// Store piped value temporarily
|
|
||||||
instructions.push(['STORE', '__pipe_value'])
|
|
||||||
|
|
||||||
// Load function
|
// If no underscore is explicitly used, add the piped value as the first positional arg
|
||||||
instructions.push(...this.#compileNode(identifierNode, input))
|
if (shouldPushPositionalArg) {
|
||||||
|
instructions.push(['LOAD', '_pipe_value'])
|
||||||
|
}
|
||||||
|
|
||||||
// Push piped value as first arg
|
|
||||||
instructions.push(['LOAD', '__pipe_value'])
|
|
||||||
|
|
||||||
// Push remaining positional args
|
|
||||||
positionalArgs.forEach((arg) => {
|
positionalArgs.forEach((arg) => {
|
||||||
|
if (arg.type.id === terms.Underscore) {
|
||||||
|
instructions.push(['LOAD', '_pipe_value'])
|
||||||
|
} else {
|
||||||
instructions.push(...this.#compileNode(arg, input))
|
instructions.push(...this.#compileNode(arg, input))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Push named args
|
|
||||||
namedArgs.forEach((arg) => {
|
namedArgs.forEach((arg) => {
|
||||||
const { name, valueNode } = getNamedArgParts(arg, input)
|
const { name, valueNode } = getNamedArgParts(arg, input)
|
||||||
instructions.push(['PUSH', name])
|
instructions.push(['PUSH', name])
|
||||||
|
if (valueNode.type.id === terms.Underscore) {
|
||||||
|
instructions.push(['LOAD', '_pipe_value'])
|
||||||
|
} else {
|
||||||
instructions.push(...this.#compileNode(valueNode, input))
|
instructions.push(...this.#compileNode(valueNode, input))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Call with (positionalArgs + 1 for piped value) and namedArgs
|
instructions.push(['PUSH', positionalArgs.length + (shouldPushPositionalArg ? 1 : 0)])
|
||||||
instructions.push(['PUSH', positionalArgs.length + 1])
|
|
||||||
instructions.push(['PUSH', namedArgs.length])
|
instructions.push(['PUSH', namedArgs.length])
|
||||||
instructions.push(['CALL'])
|
instructions.push(['CALL'])
|
||||||
}
|
})
|
||||||
} else {
|
|
||||||
throw new CompilerError(`Unsupported pipe operand type: ${operand.type.name}`, operand.from, operand.to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return instructions
|
return instructions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
export class CompilerError extends Error {
|
export class CompilerError extends Error {
|
||||||
constructor(message: string, private from: number, private to: number) {
|
constructor(message: string, private from: number, private to: number) {
|
||||||
super(message)
|
super(message)
|
||||||
|
|
||||||
|
if (from < 0 || to < 0 || to < from) {
|
||||||
|
throw new Error(`Invalid CompilerError positions: from=${from}, to=${to}`)
|
||||||
|
}
|
||||||
|
|
||||||
this.name = 'CompilerError'
|
this.name = 'CompilerError'
|
||||||
this.message = message
|
this.message = message
|
||||||
}
|
}
|
||||||
|
|
@ -19,27 +24,24 @@ export class CompilerError extends Error {
|
||||||
const lines = previousSevenLines
|
const lines = previousSevenLines
|
||||||
.map((line, index) => {
|
.map((line, index) => {
|
||||||
const currentLineNumber = lineNumber - previousSevenLines.length + index + 1
|
const currentLineNumber = lineNumber - previousSevenLines.length + index + 1
|
||||||
|
// repace leading whitespace with barely visible characters so they show up in terminal
|
||||||
|
line = line.replace(/^\s+/, (ws) => ws.replace(/ /g, green('·')).replace(/\t/g, '→ '))
|
||||||
return `${grey(currentLineNumber.toString().padStart(padding))} │ ${line}`
|
return `${grey(currentLineNumber.toString().padStart(padding))} │ ${line}`
|
||||||
})
|
})
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
const underlineStartLen = (columnEnd - columnStart) / 2
|
const underlineLen = columnEnd - columnStart + 1
|
||||||
const underlineEndLen = columnEnd - columnStart - underlineStartLen
|
const underline = ' '.repeat(columnStart - 1) + red('═'.repeat(underlineLen))
|
||||||
const underline =
|
|
||||||
' '.repeat(columnStart - 1) +
|
|
||||||
'─'.repeat(underlineStartLen) +
|
|
||||||
'┬' +
|
|
||||||
'─'.repeat(underlineEndLen)
|
|
||||||
|
|
||||||
const messageWithArrow =
|
const messageWithArrow = blue(this.message)
|
||||||
' '.repeat(columnStart + underlineStartLen - 1) + '╰── ' + blue(this.message)
|
|
||||||
|
|
||||||
const message = `${green('')}
|
const message = `${green('')}
|
||||||
${ws}╭───┨ ${red('Compiler Error')} ┃
|
${ws}╭───┨ ${red('Compiler Error')} ┃
|
||||||
${ws}│
|
${ws}│
|
||||||
${lines}
|
${lines}
|
||||||
${ws}│ ${underline}
|
${ws}│ ${underline}
|
||||||
${ws}│ ${messageWithArrow}
|
${ws}│ ${messageWithArrow.split('\n').join(`\n${ws}│ `)}
|
||||||
|
${ws}│
|
||||||
${ws}╰───
|
${ws}╰───
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -54,7 +56,7 @@ ${ws}╰───
|
||||||
const line = lines[i]!
|
const line = lines[i]!
|
||||||
if (this.from >= currentPos && this.from <= currentPos + line.length) {
|
if (this.from >= currentPos && this.from <= currentPos + line.length) {
|
||||||
const columnStart = this.from - currentPos + 1
|
const columnStart = this.from - currentPos + 1
|
||||||
const columnEnd = columnStart + (this.to - this.from) - 1
|
const columnEnd = columnStart + (this.to - this.from)
|
||||||
|
|
||||||
// If the error spans multiple lines, so just return the line start
|
// If the error spans multiple lines, so just return the line start
|
||||||
if (columnEnd > line.length) {
|
if (columnEnd > line.length) {
|
||||||
|
|
|
||||||
|
|
@ -2,60 +2,80 @@ import { describe, test, expect } from 'bun:test'
|
||||||
|
|
||||||
describe('pipe expressions', () => {
|
describe('pipe expressions', () => {
|
||||||
test('simple pipe passes result as first argument', () => {
|
test('simple pipe passes result as first argument', () => {
|
||||||
const code = `double = fn x: x * 2 end; result = 5 | double; result`
|
const code = `
|
||||||
|
double = fn x: x * 2 end
|
||||||
|
double 2 | double`
|
||||||
|
|
||||||
expect(code).toEvaluateTo(10)
|
expect(code).toEvaluateTo(8)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('pipe chain with three stages', () => {
|
test('pipe chain with three stages', () => {
|
||||||
const code = `add-one = fn x: x + 1 end; double = fn x: x * 2 end; square = fn x: x * x end; result = 3 | add-one | double | square; result`
|
const code = `
|
||||||
|
add-one = fn x: x + 1 end
|
||||||
// 3 -> 4 -> 8 -> 64
|
double = fn x: x * 2 end
|
||||||
expect(code).toEvaluateTo(64)
|
minus-point-one = fn x: x - 0.1 end
|
||||||
|
add-one 3 | double | minus-point-one`
|
||||||
|
// 4 8 7.9
|
||||||
|
expect(code).toEvaluateTo(7.9)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('pipe with function that has additional arguments', () => {
|
test('pipe with function that has additional arguments', () => {
|
||||||
// TODO: This test reveals a bug where functions with 2+ parameters
|
const code = `
|
||||||
// don't properly bind all arguments. This is a general Shrimp issue,
|
multiply = fn a b: a * b end
|
||||||
// not specific to pipes. Skipping until the broader issue is fixed.
|
get-five = fn: 5 end
|
||||||
const code = `multiply = fn a b: a * b end; result = 5 | multiply 3; result`
|
get-five | multiply 3`
|
||||||
|
|
||||||
// 5 becomes first arg, 3 is second arg: 5 * 3 = 15
|
|
||||||
expect(code).toEvaluateTo(15)
|
expect(code).toEvaluateTo(15)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('pipe with bare identifier', () => {
|
test('pipe with bare identifier', () => {
|
||||||
const code = `get-value = 42; process = fn x: x + 10 end; result = get-value | process; result`
|
const code = `
|
||||||
|
get-value = 42
|
||||||
|
process = fn x: x + 10 end
|
||||||
|
get-value | process`
|
||||||
|
|
||||||
expect(code).toEvaluateTo(52)
|
expect(code).toEvaluateTo(52)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('pipe in assignment', () => {
|
test('pipe in assignment', () => {
|
||||||
const code = `add-ten = fn x: x + 10 end; result = 5 | add-ten; result`
|
const code = `
|
||||||
|
add-ten = fn x: x + 10 end
|
||||||
|
result = add-ten 5 | add-ten
|
||||||
|
result`
|
||||||
|
|
||||||
expect(code).toEvaluateTo(15)
|
// 5 + 10 = 15, then 15 + 10 = 25
|
||||||
|
expect(code).toEvaluateTo(25)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('pipe with underscore placeholder', () => {
|
test('pipe with named underscore arg', () => {
|
||||||
// TODO: This test depends on the fix for two-parameter functions
|
expect(`
|
||||||
// which is tracked by the skipped test above. The underscore placeholder
|
divide = fn a b: a / b end
|
||||||
// logic is implemented, but we can't verify it works until the broader
|
get-ten = fn: 10 end
|
||||||
// multi-parameter function bug is fixed.
|
get-ten | divide 2 b=_`).toEvaluateTo(0.2)
|
||||||
const code = `divide = fn a b: a / b end; result = 10 | divide 2 _; result`
|
|
||||||
|
|
||||||
// Underscore is replaced with piped value (10)
|
expect(`
|
||||||
// Should call: divide(2, 10) = 2 / 10 = 0.2
|
divide = fn a b: a / b end
|
||||||
expect(code).toEvaluateTo(0.2)
|
get-ten = fn: 10 end
|
||||||
|
get-ten | divide b=_ 2`).toEvaluateTo(0.2)
|
||||||
|
|
||||||
|
expect(`
|
||||||
|
divide = fn a b: a / b end
|
||||||
|
get-ten = fn: 10 end
|
||||||
|
get-ten | divide 2 a=_`).toEvaluateTo(5)
|
||||||
|
|
||||||
|
expect(`
|
||||||
|
divide = fn a b: a / b end
|
||||||
|
get-ten = fn: 10 end
|
||||||
|
get-ten | divide a=_ 2`).toEvaluateTo(5)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('pipe with underscore placeholder (single param verification)', () => {
|
test('nested pipes', () => {
|
||||||
// Since multi-param functions don't work yet, let's verify the underscore
|
// This is complicated, but the idea is to make sure the underscore
|
||||||
// detection and replacement logic works by testing that underscore is NOT
|
// handling logic works correctly when there are multiple pipe stages
|
||||||
// treated as a literal word/string
|
// in a single expression.
|
||||||
const code = `identity = fn x: x end; result = 42 | identity _; result`
|
expect(`
|
||||||
|
sub = fn a b: a - b end
|
||||||
// If underscore is properly detected and replaced, we get 42
|
div = fn a b: a / b end
|
||||||
// If it's treated as a Word, we'd get the string "_"
|
sub 3 1 | div (sub 110 9 | sub 1) _ | div 5`).toEvaluateTo(10)
|
||||||
expect(code).toEvaluateTo(42)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { CompilerError } from '#compiler/compilerError.ts'
|
||||||
import * as terms from '#parser/shrimp.terms'
|
import * as terms from '#parser/shrimp.terms'
|
||||||
import type { SyntaxNode, Tree } from '@lezer/common'
|
import type { SyntaxNode, Tree } from '@lezer/common'
|
||||||
|
|
||||||
export const checkTreeForErrors = (tree: Tree, input: string): string[] => {
|
export const checkTreeForErrors = (tree: Tree): CompilerError[] => {
|
||||||
const errors: string[] = []
|
const errors: CompilerError[] = []
|
||||||
tree.iterate({
|
tree.iterate({
|
||||||
enter: (node) => {
|
enter: (node) => {
|
||||||
if (node.type.isError) {
|
if (node.type.isError) {
|
||||||
const errorText = input.slice(node.from, node.to)
|
errors.push(new CompilerError(`Unexpected syntax.`, node.from, node.to))
|
||||||
errors.push(`Syntax error at ${node.from}-${node.to}: "${errorText}"`)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -70,8 +69,7 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const paramNames = getAllChildren(paramsNode)
|
const paramNames = getAllChildren(paramsNode).map((param) => {
|
||||||
.map((param) => {
|
|
||||||
if (param.type.id !== terms.Identifier) {
|
if (param.type.id !== terms.Identifier) {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`FunctionDef params must be Identifiers, got ${param.type.name}`,
|
`FunctionDef params must be Identifiers, got ${param.type.name}`,
|
||||||
|
|
@ -81,7 +79,6 @@ export const getFunctionDefParts = (node: SyntaxNode, input: string) => {
|
||||||
}
|
}
|
||||||
return input.slice(param.from, param.to)
|
return input.slice(param.from, param.to)
|
||||||
})
|
})
|
||||||
.join(' ')
|
|
||||||
|
|
||||||
return { paramNames, bodyNode }
|
return { paramNames, bodyNode }
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +112,7 @@ export const getNamedArgParts = (node: SyntaxNode, input: string) => {
|
||||||
throw new CompilerError(message, node.from, node.to)
|
throw new CompilerError(message, node.from, node.to)
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = input.slice(namedArgPrefix.from, namedArgPrefix.to - 2) // Remove the trailing =
|
const name = input.slice(namedArgPrefix.from, namedArgPrefix.to - 1) // Remove the trailing =
|
||||||
return { name, valueNode }
|
return { name, valueNode }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,3 +153,15 @@ export const getIfExprParts = (node: SyntaxNode, input: string) => {
|
||||||
|
|
||||||
return { conditionNode, thenBlock, elseThenBlock, elseIfBlocks }
|
return { conditionNode, thenBlock, elseThenBlock, elseIfBlocks }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getPipeExprParts = (node: SyntaxNode) => {
|
||||||
|
const [pipedFunctionCall, operator, ...rest] = getAllChildren(node)
|
||||||
|
if (!pipedFunctionCall || !operator || rest.length === 0) {
|
||||||
|
const message = `PipeExpr expected at least 3 children, got ${getAllChildren(node).length}`
|
||||||
|
throw new CompilerError(message, node.from, node.to)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pipeReceivers = rest.filter((child) => child.name !== 'operator')
|
||||||
|
|
||||||
|
return { pipedFunctionCall, pipeReceivers }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
rightParen { ")" }
|
rightParen { ")" }
|
||||||
colon[closedBy="end", @name="colon"] { ":" }
|
colon[closedBy="end", @name="colon"] { ":" }
|
||||||
end[openedBy="colon", @name="end"] { "end" }
|
end[openedBy="colon", @name="end"] { "end" }
|
||||||
|
Underscore { "_" }
|
||||||
"fn" [@name=keyword]
|
"fn" [@name=keyword]
|
||||||
"if" [@name=keyword]
|
"if" [@name=keyword]
|
||||||
"elsif" [@name=keyword]
|
"elsif" [@name=keyword]
|
||||||
|
|
@ -68,7 +69,7 @@ PipeExpr {
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeOperand {
|
pipeOperand {
|
||||||
FunctionCall | FunctionCallOrIdentifier | expressionWithoutIdentifier
|
FunctionCall | FunctionCallOrIdentifier
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCallOrIdentifier {
|
FunctionCallOrIdentifier {
|
||||||
|
|
@ -87,12 +88,13 @@ arg {
|
||||||
PositionalArg | NamedArg
|
PositionalArg | NamedArg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PositionalArg {
|
PositionalArg {
|
||||||
expression | FunctionDef
|
expression | FunctionDef | Underscore
|
||||||
}
|
}
|
||||||
|
|
||||||
NamedArg {
|
NamedArg {
|
||||||
NamedArgPrefix (expression | FunctionDef)
|
NamedArgPrefix (expression | FunctionDef | Underscore)
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionDef {
|
FunctionDef {
|
||||||
|
|
@ -158,7 +160,7 @@ BinOp {
|
||||||
}
|
}
|
||||||
|
|
||||||
ParenExpr {
|
ParenExpr {
|
||||||
leftParen (ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp ) rightParen
|
leftParen (ambiguousFunctionCall | BinOp | expressionWithoutIdentifier | ConditionalOp | PipeExpr) rightParen
|
||||||
}
|
}
|
||||||
|
|
||||||
expression {
|
expression {
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,11 @@ export const
|
||||||
Params = 28,
|
Params = 28,
|
||||||
colon = 29,
|
colon = 29,
|
||||||
end = 30,
|
end = 30,
|
||||||
NamedArg = 31,
|
Underscore = 31,
|
||||||
NamedArgPrefix = 32,
|
NamedArg = 32,
|
||||||
IfExpr = 34,
|
NamedArgPrefix = 33,
|
||||||
ThenBlock = 37,
|
IfExpr = 35,
|
||||||
ElsifExpr = 38,
|
ThenBlock = 38,
|
||||||
ElseExpr = 40,
|
ElsifExpr = 39,
|
||||||
Assign = 42
|
ElseExpr = 41,
|
||||||
|
Assign = 43
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import {tokenizer} from "./tokenizer"
|
||||||
import {highlighting} from "./highlight"
|
import {highlighting} from "./highlight"
|
||||||
export const parser = LRParser.deserialize({
|
export const parser = LRParser.deserialize({
|
||||||
version: 14,
|
version: 14,
|
||||||
states: ",xQVQTOOO!lQUO'#CdO#PQPO'#DiO#_QPO'#CeO#mQPO'#DcO$gQTO'#CcOOQS'#Dg'#DgO$nQPO'#DfO%YQTO'#DkOOQS'#Cv'#CvO%bQPO'#C`O%gQTO'#DoOOQO'#DO'#DOOOQO'#Dc'#DcO%nQPO'#DbOOQS'#Db'#DbOOQS'#DX'#DXQVQTOOOOQS'#Df'#DfOOQS'#Cb'#CbO%vQTO'#C{OOQS'#De'#DeOOQS'#DY'#DYO&QQUO,58{O&nQTO,59rO%gQTO,59PO%gQTO,59PO&{QUO'#CdOOQO'#Di'#DiO(WQPO'#CeO(hQPO,58}O(tQPO,58}O(yQPO,58}OOQS'#DZ'#DZO)tQTO'#CxO)|QPO,5:VO*RQTO'#D]O*YQPO,58zO*hQPO,5:ZO*oQPO,5:ZOOQS,59|,59|OOQS-E7V-E7VOOQS,59g,59gOOQS-E7W-E7WOOQO1G/^1G/^OOQO1G.k1G.kO*tQPO1G.kO%gQTO,59UO%gQTO,59UOOQS1G.i1G.iOOQS-E7X-E7XO+`QTO1G/qO+pQUO'#CdOOQO'#Dd'#DdOOQO,59w,59wOOQO-E7Z-E7ZO,ZQTO1G/uOOQO1G.p1G.pO,kQPO1G.pO,uQPO7+%]O,zQTO7+%^OOQO'#DQ'#DQOOQO7+%a7+%aO-[QTO7+%bOOQS<<Hw<<HwO-rQPO'#D[O-wQTO'#DnO._QPO<<HxOOQO'#DR'#DRO.dQPO<<H|OOQS,59v,59vOOQS-E7Y-E7YOOQSAN>dAN>dO%gQTO'#DSOOQO'#D^'#D^O.oQPOAN>hO.zQPO'#DUOOQOAN>hAN>hO/PQPOAN>hO/UQPO,59nO/]QPO,59nOOQO-E7[-E7[OOQOG24SG24SO/bQPOG24SO/gQPO,59pO/lQPO1G/YOOQOLD)nLD)nO,zQTO1G/[O-[QTO7+$tOOQO7+$v7+$vOOQO<<H`<<H`",
|
states: ",rQVQTOOO!rQUO'#CdO#SQPO'#CeO#bQPO'#DdO$[QTO'#CcOOQS'#Dh'#DhO$cQPO'#DgO$zQTO'#DkOOQS'#Cv'#CvOOQO'#De'#DeO%SQPO'#DdO%bQTO'#DoOOQO'#DP'#DPOOQO'#Dd'#DdO%iQPO'#DcOOQS'#Dc'#DcOOQS'#DY'#DYQVQTOOOOQS'#Dg'#DgOOQS'#Cb'#CbO%qQTO'#C|OOQS'#Df'#DfOOQS'#DZ'#DZO&OQUO,58{O&oQTO,59sO%bQTO,59PO%bQTO,59PO&|QUO'#CdO(XQPO'#CeO(iQPO,58}O(zQPO,58}O(uQPO,58}O)uQPO,58}OOQS'#D['#D[O)}QTO'#CxO*VQPO,5:VO*[QTO'#D^O*aQPO,58zO*rQPO,5:ZO*yQPO,5:ZOOQS,59},59}OOQS-E7W-E7WOOQS,59h,59hOOQS-E7X-E7XOOQO1G/_1G/_OOQO1G.k1G.kO+OQPO1G.kO%bQTO,59UO%bQTO,59UOOQS1G.i1G.iOOQS-E7Y-E7YO+jQTO1G/qO+zQUO'#CdOOQO,59x,59xOOQO-E7[-E7[O,kQTO1G/uOOQO1G.p1G.pO,{QPO1G.pO-VQPO7+%]O-[QTO7+%^OOQO'#DR'#DROOQO7+%a7+%aO-lQTO7+%bOOQS<<Hw<<HwO.SQPO'#D]O.XQTO'#DnO.oQPO<<HxOOQO'#DS'#DSO.tQPO<<H|OOQS,59w,59wOOQS-E7Z-E7ZOOQSAN>dAN>dO%bQTO'#DTOOQO'#D_'#D_O/PQPOAN>hO/[QPO'#DVOOQOAN>hAN>hO/aQPOAN>hO/fQPO,59oO/mQPO,59oOOQO-E7]-E7]OOQOG24SG24SO/rQPOG24SO/wQPO,59qO/|QPO1G/ZOOQOLD)nLD)nO-[QTO1G/]O-lQTO7+$uOOQO7+$w7+$wOOQO<<Ha<<Ha",
|
||||||
stateData: "/t~O!TOS~OPPOQUOgUOhUOiUOkWOsZO![TO!a_O~OPbOQUOgUOhUOiUOkWOpdO![TOY!YXZ!YX[!YX]!YX~O_hOqWX!aWX!eWXnWX~PtOq!WX!a!]X!e!]Xn!]X~OYiOZiO[jO]jO~OYiOZiO[jO]jO!a!VX!e!VXn!VX~OQUOgUOhUOiUO![TO~OPkO~P$UOY!YXZ!YX[!YX]!YXq!WX!a!VX!e!VXn!VX~OPqOmlP~OqtO~OPbO~P$UO!axO!exO~OPbOkWO~P$UOPbOkWOpdOqTa!aTa!eTa!^TanTa~P$UOPPOkWOsZO~P$UO_!YX`!YXa!YXb!YXc!YXd!YXe!YXf!YX!^WX~PtO_!PO`!POa!POb!POc!POd!POe!QOf!QO~OYiOZiO[jO]jO~P'lOYiOZiO[jO]jO!^!RO~O!^!ROY!YXZ!YX[!YX]!YX_!YX`!YXa!YXb!YXc!YXd!YXe!YXf!YX~OPqOmlX~Om!TO~OP!UO~P$UOqtO!aSa!eSanSa~Om!YO~P'lOm!YO~OYiOZiO[Xi]Xi!aXi!eXi!^XinXi~OPPOkWOsZO!a!^O~P$UOPbOkWOpdOqWX!aWX!eWXnWX~P$UOPPOkWOsZO!a!aO~P$UO!^^im^i~P'lOn!bO~OPPOkWOsZOn!bP~P$UOPPOkWOsZOn!bPw!bPy!bP~P$UO!a!hO~OPPOkWOsZOn!bXw!bXy!bX~P$UOn!jO~On!oOw!kOy!nO~On!tOw!kOy!nO~Om!vO~On!tO~Om!wO~P'lOm!wO~On!xO~O!a!yO~O!a!zO~Oh]~",
|
stateData: "0U~O!UOS~OPPOQTOgTOhTOiTOkVOtZO!]SO!a_O~OPbOQTOgTOhTOiTOkVOocOqdO!]SOY!ZXZ!ZX[!ZX]!ZXrWX~O_hO!aWX!eWXnWX~PtOYiOZiO[jO]jO~OYiOZiO[jO]jO!a!WX!e!WXn!WX~OQTOgTOhTOiTO!]SO~OPkO~P#yOY!ZXZ!ZX[!ZX]!ZX!a!WX!e!WXn!WX~OPqOmlP~OrtO!a!WX!e!WXn!WX~OPbO~P#yO!axO!exO~OPbOkVOozO~P#yOPbOkVOocOqdOrTa!aTa!eTa!^TanTa~P#yOPPOkVOtZO~P#yO_!ZX`!ZXa!ZXb!ZXc!ZXd!ZXe!ZXf!ZX!^WX~PtO_!PO`!POa!POb!POc!POd!POe!QOf!QO~OYiOZiO[jO]jO~P'mOYiOZiO[jO]jO!^!RO~O!^!ROY!ZXZ!ZX[!ZX]!ZX_!ZX`!ZXa!ZXb!ZXc!ZXd!ZXe!ZXf!ZX~OrtO!^!RO~OPqOmlX~Om!TO~OP!UO~OrtO!aSa!eSa!^SanSa~Om!XO~P'mOm!XO~OYiOZiO[Xi]Xi!aXi!eXi!^XinXi~OPPOkVOtZO!a!]O~P#yOPbOkVOocOqdOrWX!aWX!eWX!^WXnWX~P#yOPPOkVOtZO!a!`O~P#yO!^^im^i~P'mOn!aO~OPPOkVOtZOn!bP~P#yOPPOkVOtZOn!bPx!bPz!bP~P#yO!a!gO~OPPOkVOtZOn!bXx!bXz!bX~P#yOn!iO~On!nOx!jOz!mO~On!sOx!jOz!mO~Om!uO~On!sO~Om!vO~P'mOm!vO~On!wO~O!a!xO~O!a!yO~Oh]~",
|
||||||
goto: "*T!ePPPP!f!r#U#[!r#uPPPP$[PPPPPPPPPPP$hP$}PP#UPP!fP%Q%T%^P%bP!f%h%n%v%|&V&]PPP&c&g&{'['b(^P(}P)^)^P)o)w)we]Oah!T!Y!^!a!d!y!zdQOah!T!Y!^!a!d!y!zQlTR!VtXePgk!U!PUOPTZadghijkt!P!Q!T!U!Y!^!a!d!k!y!zdSOah!T!Y!^!a!d!y!zQnTQ}iR!OjQoTQwZQ!Z!QR!r!kd]Oah!T!Y!^!a!d!y!zWcPgk!URzdRsWR!`!YQ!g!aQ!{!yR!|!zT!l!g!mQ!p!gR!u!mQaORyaUgPk!UR{gQrWR!SrW!d!^!a!y!zR!i!dQuYR!XuQ!m!gR!s!mT`OaS^OaQ|hQ!]!TQ!_!YZ!c!^!a!d!y!zdYOah!T!Y!^!a!d!y!zR!WtXfPgk!UdROah!T!Y!^!a!d!y!zWcPgk!UQmTQvZQzdQ}iQ!OjQ!Z!PQ![!QR!q!kdVOah!T!Y!^!a!d!y!zfbPZdgijk!P!Q!U!kQpTR!Vtd]Oah!T!Y!^!a!d!y!zRoToXOPadghk!T!U!Y!^!a!d!y!zQ!e!^V!f!a!y!ze[Oah!T!Y!^!a!d!y!z",
|
goto: "*P!ePPPP!f!u#T#Z!u#sPPPP$YPPPPPPPPPPP$fP${PPP#TPP%OP%[%_%hP%lP%O%r%x&Q&W&a&hPPP&n&r'W'j'p(lPP)Y)YP)k)s)sd]Oah!T!X!]!`!c!x!yRoSiXOSaht!T!X!]!`!c!x!yXePgk!U}TOPSZadghijk!P!Q!T!U!X!]!`!c!j!x!ydROah!T!X!]!`!c!x!yQmSQ}iR!OjQoSQwZQ!Y!QR!q!jd]Oah!T!X!]!`!c!x!yWcPgk!URzdRsVe]Oah!T!X!]!`!c!x!yR!_!XQ!f!`Q!z!xR!{!yT!k!f!lQ!o!fR!t!lQaORyaUgPk!UR{gQrVR!SrW!c!]!`!x!yR!h!cSuYpR!WuQ!l!fR!r!lT`OaS^OaQ|hQ![!TQ!^!XZ!b!]!`!c!x!ydYOah!T!X!]!`!c!x!yQpSR!VtXfPgk!UdQOah!T!X!]!`!c!x!yWcPgk!UQlSQvZQzdQ}iQ!OjQ!Y!PQ!Z!QR!p!jdUOah!T!X!]!`!c!x!yfbPZdgijk!P!Q!U!jRnSoWOPadghk!T!U!X!]!`!c!x!yQ!d!]V!e!`!x!ye[Oah!T!X!]!`!c!x!y",
|
||||||
nodeNames: "⚠ Identifier Word Program PipeExpr FunctionCall PositionalArg ParenExpr FunctionCallOrIdentifier BinOp operator operator operator operator ConditionalOp operator operator operator operator operator operator operator operator String Number Boolean FunctionDef keyword Params colon end NamedArg NamedArgPrefix operator IfExpr keyword ThenBlock ThenBlock ElsifExpr keyword ElseExpr keyword Assign",
|
nodeNames: "⚠ Identifier Word Program PipeExpr FunctionCall PositionalArg ParenExpr FunctionCallOrIdentifier BinOp operator operator operator operator ConditionalOp operator operator operator operator operator operator operator operator String Number Boolean FunctionDef keyword Params colon end Underscore NamedArg NamedArgPrefix operator IfExpr keyword ThenBlock ThenBlock ElsifExpr keyword ElseExpr keyword Assign",
|
||||||
maxTerm: 67,
|
maxTerm: 67,
|
||||||
nodeProps: [
|
nodeProps: [
|
||||||
["closedBy", 29,"end"],
|
["closedBy", 29,"end"],
|
||||||
|
|
@ -16,8 +16,8 @@ export const parser = LRParser.deserialize({
|
||||||
propSources: [highlighting],
|
propSources: [highlighting],
|
||||||
skippedNodes: [0],
|
skippedNodes: [0],
|
||||||
repeatNodeCount: 6,
|
repeatNodeCount: 6,
|
||||||
tokenData: "-u~RnXY#PYZ#Upq#Pqr#Zwx#fxy$Tyz$Yz{$_{|$d}!O$i!P!Q%[!Q![$q![!]%a!]!^#U!^!_%f!_!`%s!`!a%x#T#U&V#U#X&k#X#Y'`#Y#Z)|#Z#]&k#]#^+u#^#c&k#c#d,a#d#h&k#h#i,{#i#o&k#p#q-k~~-p~#UO!T~~#ZO!a~~#^P!_!`#a~#fO`~~#iTOw#fwx#xx;'S#f;'S;=`#}<%lO#f~#}Og~~$QP;=`<%l#f~$YO![~~$_O!^~~$dOY~~$iO[~~$nP]~!Q![$q~$vQh~!O!P$|!Q![$q~%PP!Q![%S~%XPh~!Q![%S~%aOZ~~%fOm~~%kPa~!_!`%n~%sOb~~%xO_~~%}Pc~!_!`&Q~&VOd~~&YS!_!`&f#T#b&k#b#c&t#c#o&kQ&kOpQQ&nQ!_!`&f#T#o&k~&wS!_!`&f#T#W&k#W#X'T#X#o&k~'YQe~!_!`&f#T#o&k~'cU!_!`&f#T#`&k#`#a'u#a#b&k#b#c)b#c#o&kR'xS!_!`&f#T#g&k#g#h(U#h#o&kR(XU!_!`&f#T#X&k#X#Y(k#Y#]&k#]#^(v#^#o&kR(pQyP!_!`&f#T#o&kR(yS!_!`&f#T#Y&k#Y#Z)V#Z#o&kR)[QwP!_!`&f#T#o&k~)eS!_!`&f#T#W&k#W#X)q#X#o&k~)vQn~!_!`&f#T#o&k~*PT!_!`&f#T#U*`#U#b&k#b#c+j#c#o&k~*cS!_!`&f#T#`&k#`#a*o#a#o&k~*rS!_!`&f#T#g&k#g#h+O#h#o&k~+RS!_!`&f#T#X&k#X#Y+_#Y#o&k~+dQi~!_!`&f#T#o&k~+oQk~!_!`&f#T#o&kR+xS!_!`&f#T#Y&k#Y#Z,U#Z#o&kR,ZQsP!_!`&f#T#o&k~,dS!_!`&f#T#f&k#f#g,p#g#o&k~,uQf~!_!`&f#T#o&k~-OS!_!`&f#T#f&k#f#g-[#g#o&k~-_S!_!`&f#T#i&k#i#j+O#j#o&k~-pOq~~-uO!e~",
|
tokenData: "-}~RoXY#SYZ#Xpq#Sqr#^wx#ixy$Wyz$]z{$b{|$g}!O$l!P!Q%_!Q![$t![!]%d!]!^#X!^!_%i!_!`%v!`!a%{#R#S&Y#T#U&_#U#X&s#X#Y'h#Y#Z*U#Z#]&s#]#^+}#^#c&s#c#d,i#d#h&s#h#i-T#i#o&s#p#q-s~~-x~#XO!U~~#^O!a~~#aP!_!`#d~#iO`~~#lTOw#iwx#{x;'S#i;'S;=`$Q<%lO#i~$QOg~~$TP;=`<%l#i~$]O!]~~$bO!^~~$gOY~~$lO[~~$qP]~!Q![$t~$yQh~!O!P%P!Q![$t~%SP!Q![%V~%[Ph~!Q![%V~%dOZ~~%iOm~~%nPa~!_!`%q~%vOb~~%{O_~~&QPc~!_!`&T~&YOd~~&_Oo~~&bS!_!`&n#T#b&s#b#c&|#c#o&sQ&sOqQQ&vQ!_!`&n#T#o&s~'PS!_!`&n#T#W&s#W#X']#X#o&s~'bQe~!_!`&n#T#o&s~'kU!_!`&n#T#`&s#`#a'}#a#b&s#b#c)j#c#o&sR(QS!_!`&n#T#g&s#g#h(^#h#o&sR(aU!_!`&n#T#X&s#X#Y(s#Y#]&s#]#^)O#^#o&sR(xQzP!_!`&n#T#o&sR)RS!_!`&n#T#Y&s#Y#Z)_#Z#o&sR)dQxP!_!`&n#T#o&s~)mS!_!`&n#T#W&s#W#X)y#X#o&s~*OQn~!_!`&n#T#o&s~*XT!_!`&n#T#U*h#U#b&s#b#c+r#c#o&s~*kS!_!`&n#T#`&s#`#a*w#a#o&s~*zS!_!`&n#T#g&s#g#h+W#h#o&s~+ZS!_!`&n#T#X&s#X#Y+g#Y#o&s~+lQi~!_!`&n#T#o&s~+wQk~!_!`&n#T#o&sR,QS!_!`&n#T#Y&s#Y#Z,^#Z#o&sR,cQtP!_!`&n#T#o&s~,lS!_!`&n#T#f&s#f#g,x#g#o&s~,}Qf~!_!`&n#T#o&s~-WS!_!`&n#T#f&s#f#g-d#g#o&s~-gS!_!`&n#T#i&s#i#j+W#j#o&s~-xOr~~-}O!e~",
|
||||||
tokenizers: [0, 1, tokenizer],
|
tokenizers: [0, 1, tokenizer],
|
||||||
topRules: {"Program":[0,3]},
|
topRules: {"Program":[0,3]},
|
||||||
tokenPrec: 677
|
tokenPrec: 693
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,6 @@ export const tokenizer = new ExternalTokenizer((input: InputStream, stack: Stack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pipe character always terminates a word/identifier
|
|
||||||
if (ch === 124 /* | */) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track identifier validity
|
// Track identifier validity
|
||||||
if (!isLowercaseLetter(ch) && !isDigit(ch) && ch !== 45 && !isEmoji(ch)) {
|
if (!isLowercaseLetter(ch) && !isDigit(ch) && ch !== 45 && !isEmoji(ch)) {
|
||||||
if (!canBeWord) break
|
if (!canBeWord) break
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user