restore errors, fancy printing
This commit is contained in:
parent
757a50e23e
commit
21e7ed41af
|
|
@ -66,12 +66,12 @@ export class Compiler {
|
||||||
if (globals) setGlobals(Array.isArray(globals) ? globals : Object.keys(globals))
|
if (globals) setGlobals(Array.isArray(globals) ? globals : Object.keys(globals))
|
||||||
const ast = parse(input)
|
const ast = parse(input)
|
||||||
const cst = new Tree(ast)
|
const cst = new Tree(ast)
|
||||||
// const errors = checkTreeForErrors(cst)
|
const errors = checkTreeForErrors(cst)
|
||||||
|
|
||||||
// const firstError = errors[0]
|
const firstError = errors[0]
|
||||||
// if (firstError) {
|
if (firstError) {
|
||||||
// throw firstError
|
throw firstError
|
||||||
// }
|
}
|
||||||
|
|
||||||
this.#compileCst(cst, input)
|
this.#compileCst(cst, input)
|
||||||
this.bytecode = toBytecode(this.instructions)
|
this.bytecode = toBytecode(this.instructions)
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ import type { SyntaxNode, Tree } from '#parser/node'
|
||||||
export const checkTreeForErrors = (tree: Tree): CompilerError[] => {
|
export const checkTreeForErrors = (tree: Tree): CompilerError[] => {
|
||||||
const errors: CompilerError[] = []
|
const errors: CompilerError[] = []
|
||||||
|
|
||||||
// tree.iterate({
|
tree.iterate({
|
||||||
// enter: (node) => {
|
enter: (node) => {
|
||||||
// if (node.type.isError) {
|
if (node.type.isError) {
|
||||||
// errors.push(new CompilerError(`Unexpected syntax.`, node.from, node.to))
|
errors.push(new CompilerError(`Unexpected syntax.`, node.from, node.to))
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
// })
|
})
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
@ -58,8 +58,7 @@ export const getAssignmentParts = (node: SyntaxNode) => {
|
||||||
|
|
||||||
if (!left || left.type.id !== terms.AssignableIdentifier) {
|
if (!left || left.type.id !== terms.AssignableIdentifier) {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`Assign left child must be an AssignableIdentifier or Array, got ${
|
`Assign left child must be an AssignableIdentifier or Array, got ${left ? left.type.name : 'none'
|
||||||
left ? left.type.name : 'none'
|
|
||||||
}`,
|
}`,
|
||||||
node.from,
|
node.from,
|
||||||
node.to
|
node.to
|
||||||
|
|
@ -75,8 +74,7 @@ export const getCompoundAssignmentParts = (node: SyntaxNode) => {
|
||||||
|
|
||||||
if (!left || left.type.id !== terms.AssignableIdentifier) {
|
if (!left || left.type.id !== terms.AssignableIdentifier) {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`CompoundAssign left child must be an AssignableIdentifier, got ${
|
`CompoundAssign left child must be an AssignableIdentifier, got ${left ? left.type.name : 'none'
|
||||||
left ? left.type.name : 'none'
|
|
||||||
}`,
|
}`,
|
||||||
node.from,
|
node.from,
|
||||||
node.to
|
node.to
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,15 @@ export class Tree {
|
||||||
node: this.topNode,
|
node: this.topNode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iterate(options: { enter: (node: SyntaxNode) => void }) {
|
||||||
|
const iter = (node: SyntaxNode) => {
|
||||||
|
for (const n of node.children) iter(n)
|
||||||
|
options.enter(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
iter(this.topNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: TEMPORARY SHIM
|
// TODO: TEMPORARY SHIM
|
||||||
|
|
@ -295,7 +304,6 @@ class SyntaxNodeType {
|
||||||
|
|
||||||
case 'keyword':
|
case 'keyword':
|
||||||
return term.keyword
|
return term.keyword
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
@ -307,6 +315,7 @@ class SyntaxNodeType {
|
||||||
|
|
||||||
export class SyntaxNode {
|
export class SyntaxNode {
|
||||||
#type: NodeType
|
#type: NodeType
|
||||||
|
#isError = false
|
||||||
from: number
|
from: number
|
||||||
to: number
|
to: number
|
||||||
parent: SyntaxNode | null
|
parent: SyntaxNode | null
|
||||||
|
|
@ -336,7 +345,11 @@ export class SyntaxNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
get isError(): boolean {
|
get isError(): boolean {
|
||||||
return false
|
return this.#isError
|
||||||
|
}
|
||||||
|
|
||||||
|
set isError(err: boolean) {
|
||||||
|
this.#isError = err
|
||||||
}
|
}
|
||||||
|
|
||||||
get firstChild(): SyntaxNode | null {
|
get firstChild(): SyntaxNode | null {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { CompilerError } from '#compiler/compilerError'
|
||||||
import { Scanner, type Token, TokenType } from './tokenizer2'
|
import { Scanner, type Token, TokenType } from './tokenizer2'
|
||||||
import { SyntaxNode, operators, precedence, conditionals, compounds } from './node'
|
import { SyntaxNode, operators, precedence, conditionals, compounds } from './node'
|
||||||
import { globals } from './tokenizer'
|
import { globals } from './tokenizer'
|
||||||
import { parseString } from './stringParser'
|
import { parseString } from './stringParser'
|
||||||
|
import { Compiler } from '#compiler/compiler'
|
||||||
|
|
||||||
const $T = TokenType
|
const $T = TokenType
|
||||||
|
|
||||||
|
|
@ -256,6 +258,7 @@ export class Parser {
|
||||||
return val
|
return val
|
||||||
} else {
|
} else {
|
||||||
const arg = new SyntaxNode('PositionalArg', val.from, val.to)
|
const arg = new SyntaxNode('PositionalArg', val.from, val.to)
|
||||||
|
if (val.isError) arg.isError = true
|
||||||
arg.add(val)
|
arg.add(val)
|
||||||
return arg
|
return arg
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +359,7 @@ export class Parser {
|
||||||
return SyntaxNode.from(this.next())
|
return SyntaxNode.from(this.next())
|
||||||
|
|
||||||
const next = this.next()
|
const next = this.next()
|
||||||
throw `[atom] unexpected token ${TokenType[next.type]}: ${JSON.stringify(next)}\n\n ${this.input}\n`
|
throw new CompilerError(`Unexpected token: ${TokenType[next.type]}`, next.from, next.to)
|
||||||
}
|
}
|
||||||
|
|
||||||
// blocks in if, do, special calls, etc
|
// blocks in if, do, special calls, etc
|
||||||
|
|
@ -432,6 +435,7 @@ export class Parser {
|
||||||
// [ a=1 b=true c='three' ]
|
// [ a=1 b=true c='three' ]
|
||||||
dict(): SyntaxNode {
|
dict(): SyntaxNode {
|
||||||
const open = this.expect($T.OpenBracket)
|
const open = this.expect($T.OpenBracket)
|
||||||
|
let isError = false
|
||||||
|
|
||||||
// empty dict [=] or [ = ]
|
// empty dict [=] or [ = ]
|
||||||
if (this.is($T.Operator, '=') && this.nextIs($T.CloseBracket)) {
|
if (this.is($T.Operator, '=') && this.nextIs($T.CloseBracket)) {
|
||||||
|
|
@ -456,20 +460,29 @@ export class Parser {
|
||||||
if (this.nextIs($T.Operator, '=')) {
|
if (this.nextIs($T.Operator, '=')) {
|
||||||
const ident = this.identifier()
|
const ident = this.identifier()
|
||||||
const op = this.op('=')
|
const op = this.op('=')
|
||||||
const val = this.arg(true)
|
|
||||||
const prefix = new SyntaxNode('NamedArgPrefix', ident.from, op.to)
|
const prefix = new SyntaxNode('NamedArgPrefix', ident.from, op.to)
|
||||||
const node = new SyntaxNode('NamedArg', ident.from, val.to)
|
|
||||||
node.add(prefix)
|
if (this.is($T.CloseBracket) || this.is($T.Semicolon) || this.is($T.Newline)) {
|
||||||
node.add(val)
|
const node = new SyntaxNode('NamedArg', ident.from, op.to)
|
||||||
values.push(node)
|
node.isError = true
|
||||||
|
isError = true
|
||||||
|
values.push(node.push(prefix))
|
||||||
|
} else {
|
||||||
|
const val = this.arg(true)
|
||||||
|
const node = new SyntaxNode('NamedArg', ident.from, val.to)
|
||||||
|
values.push(node.push(prefix, val))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
values.push(this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg())
|
const arg = this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg()
|
||||||
|
if (arg.isError) isError = true
|
||||||
|
values.push(arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = this.expect($T.CloseBracket)
|
const close = this.expect($T.CloseBracket)
|
||||||
|
|
||||||
const node = new SyntaxNode('Dict', open.from, close.to)
|
const node = new SyntaxNode('Dict', open.from, close.to)
|
||||||
|
node.isError = isError
|
||||||
return node.push(...values)
|
return node.push(...values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,7 +504,7 @@ export class Parser {
|
||||||
else if (this.is($T.NamedArgPrefix))
|
else if (this.is($T.NamedArgPrefix))
|
||||||
arg = this.namedParam()
|
arg = this.namedParam()
|
||||||
else
|
else
|
||||||
throw `[do] expected Identifier or NamedArgPrefix, got ${JSON.stringify(this.current())}\n\n ${this.input}\n`
|
throw new CompilerError(`Expected Identifier or NamedArgPrefix, got ${TokenType[this.current().type]}`, this.current().from, this.current().to)
|
||||||
|
|
||||||
params.push(arg)
|
params.push(arg)
|
||||||
}
|
}
|
||||||
|
|
@ -605,14 +618,20 @@ export class Parser {
|
||||||
// you're lookin at it
|
// you're lookin at it
|
||||||
functionCall(fn?: SyntaxNode): SyntaxNode {
|
functionCall(fn?: SyntaxNode): SyntaxNode {
|
||||||
const ident = fn ?? this.identifier()
|
const ident = fn ?? this.identifier()
|
||||||
|
let isError = false
|
||||||
|
|
||||||
const args: SyntaxNode[] = []
|
const args: SyntaxNode[] = []
|
||||||
while (!this.isExprEnd())
|
while (!this.isExprEnd()) {
|
||||||
args.push(this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg())
|
const arg = this.is($T.NamedArgPrefix) ? this.namedArg() : this.arg()
|
||||||
|
if (arg.isError) isError = true
|
||||||
|
args.push(arg)
|
||||||
|
}
|
||||||
|
|
||||||
const node = new SyntaxNode('FunctionCall', ident.from, (args.at(-1) || ident).to)
|
const node = new SyntaxNode('FunctionCall', ident.from, (args.at(-1) || ident).to)
|
||||||
node.push(ident, ...args)
|
node.push(ident, ...args)
|
||||||
|
|
||||||
|
if (isError) node.isError = true
|
||||||
|
|
||||||
if (!this.inTestExpr && this.is($T.Colon)) {
|
if (!this.inTestExpr && this.is($T.Colon)) {
|
||||||
const block = this.block()
|
const block = this.block()
|
||||||
const end = this.keyword('end')
|
const end = this.keyword('end')
|
||||||
|
|
@ -718,6 +737,13 @@ export class Parser {
|
||||||
// abc= true
|
// abc= true
|
||||||
namedArg(): SyntaxNode {
|
namedArg(): SyntaxNode {
|
||||||
const prefix = SyntaxNode.from(this.expect($T.NamedArgPrefix))
|
const prefix = SyntaxNode.from(this.expect($T.NamedArgPrefix))
|
||||||
|
|
||||||
|
if (this.isExprEnd()) {
|
||||||
|
const node = new SyntaxNode('NamedArg', prefix.from, prefix.to)
|
||||||
|
node.isError = true
|
||||||
|
return node.push(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
const val = this.arg(true)
|
const val = this.arg(true)
|
||||||
const node = new SyntaxNode('NamedArg', prefix.from, val.to)
|
const node = new SyntaxNode('NamedArg', prefix.from, val.to)
|
||||||
return node.push(prefix, val)
|
return node.push(prefix, val)
|
||||||
|
|
@ -729,7 +755,7 @@ export class Parser {
|
||||||
const val = this.value()
|
const val = this.value()
|
||||||
|
|
||||||
if (!['Null', 'Boolean', 'Number', 'String'].includes(val.type.name))
|
if (!['Null', 'Boolean', 'Number', 'String'].includes(val.type.name))
|
||||||
throw `[namedParam] default value must be Null|Bool|Num|Str, got ${val.type}\n\n ${this.input}\n`
|
throw new CompilerError(`Default value must be null, boolean, number, or string, got ${val.type.name}`, val.from, val.to)
|
||||||
|
|
||||||
const node = new SyntaxNode('NamedParam', prefix.from, val.to)
|
const node = new SyntaxNode('NamedParam', prefix.from, val.to)
|
||||||
return node.push(prefix, val)
|
return node.push(prefix, val)
|
||||||
|
|
@ -739,7 +765,7 @@ export class Parser {
|
||||||
op(op?: string): SyntaxNode {
|
op(op?: string): SyntaxNode {
|
||||||
const token = op ? this.expect($T.Operator, op) : this.expect($T.Operator)
|
const token = op ? this.expect($T.Operator, op) : this.expect($T.Operator)
|
||||||
const name = operators[token.value!]
|
const name = operators[token.value!]
|
||||||
if (!name) throw `[op] operator not registered: ${token.value!}\n\n ${this.input}\n`
|
if (!name) throw new CompilerError(`Operator not registered: ${token.value!}`, token.from, token.to)
|
||||||
return new SyntaxNode(name, token.from, token.to)
|
return new SyntaxNode(name, token.from, token.to)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -919,7 +945,7 @@ export class Parser {
|
||||||
expect(type: TokenType, value?: string): Token | never {
|
expect(type: TokenType, value?: string): Token | never {
|
||||||
if (!this.is(type, value)) {
|
if (!this.is(type, value)) {
|
||||||
const token = this.current()
|
const token = this.current()
|
||||||
throw `expected ${TokenType[type]}${value ? ` "${value}"` : ''}, got ${TokenType[token?.type || 0]}${token?.value ? ` "${token.value}"` : ''} at position ${this.pos}\n\n ${this.input}\n`
|
throw new CompilerError(`Expected ${TokenType[type]}${value ? ` "${value}"` : ''}, got ${TokenType[token?.type || 0]}${token?.value ? ` "${token.value}"` : ''} at position ${this.pos}`, token.from, token.to)
|
||||||
}
|
}
|
||||||
return this.next()
|
return this.next()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,14 +109,14 @@ describe('calling functions', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.skip('Incomplete namedArg', () => {
|
test('Incomplete namedArg', () => {
|
||||||
expect('tail lines=').toMatchTree(`
|
expect('tail lines=').toMatchTree(`
|
||||||
FunctionCall
|
FunctionCall
|
||||||
Identifier tail
|
Identifier tail
|
||||||
NamedArg
|
NamedArg
|
||||||
NamedArgPrefix lines=
|
NamedArgPrefix lines=
|
||||||
⚠
|
⚠
|
||||||
⚠ `)
|
⚠`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,21 @@ export const treeToString2 = (tree: SyntaxNode, input: string, depth = 0): strin
|
||||||
if (node.name === 'Program') node = node.firstChild
|
if (node.name === 'Program') node = node.firstChild
|
||||||
|
|
||||||
while (node) {
|
while (node) {
|
||||||
lines.push(nodeToString(node, input, depth))
|
// If this node is an error, print ⚠ instead of its content
|
||||||
|
if (node.isError && !node.firstChild) {
|
||||||
|
lines.push(' '.repeat(depth) + '⚠')
|
||||||
|
} else {
|
||||||
|
lines.push(nodeToString(node, input, depth))
|
||||||
|
|
||||||
if (node.firstChild)
|
if (node.firstChild) {
|
||||||
lines.push(treeToString2(node.firstChild, input, depth + 1))
|
lines.push(treeToString2(node.firstChild, input, depth + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this node has an error, add ⚠ after its children
|
||||||
|
if (node.isError && node.firstChild) {
|
||||||
|
lines.push(' '.repeat(depth === 0 ? 0 : depth + 1) + '⚠')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node = node.nextSibling
|
node = node.nextSibling
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user