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 = { // 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, } } } // 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 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 false } 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 = { // 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 = [ '??=', '+=', '-=', '*=', '/=', '%=' ]