Track spans in AST
This commit is contained in:
parent
cd84d74ec7
commit
5dfa31f27f
3 changed files with 76 additions and 65 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import { Span } from "./parser/source_text"
|
||||
|
||||
// === Identifiers ===
|
||||
export type VariableName = string
|
||||
export type FunctionName = string
|
||||
|
|
@ -6,19 +8,21 @@ export type Tag = string
|
|||
export type FieldName = string
|
||||
|
||||
// === Expr ===
|
||||
export type Meta = { span: Span };
|
||||
|
||||
export type Expr =
|
||||
| { tag: "literal", literal: Literal }
|
||||
| { tag: "var_use", name: VariableName }
|
||||
| { tag: "literal", literal: Literal} & Meta
|
||||
| { tag: "var_use", name: VariableName } & Meta
|
||||
// | { tag: "cell_ref", name: CellName }
|
||||
| { tag: "call", name: FunctionName, args: Expr[] }
|
||||
| { tag: "let", bindings: ExprBinding[], body: Expr }
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, expr: Expr }
|
||||
| { tag: "tuple", exprs: Expr[] }
|
||||
| { tag: "record", fields: FieldAssignment[] }
|
||||
| { tag: "match", arg: Expr, branches: MatchBranch[] }
|
||||
| { tag: "lambda", parameters: ProductPattern[], body: Expr }
|
||||
| { tag: "apply", callee: Expr, args: Expr[] }
|
||||
| { tag: "call", name: FunctionName, args: Expr[] } & Meta
|
||||
| { tag: "let", bindings: ExprBinding[], body: Expr } & Meta
|
||||
| { tag: "tag", tag_name: Tag } & Meta
|
||||
| { tag: "tagged", tag_name: Tag, expr: Expr } & Meta
|
||||
| { tag: "tuple", exprs: Expr[] } & Meta
|
||||
| { tag: "record", fields: FieldAssignment[] } & Meta
|
||||
| { tag: "match", arg: Expr, branches: MatchBranch[] } & Meta
|
||||
| { tag: "lambda", parameters: ProductPattern[], body: Expr } & Meta
|
||||
| { tag: "apply", callee: Expr, args: Expr[] } & Meta
|
||||
|
||||
export type Literal =
|
||||
| { tag: "number", value: number }
|
||||
|
|
@ -27,60 +31,60 @@ export type Literal =
|
|||
export type ExprBinding = {
|
||||
pattern: ProductPattern,
|
||||
expr: Expr,
|
||||
}
|
||||
} & Meta
|
||||
|
||||
export type MatchBranch = {
|
||||
pattern: Pattern,
|
||||
body: Expr,
|
||||
}
|
||||
} & Meta
|
||||
|
||||
export type FieldAssignment = { name: FieldName, expr: Expr };
|
||||
export type FieldAssignment = { name: FieldName, expr: Expr } & Meta;
|
||||
|
||||
// === Pattern ===
|
||||
export type ProductPattern =
|
||||
| { tag: "any", name: VariableName }
|
||||
| { tag: "tuple", patterns: ProductPattern[] }
|
||||
| { tag: "record", fields: FieldPattern[] }
|
||||
| { tag: "any", name: VariableName } & Meta
|
||||
| { tag: "tuple", patterns: ProductPattern[] } & Meta
|
||||
| { tag: "record", fields: FieldPattern[] } & Meta
|
||||
|
||||
export type FieldPattern = { fieldName: FieldName, pattern: ProductPattern };
|
||||
export type FieldPattern = { fieldName: FieldName, pattern: ProductPattern } & Meta;
|
||||
|
||||
export type Pattern =
|
||||
| ProductPattern
|
||||
| { tag: "tag", tag_name: Tag }
|
||||
| { tag: "tagged", tag_name: Tag, pattern: Pattern }
|
||||
| { tag: "tag", tag_name: Tag } & Meta
|
||||
| { tag: "tagged", tag_name: Tag, pattern: Pattern } & Meta
|
||||
|
||||
|
||||
// === Constructors ===
|
||||
export namespace Expr {
|
||||
const literal = (literal: Literal): Expr => ({ tag: "literal", literal });
|
||||
export const number = (value: number): Expr => literal({ tag: "number", value });
|
||||
export const string = (value: string): Expr => literal({ tag: "string", value });
|
||||
export const call = (name: FunctionName, args: Expr[]): Expr => ({ tag: "call", name, args, });
|
||||
export const tag = (tag_name: Tag): Expr => ({ tag: "tag", tag_name, });
|
||||
export const tagged = (tag_name: Tag, expr: Expr): Expr => ({ tag: "tagged", tag_name, expr, });
|
||||
export const tuple = (exprs: Expr[]): Expr => ({ tag: "tuple", exprs });
|
||||
export const record = (fields: FieldAssignment[]): Expr => ({ tag: "record", fields });
|
||||
export const match = (arg: Expr, branches: MatchBranch[]): Expr => ({ tag: "match", arg, branches, });
|
||||
export const var_use = (name: VariableName): Expr => ({ tag: "var_use", name, });
|
||||
export const let_ = (bindings: ExprBinding[], body: Expr): Expr => ({ tag: "let", bindings, body, });
|
||||
export const apply = (callee: Expr, args: Expr[]): Expr => ({ tag: "apply", callee, args, });
|
||||
export const lambda = (parameters: ProductPattern[], body: Expr): Expr => ({ tag: "lambda", parameters, body, });
|
||||
const literal = (literal: Literal, span: Span): Expr => ({ tag: "literal", literal, span });
|
||||
export const number = (value: number, span: Span): Expr => literal({ tag: "number", value }, span);
|
||||
export const string = (value: string, span: Span): Expr => literal({ tag: "string", value }, span);
|
||||
export const call = (name: FunctionName, args: Expr[], span: Span): Expr => ({ tag: "call", name, args, span});
|
||||
export const tag = (tag_name: Tag, span: Span): Expr => ({ tag: "tag", tag_name, span });
|
||||
export const tagged = (tag_name: Tag, expr: Expr, span: Span): Expr => ({ tag: "tagged", tag_name, expr, span });
|
||||
export const tuple = (exprs: Expr[], span: Span): Expr => ({ tag: "tuple", exprs, span });
|
||||
export const record = (fields: FieldAssignment[], span: Span): Expr => ({ tag: "record", fields, span });
|
||||
export const match = (arg: Expr, branches: MatchBranch[], span: Span): Expr => ({ tag: "match", arg, branches, span});
|
||||
export const var_use = (name: VariableName, span: Span): Expr => ({ tag: "var_use", name, span });
|
||||
export const let_ = (bindings: ExprBinding[], body: Expr, span: Span): Expr => ({ tag: "let", bindings, body, span });
|
||||
export const apply = (callee: Expr, args: Expr[], span: Span): Expr => ({ tag: "apply", callee, args, span });
|
||||
export const lambda = (parameters: ProductPattern[], body: Expr, span: Span): Expr => ({ tag: "lambda", parameters, body, span });
|
||||
|
||||
export const matchBranch = (pattern: Pattern, expr: Expr): MatchBranch => ({ pattern, body: expr });
|
||||
export const exprBinding = (pattern: ProductPattern, expr: Expr): ExprBinding => ({ pattern, expr });
|
||||
export const fieldAssignment = (name: FieldName, expr: Expr): FieldAssignment => ({ name, expr });
|
||||
export const matchBranch = (pattern: Pattern, expr: Expr, span: Span): MatchBranch => ({ pattern, body: expr, span });
|
||||
export const exprBinding = (pattern: ProductPattern, expr: Expr, span: Span): ExprBinding => ({ pattern, expr, span });
|
||||
export const fieldAssignment = (name: FieldName, expr: Expr, span: Span): FieldAssignment => ({ name, expr, span });
|
||||
}
|
||||
|
||||
export namespace ProductPattern {
|
||||
export const any = (name: VariableName): ProductPattern => ({ tag: "any", name });
|
||||
export const tuple = (patterns: ProductPattern[]): ProductPattern => ({ tag: "tuple", patterns });
|
||||
export const record = (fields: FieldPattern[]): ProductPattern => ({ tag: "record", fields });
|
||||
export const any = (name: VariableName, span: Span): ProductPattern => ({ tag: "any", name, span });
|
||||
export const tuple = (patterns: ProductPattern[], span: Span): ProductPattern => ({ tag: "tuple", patterns, span });
|
||||
export const record = (fields: FieldPattern[], span: Span): ProductPattern => ({ tag: "record", fields, span });
|
||||
|
||||
export const fieldPattern = (fieldName: FieldName, pattern: ProductPattern): FieldPattern => ({ fieldName, pattern });
|
||||
export const fieldPattern = (fieldName: FieldName, pattern: ProductPattern, span: Span): FieldPattern => ({ fieldName, pattern, span });
|
||||
}
|
||||
|
||||
export namespace Pattern {
|
||||
export const tag = (tag_name: Tag): Pattern => ({ tag: "tag", tag_name });
|
||||
export const tagged = (tag_name: Tag, pattern: Pattern): Pattern => ({ tag: "tagged", tag_name, pattern });
|
||||
export const tag = (tag_name: Tag, span: Span): Pattern => ({ tag: "tag", tag_name, span });
|
||||
export const tagged = (tag_name: Tag, pattern: Pattern, span: Span): Pattern => ({ tag: "tagged", tag_name, pattern, span });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import { Versions } from './versions';
|
|||
|
||||
declare const versions: Versions; // preload
|
||||
|
||||
const information = document.getElementById('info');
|
||||
const information = document.getElementById('info') as HTMLElement;
|
||||
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`;
|
||||
|
||||
async function func() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue