Track spans in AST

This commit is contained in:
Yura Dupyn 2026-02-07 16:02:35 +01:00
parent cd84d74ec7
commit 5dfa31f27f
3 changed files with 76 additions and 65 deletions

View file

@ -168,6 +168,7 @@ function identifier(cursor: Cursor, kind: IdentifierKind): { name: string, span:
// === Expression Parsers ===
function expr(cursor: Cursor): Expr {
const start = cursor.currentLocation();
const token = exprStartToken(cursor);
// TODO: You need to include the spans and perhaps other meta-info.
switch (token.tag) {
@ -178,24 +179,24 @@ function expr(cursor: Cursor): Expr {
span: token.span
} as ParseError;
case "number":
return Expr.number(token.value);
return Expr.number(token.value, token.span);
case "string":
return Expr.string(token.text);
return Expr.string(token.text, token.span);
case "variable_use":
return Expr.var_use(token.name);
return Expr.var_use(token.name, token.span);
case "tag":
// #true
// #foo e
if (isNextTokenExprStart(cursor)) {
const value = expr(cursor);
return Expr.tagged(token.name, value);
return Expr.tagged(token.name, value, cursor.makeSpan(start));
} else {
return Expr.tag(token.name);
return Expr.tag(token.name, token.span);
}
case "tuple_start":
// e.g. (a, b, c)
const items = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, expr);
return Expr.tuple(items);
return Expr.tuple(items, cursor.makeSpan(start));
case "function_name":
// e.g. my_func(arg1, arg2)
// parse a `,` delimiter sequence of expr
@ -207,7 +208,7 @@ function expr(cursor: Cursor): Expr {
} as ParseError;
}
const args = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, expr);
return Expr.call(token.name, args);
return Expr.call(token.name, args, cursor.makeSpan(start));
case "keyword":
switch (token.kw) {
case ":":
@ -220,7 +221,7 @@ function expr(cursor: Cursor): Expr {
} as ParseError;
}
const fields = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, fieldAssignment);
return Expr.record(fields);
return Expr.record(fields, cursor.makeSpan(start));
case "let":
// let { p0 = e0, p1 = e2 . body }
if (!tryConsume(cursor, char('{'))) {
@ -238,7 +239,7 @@ function expr(cursor: Cursor): Expr {
span: cursor.makeSpan(cursor.currentLocation())
} as ParseError;
}
return Expr.let_(bindings, body);
return Expr.let_(bindings, body, cursor.makeSpan(start));
case "fn": {
// fn { p0, p1, p2 . body }
if (!tryConsume(cursor, char('{'))) {
@ -256,7 +257,7 @@ function expr(cursor: Cursor): Expr {
span: cursor.makeSpan(cursor.currentLocation())
} as ParseError;
}
return Expr.lambda(parameters, body);
return Expr.lambda(parameters, body, cursor.makeSpan(start));
}
case "apply":
// apply(e ! e0, e1, e2)
@ -275,7 +276,7 @@ function expr(cursor: Cursor): Expr {
}
const args = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, expr);
return Expr.apply(callee, args);
return Expr.apply(callee, args, cursor.makeSpan(start));
case "match":
// match e { branch0 | branch1 | branch2 }
const arg = expr(cursor);
@ -287,7 +288,7 @@ function expr(cursor: Cursor): Expr {
}
const branches = delimitedTerminalSequence(cursor, DELIMITER_PIPE, TERMINATOR_CLOSE_BRACE, matchBranch);
return Expr.match(arg, branches)
return Expr.match(arg, branches, cursor.makeSpan(start))
case "=":
case "|":
case "!":
@ -302,6 +303,7 @@ function expr(cursor: Cursor): Expr {
function matchBranch(cursor: Cursor): MatchBranch {
// p . body
const start = cursor.currentLocation();
const p = pattern(cursor);
if (!tryConsume(cursor, char("."))) {
@ -311,10 +313,11 @@ function matchBranch(cursor: Cursor): MatchBranch {
} as ParseError;
}
const e = expr(cursor);
return Expr.matchBranch(p, e);
return Expr.matchBranch(p, e, cursor.makeSpan(start));
}
function productPatternBinding(cursor: Cursor): ExprBinding {
const start = cursor.currentLocation();
// TODO: There's a potential here to do a lot of work on nice errors.
// `p = e`
// here there could be problems like the pattern being just a variable that uses `=` as its part
@ -333,10 +336,11 @@ function productPatternBinding(cursor: Cursor): ExprBinding {
} as ParseError;
}
const e = expr(cursor);
return Expr.exprBinding(pattern, e);
return Expr.exprBinding(pattern, e, cursor.makeSpan(start));
}
function fieldAssignment(cursor: Cursor): FieldAssignment {
const start = cursor.currentLocation();
// `f = e`
const { name, span } = identifier(cursor, 'field_name');
@ -348,10 +352,11 @@ function fieldAssignment(cursor: Cursor): FieldAssignment {
}
const value = expr(cursor);
return Expr.fieldAssignment(name, value);
return Expr.fieldAssignment(name, value, cursor.makeSpan(start));
}
function pattern(cursor: Cursor): Pattern {
const start = cursor.currentLocation();
// x
// (x, y, z)
// ((x, y), z)
@ -368,10 +373,10 @@ function pattern(cursor: Cursor): Pattern {
if (isNextTokenProductPatternStart(cursor)) {
// Parse the payload (must be a product pattern)
const payload = productPattern(cursor);
return Pattern.tagged(token.name, payload);
return Pattern.tagged(token.name, payload, cursor.makeSpan(start));
} else {
// Standalone Tag: #foo
return Pattern.tag(token.name);
return Pattern.tag(token.name, cursor.makeSpan(start));
}
}
@ -392,15 +397,16 @@ function productPattern(cursor: Cursor): ProductPattern {
}
function finishProductPattern(cursor: Cursor, token: PatternStartToken): ProductPattern {
const start = cursor.currentLocation();
switch (token.tag) {
case "pattern_binding":
// foo
return ProductPattern.any(token.name);
return ProductPattern.any(token.name, cursor.makeSpan(start));
case "tuple_start": {
// ( p1, p2 )
const items = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, productPattern);
return ProductPattern.tuple(items);
return ProductPattern.tuple(items, cursor.makeSpan(start));
}
case "tag":
@ -418,7 +424,7 @@ function finishProductPattern(cursor: Cursor, token: PatternStartToken): Product
} as ParseError;
}
const fields = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, recordPatternField);
return ProductPattern.record(fields);
return ProductPattern.record(fields, cursor.makeSpan(start));
}
default:
// These keywords CANNOT start a pattern.
@ -433,13 +439,14 @@ function finishProductPattern(cursor: Cursor, token: PatternStartToken): Product
}
function recordPatternField(cursor: Cursor): FieldPattern {
const start = cursor.currentLocation();
const { name, span } = identifier(cursor, 'field_name');
if (tryConsume(cursor, char('='))) {
const p = productPattern(cursor);
return ProductPattern.fieldPattern(name, p);
return ProductPattern.fieldPattern(name, p, cursor.makeSpan(start));
} else {
// Punning: :( a ) -> :( a = a )
return ProductPattern.fieldPattern(name, ProductPattern.any(name));
return ProductPattern.fieldPattern(name, ProductPattern.any(name, span), cursor.makeSpan(start));
}
}