diff --git a/src/lang/expr.ts b/src/lang/expr.ts index 329796f..492ad0f 100644 --- a/src/lang/expr.ts +++ b/src/lang/expr.ts @@ -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 }); } diff --git a/src/lang/parser/parser.ts b/src/lang/parser/parser.ts index 4156b70..57b9c35 100644 --- a/src/lang/parser/parser.ts +++ b/src/lang/parser/parser.ts @@ -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)); } } diff --git a/src/renderer.ts b/src/renderer.ts index cd03b6d..94a8c75 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -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() {