shrimp/src/parser/node.ts

440 lines
7.6 KiB
TypeScript

import { type Token, TokenType } from './tokenizer2'
import * as term from './shrimp.terms'
export type NodeType =
| 'Program'
| 'Block'
| 'FunctionCall'
| 'FunctionCallOrIdentifier'
| 'FunctionCallWithBlock'
| 'PositionalArg'
| 'NamedArg'
| 'NamedArgPrefix'
| 'FunctionDef'
| 'Params'
| 'NamedParam'
| 'Null'
| 'Boolean'
| 'Number'
| 'String'
| 'StringFragment'
| 'CurlyString'
| 'DoubleQuote'
| 'EscapeSeq'
| 'Interpolation'
| 'Regex'
| 'Identifier'
| 'AssignableIdentifier'
| 'IdentifierBeforeDot'
| 'Word'
| 'Array'
| 'Dict'
| 'Comment'
| 'BinOp'
| 'ConditionalOp'
| 'ParenExpr'
| 'Assign'
| 'CompoundAssign'
| 'DotGet'
| 'PipeExpr'
| 'IfExpr'
| 'ElseIfExpr'
| 'ElseExpr'
| 'WhileExpr'
| 'TryExpr'
| 'CatchExpr'
| 'FinallyExpr'
| 'Throw'
| 'Eq'
| 'Modulo'
| 'Plus'
| 'Star'
| 'Slash'
| 'Import'
| 'Do'
| 'Underscore'
| 'colon'
| 'keyword'
| 'operator'
// TODO: remove this when we switch from lezer
export const operators: Record<string, any> = {
// Logic
'and': 'And',
'or': 'Or',
// Bitwise
'band': 'Band',
'bor': 'Bor',
'bxor': 'Bxor',
'>>>': 'Ushr',
'>>': 'Shr',
'<<': 'Shl',
// Comparison
'>=': 'Gte',
'<=': 'Lte',
'>': 'Gt',
'<': 'Lt',
'!=': 'Neq',
'==': 'EqEq',
// Compound assignment operators
'??=': 'NullishEq',
'+=': 'PlusEq',
'-=': 'MinusEq',
'*=': 'StarEq',
'/=': 'SlashEq',
'%=': 'ModuloEq',
// Nullish coalescing
'??': 'NullishCoalesce',
// Math
'*': 'Star',
'**': 'StarStar',
'=': 'Eq',
'/': 'Slash',
'+': 'Plus',
'-': 'Minus',
'%': 'Modulo',
// Dotget
'.': 'Dot',
// Pipe
'|': 'operator',
}
export class Tree {
constructor(public topNode: SyntaxNode) { }
get length(): number {
return this.topNode.to
}
cursor() {
return {
type: this.topNode.type,
from: this.topNode.from,
to: this.topNode.to,
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
class SyntaxNodeType {
constructor(public nodeType: NodeType) { }
is(other: string) {
return this.nodeType === other
}
get id(): number {
switch (this.nodeType) {
case 'Program':
return term.Program
case 'Block':
return term.Block
case 'FunctionCall':
return term.FunctionCall
case 'FunctionCallOrIdentifier':
return term.FunctionCallOrIdentifier
case 'FunctionCallWithBlock':
return term.FunctionCallWithBlock
case 'PositionalArg':
return term.PositionalArg
case 'NamedArg':
return term.NamedArg
case 'FunctionDef':
return term.FunctionDef
case 'Params':
return term.Params
case 'NamedParam':
return term.NamedParam
case 'Null':
return term.Null
case 'Boolean':
return term.Boolean
case 'Number':
return term.Number
case 'String':
return term.String
case 'StringFragment':
return term.StringFragment
case 'CurlyString':
return term.CurlyString
case 'DoubleQuote':
return term.DoubleQuote
case 'EscapeSeq':
return term.EscapeSeq
case 'Interpolation':
return term.Interpolation
case 'Regex':
return term.Regex
case 'Identifier':
return term.Identifier
case 'AssignableIdentifier':
return term.AssignableIdentifier
case 'IdentifierBeforeDot':
return term.IdentifierBeforeDot
case 'Word':
return term.Word
case 'Array':
return term.Array
case 'Dict':
return term.Dict
case 'Comment':
return term.Comment
case 'BinOp':
return term.BinOp
case 'ConditionalOp':
return term.ConditionalOp
case 'ParenExpr':
return term.ParenExpr
case 'Assign':
return term.Assign
case 'CompoundAssign':
return term.CompoundAssign
case 'DotGet':
return term.DotGet
case 'PipeExpr':
return term.PipeExpr
case 'IfExpr':
return term.IfExpr
case 'ElseIfExpr':
return term.ElseIfExpr
case 'ElseExpr':
return term.ElseExpr
case 'WhileExpr':
return term.WhileExpr
case 'TryExpr':
return term.TryExpr
case 'CatchExpr':
return term.CatchExpr
case 'FinallyExpr':
return term.FinallyExpr
case 'Throw':
return term.Throw
case 'Eq':
return term.Eq
case 'Modulo':
return term.Modulo
case 'Plus':
return term.Plus
case 'Star':
return term.Star
case 'Slash':
return term.Slash
case 'Import':
return term.Import
case 'Do':
return term.Do
case 'Underscore':
return term.Underscore
case 'colon':
return term.colon
case 'keyword':
return term.keyword
}
return 0
}
get name(): string {
return this.nodeType
}
}
export class SyntaxNode {
#type: NodeType
#isError = false
from: number
to: number
parent: SyntaxNode | null
children: SyntaxNode[] = []
constructor(type: NodeType, from: number, to: number, parent: SyntaxNode | null = null) {
this.#type = type
this.from = from
this.to = to
this.parent = parent
}
static from(token: Token, parent?: SyntaxNode): SyntaxNode {
return new SyntaxNode(TokenType[token.type] as NodeType, token.from, token.to, parent ?? null)
}
get type(): SyntaxNodeType {
return new SyntaxNodeType(this.#type)
}
set type(name: NodeType) {
this.#type = name
}
get name(): string {
return this.type.name
}
get isError(): boolean {
return this.#isError
}
set isError(err: boolean) {
this.#isError = err
}
get firstChild(): SyntaxNode | null {
return this.children[0] ?? null
}
get lastChild(): SyntaxNode | null {
return this.children.at(-1) ?? null
}
get nextSibling(): SyntaxNode | null {
if (!this.parent) return null
const siblings = this.parent.children
const index = siblings.indexOf(this)
return index >= 0 && index < siblings.length - 1 ? siblings[index + 1]! : null
}
get prevSibling(): SyntaxNode | null {
if (!this.parent) return null
const siblings = this.parent.children
const index = siblings.indexOf(this)
return index > 0 ? siblings[index - 1]! : null
}
add(node: SyntaxNode) {
node.parent = this
this.children.push(node)
}
push(...nodes: SyntaxNode[]): SyntaxNode {
nodes.forEach(child => child.parent = this)
this.children.push(...nodes)
return this
}
toString(): string {
return this.type.name
}
}
// Operator precedence (binding power) - higher = tighter binding
export const precedence: Record<string, number> = {
// Logical
'or': 10,
'and': 20,
// Comparison
'==': 30,
'!=': 30,
'<': 30,
'>': 30,
'<=': 30,
'>=': 30,
// Nullish coalescing
'??': 35,
// Bitwise shifts (lower precedence than addition)
'<<': 37,
'>>': 37,
'>>>': 37,
// Addition/Subtraction
'+': 40,
'-': 40,
// Bitwise AND/OR/XOR (higher precedence than addition)
'band': 45,
'bor': 45,
'bxor': 45,
// Multiplication/Division/Modulo
'*': 50,
'/': 50,
'%': 50,
// Exponentiation (right-associative)
'**': 60,
}
export const conditionals = new Set([
'==', '!=', '<', '>', '<=', '>=', '??', 'and', 'or'
])
export const compounds = [
'??=', '+=', '-=', '*=', '/=', '%='
]