427 lines
7.4 KiB
TypeScript
427 lines
7.4 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,
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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<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 = [
|
|
'??=', '+=', '-=', '*=', '/=', '%='
|
|
]
|