feat(dashboard): hand-rolled Rhai parser + symbol table + Vitest

Foundation for upcoming editor features (scope-aware autocomplete,
goto-def / find-usages, source formatter). Hand-rolled recursive
descent in TypeScript with Pratt precedence climbing for expressions,
error-tolerant so partial trees stay usable while the user is typing.
Symbol table walks the AST to produce per-scope declarations, usage
sites, and object-literal field maps. Vitest added as a dev-only
runner; no editor wiring in this commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-23 23:38:15 +02:00
parent a80e6d1ca4
commit bc8b512b56
11 changed files with 2361 additions and 3 deletions

View File

@@ -0,0 +1,275 @@
// AST node definitions for the dashboard's hand-rolled Rhai parser.
//
// Every node carries `start` / `end` byte offsets into the source so the
// editor features (autocomplete, goto-def, find-usages, format) can map
// between positions in the document and nodes in the tree.
//
// The shape mirrors the Rhai book grammar (https://rhai.rs/book/language/)
// but simplified: type annotations are absent (Rhai is dynamic), and
// statement-vs-expression duality is collapsed by letting `if` / `switch` /
// block expressions appear in both positions (an `ExprStmt` wrapper turns
// any expression into a statement).
export interface Range {
start: number;
end: number;
}
// ---------------------------------------------------------------------------
// Comments — captured by the lexer with their positions and re-emitted by
// the formatter. Kept off the AST tree so they don't clutter walkers.
// ---------------------------------------------------------------------------
export interface Comment extends Range {
kind: 'LineComment' | 'BlockComment';
text: string;
}
// ---------------------------------------------------------------------------
// Statements
// ---------------------------------------------------------------------------
export type Stmt =
| LetStmt
| ConstStmt
| FnDecl
| ExprStmt
| ReturnStmt
| WhileStmt
| LoopStmt
| ForStmt
| BreakStmt
| ContinueStmt
| TryStmt;
export interface LetStmt extends Range {
kind: 'Let';
name: string;
nameRange: Range;
init: Expr | null;
}
export interface ConstStmt extends Range {
kind: 'Const';
name: string;
nameRange: Range;
init: Expr | null;
}
export interface Param extends Range {
name: string;
}
export interface FnDecl extends Range {
kind: 'FnDecl';
name: string;
nameRange: Range;
params: Param[];
body: BlockExpr;
}
export interface ExprStmt extends Range {
kind: 'ExprStmt';
expr: Expr;
// Whether the statement is terminated with `;`. Block-form expressions
// (`if`/`switch`/`{...}`) don't require it; everything else does.
semi: boolean;
}
export interface ReturnStmt extends Range {
kind: 'Return';
value: Expr | null;
}
export interface WhileStmt extends Range {
kind: 'While';
cond: Expr;
body: BlockExpr;
}
export interface LoopStmt extends Range {
kind: 'Loop';
body: BlockExpr;
}
export interface ForStmt extends Range {
kind: 'For';
varName: string;
varRange: Range;
iter: Expr;
body: BlockExpr;
}
export interface BreakStmt extends Range {
kind: 'Break';
}
export interface ContinueStmt extends Range {
kind: 'Continue';
}
export interface TryStmt extends Range {
kind: 'Try';
body: BlockExpr;
catchVar: string | null;
catchVarRange: Range | null;
handler: BlockExpr;
}
// ---------------------------------------------------------------------------
// Expressions
// ---------------------------------------------------------------------------
export type Expr =
| IdentExpr
| NumberExpr
| StringExpr
| BoolExpr
| NullExpr
| CallExpr
| MemberExpr
| IndexExpr
| UnaryExpr
| BinaryExpr
| AssignExpr
| ParenExpr
| ObjectMapExpr
| ArrayExpr
| FnExpr
| IfExpr
| SwitchExpr
| BlockExpr;
export interface IdentExpr extends Range {
kind: 'Ident';
name: string;
}
export interface NumberExpr extends Range {
kind: 'Number';
raw: string;
}
export interface StringExpr extends Range {
kind: 'String';
// The surrounding quote — `"` is escape-processed, backtick is raw and
// may span multiple lines. We don't decode escapes; the formatter just
// preserves the raw text between the quotes.
quote: '"' | '`';
raw: string;
}
export interface BoolExpr extends Range {
kind: 'Bool';
value: boolean;
}
export interface NullExpr extends Range {
kind: 'Null';
}
export interface CallExpr extends Range {
kind: 'Call';
callee: Expr;
args: Expr[];
}
export interface MemberExpr extends Range {
kind: 'Member';
object: Expr;
property: string;
propertyRange: Range;
}
export interface IndexExpr extends Range {
kind: 'Index';
object: Expr;
index: Expr;
}
export interface UnaryExpr extends Range {
kind: 'Unary';
op: string;
operand: Expr;
}
export interface BinaryExpr extends Range {
kind: 'Binary';
op: string;
left: Expr;
right: Expr;
}
export interface AssignExpr extends Range {
kind: 'Assign';
op: string; // = += -= *= /= %= ??=
target: Expr;
value: Expr;
}
export interface ParenExpr extends Range {
kind: 'Paren';
expr: Expr;
}
export interface ObjectMapEntry extends Range {
key: string;
keyRange: Range;
value: Expr;
}
export interface ObjectMapExpr extends Range {
kind: 'ObjectMap';
entries: ObjectMapEntry[];
}
export interface ArrayExpr extends Range {
kind: 'Array';
elements: Expr[];
}
export interface FnExpr extends Range {
kind: 'FnExpr';
params: Param[];
body: BlockExpr;
}
export interface IfExpr extends Range {
kind: 'IfExpr';
cond: Expr;
then: BlockExpr;
// else branch: either a block or another `if` for `else if` chains.
else_: BlockExpr | IfExpr | null;
}
export interface SwitchArm extends Range {
pattern: Expr | null; // null = `_` default case
guard: Expr | null;
value: Expr;
}
export interface SwitchExpr extends Range {
kind: 'SwitchExpr';
subject: Expr;
arms: SwitchArm[];
}
export interface BlockExpr extends Range {
kind: 'BlockExpr';
stmts: Stmt[];
}
// ---------------------------------------------------------------------------
// Top-level parse output
// ---------------------------------------------------------------------------
export interface ParseError extends Range {
message: string;
}
export interface ParseResult {
source: string;
program: BlockExpr;
errors: ParseError[];
comments: Comment[];
}