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:
275
dashboard/src/lib/rhai/ast.ts
Normal file
275
dashboard/src/lib/rhai/ast.ts
Normal 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[];
|
||||
}
|
||||
Reference in New Issue
Block a user