Two follow-ups on the Rhai formatter shipped in 0.5.1.
* Formatter no longer collapses user-intent blank lines between
statements. The lexer now records a side-channel list of offsets
where the source contained two-or-more consecutive newlines; the
formatter consults it and emits a single blank in the same spot
(rustfmt's `blank_lines_upper_bound = 1` policy applied strictly —
the prior forced blank between top-level `fn` decls is dropped, so
the formatter never *adds* a blank the user didn't write).
* Parse errors now read like Rhai's own diagnostics. `expect()` takes
an optional `role` hint and each call site supplies a domain phrase
(`name of a variable`, `function name in function declaration`,
`'{' to begin a block`, `name of a property`, …). End-of-input is
reported as `script is incomplete`. The dashboard banner renders
`Parse error: {message} (line L, position C)` with 1-based
coordinates, matching Rhai's format exactly.
The FormatError payload also keeps the byte `offset` so callers that
want to drive the editor cursor (CodeMirror works in offsets) still
have it.
Also folds the workspace Cargo.lock version bumps for 0.5.1 — the
lock-file rewrite that should have travelled with the prior commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
280 lines
5.8 KiB
TypeScript
280 lines
5.8 KiB
TypeScript
// 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[];
|
|
// Offsets at which the source contained a blank line (a whitespace
|
|
// run with two or more newlines). One entry per blank run; the
|
|
// formatter consults these to preserve user-intent vertical grouping.
|
|
blankLines: number[];
|
|
}
|