Update parser for signal-expressions
This commit is contained in:
parent
94cb3bd721
commit
5e7578c4a3
4 changed files with 151 additions and 9 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// AI GENERATED
|
||||
import * as readline from 'readline';
|
||||
import * as fs from 'fs';
|
||||
import { parse, ParseError } from '../parser/parser';
|
||||
import { parseExpr, ParseError } from '../parser/parser';
|
||||
import { SourceText, renderSpan, sourceText } from '../parser/source_text';
|
||||
import { exprToString } from '../debug/expr_show';
|
||||
import { valueToString } from '../debug/value_show';
|
||||
|
|
@ -30,7 +30,7 @@ function runSource(inputRaw: string, isRepl: boolean): boolean {
|
|||
const text = sourceText(input);
|
||||
|
||||
// === Parse ===
|
||||
const parseResult = parse(text);
|
||||
const parseResult = parseExpr(text);
|
||||
|
||||
if (parseResult.tag === "error") {
|
||||
printPrettyError(text, parseResult.error);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ export type Expr =
|
|||
|
||||
export type SignalExpr =
|
||||
| { tag: "read", name: SignalName } & Meta
|
||||
// TODO: Is `const` necesary?
|
||||
| { tag: "const", arg: Expr } & Meta
|
||||
| { tag: "let", bindings: SignalBinding[], body: Expr } & Meta
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { Cursor } from './cursor';
|
||||
import { ExprScanError, exprStart, ExprStartToken, IdentifierKind, identifierScanner, isNextTokenExprStart, isNextTokenProductPatternStart, patternStart, PatternStartToken, skipWhitespaceAndComments } from './scanner';
|
||||
import { ExprScanError, exprStart, ExprStartToken, IdentifierKind, identifierScanner, isNextTokenExprStart, isNextTokenProductPatternStart, patternStart, PatternStartToken, signalExprStart, SignalExprStartToken, skipWhitespaceAndComments } from './scanner';
|
||||
import { char, CodePoint, SourceText, Span } from './source_text';
|
||||
import { Result } from '../result';
|
||||
import { Expr, ExprBinding, FieldAssignment, FieldPattern, MatchBranch, Pattern, ProductPattern } from '../expr';
|
||||
import { UserFunctionDefinition } from '../program';
|
||||
import { Product } from 'electron';
|
||||
import { Expr, ExprBinding, FieldAssignment, FieldPattern, MatchBranch, Pattern, ProductPattern, SignalExpr } from '../expr';
|
||||
|
||||
// CONVENTION: Every parser is responsible to consume whitespace/comments at the end.
|
||||
// Every parser is not responsible for cleaning up whitespace/comments at the start - only the final `parse` that's exposed to the public.
|
||||
|
|
@ -23,6 +21,7 @@ export type ParseError =
|
|||
|
||||
// === Specific Context Errors ===
|
||||
| { tag: "ExpectedExpression", span: Span } // Expected start of expr (e.g. hit EOF or keyword)
|
||||
| { tag: "ExpectedSignalExpression", span: Span } // Expected start of signal expr (e.g. hit EOF or keyword)
|
||||
| { tag: "ExpectedFieldAssignmentSymbol", span: Span } // Expected '=' in field assignment
|
||||
| { tag: "ExpectedPatternAssignmentSymbol", span: Span } // Expected '=' in pattern assignment
|
||||
| { tag: "ExpectedPatternBindingSymbol", span: Span } // Expected '.' in pattern binding
|
||||
|
|
@ -155,6 +154,12 @@ function exprStartToken(cursor: Cursor): ExprStartToken {
|
|||
return token;
|
||||
}
|
||||
|
||||
function signalExprStartToken(cursor: Cursor): SignalExprStartToken {
|
||||
const token = signalExprStart(cursor);
|
||||
skipWhitespaceAndComments(cursor);
|
||||
return token;
|
||||
}
|
||||
|
||||
function patternStartToken(cursor: Cursor): PatternStartToken {
|
||||
const token = patternStart(cursor);
|
||||
skipWhitespaceAndComments(cursor);
|
||||
|
|
@ -172,7 +177,6 @@ function identifier(cursor: Cursor, kind: IdentifierKind): { name: string, span:
|
|||
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) {
|
||||
case "EOF":
|
||||
throw {
|
||||
|
|
@ -291,6 +295,9 @@ function expr(cursor: Cursor): Expr {
|
|||
|
||||
const branches = delimitedTerminalSequence(cursor, DELIMITER_PIPE, TERMINATOR_CLOSE_BRACE, matchBranch);
|
||||
return Expr.match(arg, branches, cursor.makeSpan(start))
|
||||
case "let-signal":
|
||||
case "signal":
|
||||
case "fn-signal":
|
||||
case "=":
|
||||
case "|":
|
||||
case "!":
|
||||
|
|
@ -303,6 +310,73 @@ function expr(cursor: Cursor): Expr {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function signalExpr(cursor: Cursor): SignalExpr {
|
||||
const start = cursor.currentLocation();
|
||||
const token = signalExprStartToken(cursor);
|
||||
switch (token.tag) {
|
||||
case "EOF":
|
||||
throw {
|
||||
tag: "UnexpectedToken",
|
||||
expected: "SignalExpression",
|
||||
span: token.span
|
||||
} as ParseError;
|
||||
case "signal_read":
|
||||
return SignalExpr.read(token.name, token.span);
|
||||
// case "function_name":
|
||||
// TODO: when components are ready
|
||||
// // e.g. my_func(arg1, arg2)
|
||||
// // parse a `,` delimiter sequence of expr
|
||||
// // need to consume )
|
||||
// if (!tryConsume(cursor, char('('))) {
|
||||
// throw {
|
||||
// tag: "ExpectedFunctionCallStart",
|
||||
// span: cursor.makeSpan(cursor.currentLocation())
|
||||
// } as ParseError;
|
||||
// }
|
||||
// const args = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_CLOSE_PAREN, expr);
|
||||
// return Expr.call(token.name, args, cursor.makeSpan(start));
|
||||
case "keyword":
|
||||
switch (token.kw) {
|
||||
case "let-signal":
|
||||
// TODO:
|
||||
// // let { p0 = e0, p1 = e2 . body }
|
||||
// if (!tryConsume(cursor, char('{'))) {
|
||||
// throw {
|
||||
// tag: "ExpectedLetBlockOpen",
|
||||
// span: cursor.makeSpan(cursor.currentLocation())
|
||||
// } as ParseError;
|
||||
// }
|
||||
// const bindings = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_DOT, productPatternBinding);
|
||||
// const body = expr(cursor);
|
||||
|
||||
// if (!tryConsume(cursor, TERMINATOR_CLOSE_BRACE)) {
|
||||
// throw {
|
||||
// tag: "ExpectedLetBlockClose",
|
||||
// span: cursor.makeSpan(cursor.currentLocation())
|
||||
// } as ParseError;
|
||||
// }
|
||||
// return Expr.let_(bindings, body, cursor.makeSpan(start));
|
||||
return 0 as any;
|
||||
case "let":
|
||||
case "fn":
|
||||
case "match":
|
||||
case "apply":
|
||||
case "signal":
|
||||
case "fn-signal":
|
||||
case "=":
|
||||
case "|":
|
||||
case "!":
|
||||
case ":":
|
||||
// These keywords CANNOT start a signal-expression.
|
||||
throw {
|
||||
tag: "ExpectedSignalExpression",
|
||||
span: token.span
|
||||
} as ParseError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function matchBranch(cursor: Cursor): MatchBranch {
|
||||
// p . body
|
||||
const start = cursor.currentLocation();
|
||||
|
|
@ -453,7 +527,7 @@ function recordPatternField(cursor: Cursor): FieldPattern {
|
|||
}
|
||||
|
||||
|
||||
export function parse(source: SourceText): Result<Expr, ParseError> {
|
||||
export function parseExpr(source: SourceText): Result<Expr, ParseError> {
|
||||
const cursor = new Cursor(source);
|
||||
|
||||
try {
|
||||
|
|
@ -475,6 +549,28 @@ export function parse(source: SourceText): Result<Expr, ParseError> {
|
|||
}
|
||||
}
|
||||
|
||||
export function parseSignalExpr(source: SourceText): Result<SignalExpr, ParseError> {
|
||||
const cursor = new Cursor(source);
|
||||
|
||||
try {
|
||||
skipWhitespaceAndComments(cursor);
|
||||
const expression = signalExpr(cursor);
|
||||
|
||||
if (!cursor.eof()) {
|
||||
return Result.error({
|
||||
tag: "UnexpectedToken",
|
||||
expected: "EndOfFile",
|
||||
span: cursor.makeSpan(cursor.currentLocation())
|
||||
} as ParseError);
|
||||
}
|
||||
|
||||
return Result.ok(expression);
|
||||
} catch (e) {
|
||||
// TODO: This is a bit sketchy. We maybe forced to have "checked" Exceptions for `ParseError` by wrapping it in something that has a proper tag.
|
||||
return Result.error(e as ParseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function functionParameters(cursor: Cursor): ProductPattern[] {
|
||||
const parameters = delimitedTerminalSequence(cursor, DELIMITER_COMMA, undefined, productPattern);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const DELIMITER_CHARS = ["(", ")", "{", "}", ".", ",", "@", "$", "#", '"', "\\"]
|
|||
export type Delimiter = typeof DELIMITER_CHARS[number];
|
||||
const DELIMITER_SET: Set<CodePoint> = new Set(DELIMITER_CHARS.map(c => char(c)));
|
||||
|
||||
const KEYWORD_LIST = ["let" , "fn" , "match" , "apply" , "=" , "|" , "!", ":"] as const;
|
||||
const KEYWORD_LIST = ["let" , "fn" , "match" , "apply", "let-signal", "signal", "fn-signal" , "=" , "|" , "!", ":"] as const;
|
||||
export type Keyword = typeof KEYWORD_LIST[number];
|
||||
const KEYWORD_SET: Set<string> = new Set(KEYWORD_LIST);
|
||||
|
||||
|
|
@ -62,6 +62,7 @@ export type IdentifierKind =
|
|||
| "field_name"
|
||||
| "tag_construction"
|
||||
| "function_call"
|
||||
| "signal_read"
|
||||
| "pattern_binding";
|
||||
|
||||
export type IdentifierErrorReason =
|
||||
|
|
@ -90,6 +91,15 @@ export type PatternStartToken =
|
|||
// TODO: ger rid of EOF
|
||||
| { tag: "EOF", span: Span };
|
||||
|
||||
export type SignalExprStartToken =
|
||||
// TODO: when we have parametrized signal-expressions
|
||||
// TODO: consider naming it `component` or `parametrized_signal_name`
|
||||
// | { tag: "function_name", name: string, span: Span }
|
||||
| { tag: "signal_read", name: string, span: Span }
|
||||
| { tag: "keyword", kw: Keyword, span: Span }
|
||||
// TODO: ger rid of EOF
|
||||
| { tag: "EOF", span: Span }
|
||||
|
||||
// === Identifier Scanners ===
|
||||
|
||||
// Returns the raw string.
|
||||
|
|
@ -236,6 +246,34 @@ export function exprStart(cursor: Cursor): ExprStartToken {
|
|||
}
|
||||
}
|
||||
|
||||
export function signalExprStart(cursor: Cursor): SignalExprStartToken {
|
||||
const start = cursor.currentLocation();
|
||||
if (cursor.eof()) {
|
||||
return { tag: "EOF", span: cursor.makeSpan(start) };
|
||||
}
|
||||
|
||||
const c = cursor.peek()!;
|
||||
|
||||
// === variable use ===
|
||||
if (c === char('@')) {
|
||||
cursor.next();
|
||||
const { name } = identifierScanner(cursor, 'signal_read');
|
||||
return { tag: "signal_read", name, span: cursor.makeSpan(start) };
|
||||
}
|
||||
|
||||
// === keywords & identifiers ===
|
||||
// Fallthrough: it must be a keyword or a function call
|
||||
const result = identifierOrKeywordScanner(cursor, 'function_call');
|
||||
switch (result.tag) {
|
||||
case "keyword":
|
||||
return result;
|
||||
case "identifier":
|
||||
// TODO: when we have parametrized signal-expressions
|
||||
// return { tag: "function_name", name: result.name, span: result.span };
|
||||
return 0 as any;
|
||||
}
|
||||
}
|
||||
|
||||
export function patternStart(cursor: Cursor): PatternStartToken {
|
||||
const start = cursor.currentLocation();
|
||||
|
||||
|
|
@ -300,6 +338,10 @@ export function isNextTokenExprStart(cursor: Cursor): boolean {
|
|||
case "apply":
|
||||
case ":":
|
||||
return true;
|
||||
case "let-signal":
|
||||
case "signal":
|
||||
case "fn-signal":
|
||||
case "=":
|
||||
case "=":
|
||||
case "|":
|
||||
case "!":
|
||||
|
|
@ -333,10 +375,13 @@ export function isNextTokenProductPatternStart(cursor: Cursor): boolean {
|
|||
switch (token.kw) {
|
||||
case ":":
|
||||
return true;
|
||||
case "let-signal":
|
||||
case "let":
|
||||
case "fn":
|
||||
case "match":
|
||||
case "apply":
|
||||
case "signal":
|
||||
case "fn-signal":
|
||||
case "=":
|
||||
case "|":
|
||||
case "!":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue