Make basic Signal Digith work
This commit is contained in:
parent
bf5eb54932
commit
c0198d419f
15 changed files with 464 additions and 73 deletions
|
|
@ -88,6 +88,7 @@ export namespace Expr {
|
||||||
export namespace SignalExpr {
|
export namespace SignalExpr {
|
||||||
export const read = (name: SignalName, span: Span): SignalExpr => ({ tag: "read", name, span });
|
export const read = (name: SignalName, span: Span): SignalExpr => ({ tag: "read", name, span });
|
||||||
export const signalBinding = (pattern: ProductPattern, expr: SignalExpr, span: Span): SignalExprBinding => ({ pattern, expr, span });
|
export const signalBinding = (pattern: ProductPattern, expr: SignalExpr, span: Span): SignalExprBinding => ({ pattern, expr, span });
|
||||||
|
export const let_ = (bindings: SignalExprBinding[], body: Expr, span: Span): SignalExpr => ({ tag: "let", bindings, body, span });
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace ProductPattern {
|
export namespace ProductPattern {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Cursor } from './cursor';
|
||||||
import { ExprScanError, exprStart, ExprStartToken, IdentifierKind, identifierScanner, isNextTokenExprStart, isNextTokenProductPatternStart, patternStart, PatternStartToken, signalExprStart, SignalExprStartToken, 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 { char, CodePoint, SourceText, Span } from './source_text';
|
||||||
import { Result } from '../result';
|
import { Result } from '../result';
|
||||||
import { Expr, ExprBinding, FieldAssignment, FieldPattern, FunctionName, MatchBranch, Pattern, ProductPattern, SignalExpr } from '../expr';
|
import { Expr, ExprBinding, FieldAssignment, FieldPattern, FunctionName, MatchBranch, Pattern, ProductPattern, SignalExpr, SignalExprBinding } from '../expr';
|
||||||
|
|
||||||
// CONVENTION: Every parser is responsible to consume whitespace/comments at the end.
|
// 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.
|
// Every parser is not responsible for cleaning up whitespace/comments at the start - only the final `parse` that's exposed to the public.
|
||||||
|
|
@ -29,6 +29,8 @@ export type ParseError =
|
||||||
| { tag: "ExpectedRecordOpen", span: Span } // Expected '(' after ':'
|
| { tag: "ExpectedRecordOpen", span: Span } // Expected '(' after ':'
|
||||||
| { tag: "ExpectedLetBlockOpen", span: Span } // Expected '{' after 'let'
|
| { tag: "ExpectedLetBlockOpen", span: Span } // Expected '{' after 'let'
|
||||||
| { tag: "ExpectedLetBlockClose", span: Span } // Expected '}' at end of 'let' expression
|
| { tag: "ExpectedLetBlockClose", span: Span } // Expected '}' at end of 'let' expression
|
||||||
|
| { tag: "ExpectedLetSignalBlockOpen", span: Span } // Expected '{' after `let-signal`
|
||||||
|
| { tag: "ExpectedLetSignalBlockClose", span: Span } // Expected '}' at end of 'let-signal' expression
|
||||||
| { tag: "ExpectedMatchBlockOpen", span: Span } // Expected '{' after 'match'
|
| { tag: "ExpectedMatchBlockOpen", span: Span } // Expected '{' after 'match'
|
||||||
| { tag: "ExpectedMatchBlockClose", span: Span } // Expected '}' at end of 'match' expression
|
| { tag: "ExpectedMatchBlockClose", span: Span } // Expected '}' at end of 'match' expression
|
||||||
| { tag: "ExpectedLambdaBlockOpen", span: Span } // Expected '{' after `fn`
|
| { tag: "ExpectedLambdaBlockOpen", span: Span } // Expected '{' after `fn`
|
||||||
|
|
@ -37,7 +39,7 @@ export type ParseError =
|
||||||
| { tag: "ExpectedApplySeparator", span: Span } // Expected '!' inside 'apply'
|
| { tag: "ExpectedApplySeparator", span: Span } // Expected '!' inside 'apply'
|
||||||
| { tag: "UnexpectedTagPattern", span: Span } // Found #tag where product pattern expected
|
| { tag: "UnexpectedTagPattern", span: Span } // Found #tag where product pattern expected
|
||||||
| { tag: "ExpectedPattern", span: Span } // EOF or invalid start of pattern
|
| { tag: "ExpectedPattern", span: Span } // EOF or invalid start of pattern
|
||||||
| { tag: "ExpectedRecordPatternOpen", span: Span } // Expected '(' at start of record pattern
|
| { tag: "ExpectedRecordPatternOpen", span: Span } // Expected ':(' at start of record pattern
|
||||||
| { tag: "ExpectedRecordField", span: Span }; // Expected identifier in record pattern
|
| { tag: "ExpectedRecordField", span: Span }; // Expected identifier in record pattern
|
||||||
|
|
||||||
// TODO: Delete?
|
// TODO: Delete?
|
||||||
|
|
@ -50,6 +52,8 @@ export type Expectation =
|
||||||
| "ExpectedRecordOpen"
|
| "ExpectedRecordOpen"
|
||||||
| "ExpectedLetBlockOpen"
|
| "ExpectedLetBlockOpen"
|
||||||
| "ExpectedLetBlockClose"
|
| "ExpectedLetBlockClose"
|
||||||
|
| "ExpectedLetSignalBlockOpen"
|
||||||
|
| "ExpectedLetSignalBlockClose"
|
||||||
| "ExpectedMatchBlockOpen"
|
| "ExpectedMatchBlockOpen"
|
||||||
| "ExpectedMatchBlockClose"
|
| "ExpectedMatchBlockClose"
|
||||||
| "ExpectedApplyStart"
|
| "ExpectedApplyStart"
|
||||||
|
|
@ -314,6 +318,7 @@ function expr(cursor: Cursor): Expr {
|
||||||
function signalExpr(cursor: Cursor): SignalExpr {
|
function signalExpr(cursor: Cursor): SignalExpr {
|
||||||
const start = cursor.currentLocation();
|
const start = cursor.currentLocation();
|
||||||
const token = signalExprStartToken(cursor);
|
const token = signalExprStartToken(cursor);
|
||||||
|
|
||||||
switch (token.tag) {
|
switch (token.tag) {
|
||||||
case "EOF":
|
case "EOF":
|
||||||
throw {
|
throw {
|
||||||
|
|
@ -323,41 +328,27 @@ function signalExpr(cursor: Cursor): SignalExpr {
|
||||||
} as ParseError;
|
} as ParseError;
|
||||||
case "signal_read":
|
case "signal_read":
|
||||||
return SignalExpr.read(token.name, token.span);
|
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":
|
case "keyword":
|
||||||
switch (token.kw) {
|
switch (token.kw) {
|
||||||
case "let-signal":
|
case "let-signal":
|
||||||
// TODO:
|
// let { x := sig-expr, y := sig-expr . normal-expr }
|
||||||
// // let { p0 = e0, p1 = e2 . body }
|
// TODO: Decide if to introduce new keyword `:=` or just reuse `=`?
|
||||||
// if (!tryConsume(cursor, char('{'))) {
|
if (!tryConsume(cursor, char('{'))) {
|
||||||
// throw {
|
throw {
|
||||||
// tag: "ExpectedLetBlockOpen",
|
tag: "ExpectedLetSignalBlockOpen",
|
||||||
// span: cursor.makeSpan(cursor.currentLocation())
|
span: cursor.makeSpan(cursor.currentLocation())
|
||||||
// } as ParseError;
|
} as ParseError;
|
||||||
// }
|
}
|
||||||
// const bindings = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_DOT, productPatternBinding);
|
const bindings = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_DOT, productPatternSignalBinding);
|
||||||
// const body = expr(cursor);
|
const body = expr(cursor);
|
||||||
|
|
||||||
// if (!tryConsume(cursor, TERMINATOR_CLOSE_BRACE)) {
|
if (!tryConsume(cursor, TERMINATOR_CLOSE_BRACE)) {
|
||||||
// throw {
|
throw {
|
||||||
// tag: "ExpectedLetBlockClose",
|
tag: "ExpectedLetSignalBlockClose",
|
||||||
// span: cursor.makeSpan(cursor.currentLocation())
|
span: cursor.makeSpan(cursor.currentLocation())
|
||||||
// } as ParseError;
|
} as ParseError;
|
||||||
// }
|
}
|
||||||
// return Expr.let_(bindings, body, cursor.makeSpan(start));
|
return SignalExpr.let_(bindings, body, cursor.makeSpan(start));
|
||||||
return 0 as any;
|
|
||||||
case "let":
|
case "let":
|
||||||
case "fn":
|
case "fn":
|
||||||
case "match":
|
case "match":
|
||||||
|
|
@ -415,6 +406,20 @@ function productPatternBinding(cursor: Cursor): ExprBinding {
|
||||||
return Expr.exprBinding(pattern, e, cursor.makeSpan(start));
|
return Expr.exprBinding(pattern, e, cursor.makeSpan(start));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function productPatternSignalBinding(cursor: Cursor): SignalExprBinding {
|
||||||
|
const start = cursor.currentLocation();
|
||||||
|
const pattern = productPattern(cursor);
|
||||||
|
|
||||||
|
if (!tryConsume(cursor, char('='))) {
|
||||||
|
throw {
|
||||||
|
tag: "ExpectedPatternBindingSymbol",
|
||||||
|
span: cursor.makeSpan(cursor.currentLocation())
|
||||||
|
} as ParseError;
|
||||||
|
}
|
||||||
|
const e = signalExpr(cursor);
|
||||||
|
return SignalExpr.signalBinding(pattern, e, cursor.makeSpan(start));
|
||||||
|
}
|
||||||
|
|
||||||
function fieldAssignment(cursor: Cursor): FieldAssignment {
|
function fieldAssignment(cursor: Cursor): FieldAssignment {
|
||||||
const start = cursor.currentLocation();
|
const start = cursor.currentLocation();
|
||||||
// `f = e`
|
// `f = e`
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export type ExprScanError =
|
||||||
| NumberError
|
| NumberError
|
||||||
| StringError
|
| StringError
|
||||||
| { tag: "InvalidIdentifier", text: string, kind: IdentifierKind, reason: IdentifierErrorReason, span: Span }
|
| { tag: "InvalidIdentifier", text: string, kind: IdentifierKind, reason: IdentifierErrorReason, span: Span }
|
||||||
|
| { tag: "UnexpectedIdentifier", identifier: string, span: Span }
|
||||||
|
|
||||||
// What kind of identifier were we trying to parse?
|
// What kind of identifier were we trying to parse?
|
||||||
export type IdentifierKind =
|
export type IdentifierKind =
|
||||||
|
|
@ -268,9 +269,11 @@ export function signalExprStart(cursor: Cursor): SignalExprStartToken {
|
||||||
case "keyword":
|
case "keyword":
|
||||||
return result;
|
return result;
|
||||||
case "identifier":
|
case "identifier":
|
||||||
// TODO: when we have parametrized signal-expressions
|
throw ({
|
||||||
// return { tag: "function_name", name: result.name, span: result.span };
|
tag: "UnexpectedIdentifier",
|
||||||
return 0 as any;
|
identifier: result.name,
|
||||||
|
span: result.span,
|
||||||
|
} as ExprScanError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,9 +352,6 @@ export function isNextTokenExprStart(cursor: Cursor): boolean {
|
||||||
|
|
||||||
case "EOF":
|
case "EOF":
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -386,7 +386,8 @@ export function isNextTokenProductPatternStart(cursor: Cursor): boolean {
|
||||||
case "!":
|
case "!":
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
default:
|
case "tag":
|
||||||
|
case "EOF":
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,14 @@ export namespace Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Signals ===
|
// === Signals ===
|
||||||
|
export function getSignal(program: Program, name: FunctionName): Result<SignalDefinition> {
|
||||||
|
const sigDef = program.signal_definitions.get(name);
|
||||||
|
if (sigDef === undefined) {
|
||||||
|
return Result.error({ tag: "SignalNotFound", name });
|
||||||
|
}
|
||||||
|
return Result.ok(sigDef);
|
||||||
|
}
|
||||||
|
|
||||||
export type CreateSignal = {
|
export type CreateSignal = {
|
||||||
name: SignalName,
|
name: SignalName,
|
||||||
body: SignalExpr,
|
body: SignalExpr,
|
||||||
|
|
@ -386,7 +394,7 @@ export namespace Program {
|
||||||
export function registerSignal(
|
export function registerSignal(
|
||||||
program: Program,
|
program: Program,
|
||||||
{ name, body, raw_body }: CreateSignal
|
{ name, body, raw_body }: CreateSignal
|
||||||
): Result<void> {
|
): Result<SignalName> {
|
||||||
if (program.signal_definitions.has(name)) {
|
if (program.signal_definitions.has(name)) {
|
||||||
return Result.error({ tag: "DuplicateSignalName", name });
|
return Result.error({ tag: "DuplicateSignalName", name });
|
||||||
}
|
}
|
||||||
|
|
@ -410,7 +418,7 @@ export namespace Program {
|
||||||
// TODO: Note that this doesn't actually evaluate the signal and doesn't insert it into signal-runtime.
|
// TODO: Note that this doesn't actually evaluate the signal and doesn't insert it into signal-runtime.
|
||||||
// For that we will use `get_or_create_signal`
|
// For that we will use `get_or_create_signal`
|
||||||
|
|
||||||
return Result.ok(undefined);
|
return Result.ok(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UpdateSignal = {
|
export type UpdateSignal = {
|
||||||
|
|
@ -547,7 +555,7 @@ export function updateSignal(
|
||||||
// TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
// TODO: MAY THROW RuntimeError. Should probably switch to `eval_start` - and extend the `Program.Error` with runtime errors.
|
||||||
const newValue = eval_expr(program, Env.nil(), body);
|
const newValue = eval_expr(program, Env.nil(), body);
|
||||||
|
|
||||||
// 2. Find the existing runtime signal
|
// Find the existing runtime signal
|
||||||
if (def.signalId === undefined) {
|
if (def.signalId === undefined) {
|
||||||
// This should theoretically not happen for cells since we initialize them eagerly,
|
// This should theoretically not happen for cells since we initialize them eagerly,
|
||||||
// but good to be safe.
|
// but good to be safe.
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,6 @@ import { DisplayLineViews } from "./LineView";
|
||||||
|
|
||||||
export function formatErrorMesage(err: ParseError): string {
|
export function formatErrorMesage(err: ParseError): string {
|
||||||
switch (err.tag) {
|
switch (err.tag) {
|
||||||
case "UnexpectedToken":
|
|
||||||
return `Unexpected token. Expected: ${err.expected}`;
|
|
||||||
|
|
||||||
case "UnexpectedTokenWhileParsingSequence":
|
|
||||||
return `Unexpected token in sequence. Expected delimiter ${formatChar(err.expectedDelimiter)} or terminator ${formatChar(err.expectedTerminator)}, but found ${formatChar(err.received)}.`;
|
|
||||||
|
|
||||||
case "UnexpectedCharacter":
|
case "UnexpectedCharacter":
|
||||||
return `Unexpected character: ${formatChar(err.char)}`;
|
return `Unexpected character: ${formatChar(err.char)}`;
|
||||||
|
|
||||||
|
|
@ -20,13 +14,12 @@ export function formatErrorMesage(err: ParseError): string {
|
||||||
return "Expected a number here.";
|
return "Expected a number here.";
|
||||||
|
|
||||||
case "InvalidNumber":
|
case "InvalidNumber":
|
||||||
return err.reason === "NotFinite"
|
switch (err.reason) {
|
||||||
? "Number is too large or invalid."
|
case "NotFinite":
|
||||||
: "Invalid number format (missing fractional digits?).";
|
return "Number is too large or invalid.";
|
||||||
|
case "MissingFractionalDigits":
|
||||||
case "InvalidIdentifier":
|
return "Invalid number format (missing fractional digits?).";
|
||||||
// Handle nested reasons if needed, e.g. "Keyword 'let' cannot be used as an identifier"
|
}
|
||||||
return `Invalid identifier '${err.text}': ${err.reason.tag}`;
|
|
||||||
|
|
||||||
case "InvalidEscape":
|
case "InvalidEscape":
|
||||||
switch (err.reason.tag) {
|
switch (err.reason.tag) {
|
||||||
|
|
@ -37,8 +30,56 @@ export function formatErrorMesage(err: ParseError): string {
|
||||||
case "UnicodeOverflow": return `Unicode code point ${err.reason.value.toString(16)} is out of bounds.`;
|
case "UnicodeOverflow": return `Unicode code point ${err.reason.value.toString(16)} is out of bounds.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "InvalidIdentifier": {
|
||||||
|
let identifierKind = "";
|
||||||
|
switch (err.kind) {
|
||||||
|
case "variable_use":
|
||||||
|
identifierKind = "variable name";
|
||||||
|
break;
|
||||||
|
case "field_name":
|
||||||
|
identifierKind = "field name ";
|
||||||
|
break;
|
||||||
|
case "tag_construction":
|
||||||
|
identifierKind = "tag";
|
||||||
|
break;
|
||||||
|
case "function_call":
|
||||||
|
identifierKind = "function name";
|
||||||
|
break;
|
||||||
|
case "signal_read":
|
||||||
|
identifierKind = "signal name";
|
||||||
|
break;
|
||||||
|
case "pattern_binding":
|
||||||
|
identifierKind = "pattern variable";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reason = "";
|
||||||
|
switch (err.reason.tag) {
|
||||||
|
case "Empty":
|
||||||
|
reason = "It's empty";
|
||||||
|
break;
|
||||||
|
case "StartsWithDigit":
|
||||||
|
reason = "Can't start with a digit"
|
||||||
|
break;
|
||||||
|
case "IsKeyword":
|
||||||
|
reason = "I'ts a keyword";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return `Invalid ${identifierKind} '${err.text}' ${reason}.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "UnexpectedIdentifier":
|
||||||
|
return `Unexpected identifier encountered '${err.identifier}'`;
|
||||||
|
|
||||||
|
case "UnexpectedToken":
|
||||||
|
return `Unexpected token. Expected: ${err.expected}`;
|
||||||
|
|
||||||
|
case "UnexpectedTokenWhileParsingSequence":
|
||||||
|
return `Unexpected token in sequence. Expected delimiter ${formatChar(err.expectedDelimiter)} or terminator ${formatChar(err.expectedTerminator)}, but found ${formatChar(err.received)}.`;
|
||||||
|
|
||||||
// Context specific errors
|
// Context specific errors
|
||||||
case "ExpectedExpression": return "Expected an expression here.";
|
case "ExpectedExpression": return "Expected an expression here.";
|
||||||
|
case "ExpectedSignalExpression": return "Expected a signal expression here.";
|
||||||
case "ExpectedFieldAssignmentSymbol": return "Expected '=' for field assignment.";
|
case "ExpectedFieldAssignmentSymbol": return "Expected '=' for field assignment.";
|
||||||
case "ExpectedPatternAssignmentSymbol": return "Expected '=' for pattern assignment.";
|
case "ExpectedPatternAssignmentSymbol": return "Expected '=' for pattern assignment.";
|
||||||
case "ExpectedPatternBindingSymbol": return "Expected '.' for pattern binding.";
|
case "ExpectedPatternBindingSymbol": return "Expected '.' for pattern binding.";
|
||||||
|
|
@ -46,6 +87,8 @@ export function formatErrorMesage(err: ParseError): string {
|
||||||
case "ExpectedRecordOpen": return "Expected '(' to start record.";
|
case "ExpectedRecordOpen": return "Expected '(' to start record.";
|
||||||
case "ExpectedLetBlockOpen": return "Expected '{' to start let-block.";
|
case "ExpectedLetBlockOpen": return "Expected '{' to start let-block.";
|
||||||
case "ExpectedLetBlockClose": return "Expected '}' to close let-block.";
|
case "ExpectedLetBlockClose": return "Expected '}' to close let-block.";
|
||||||
|
case "ExpectedLetSignalBlockOpen": return "Expected '{' to start let-signal-block.";
|
||||||
|
case "ExpectedLetSignalBlockClose": return "Expected '}' to close let-signal-block.";
|
||||||
case "ExpectedMatchBlockOpen": return "Expected '{' to start match-block.";
|
case "ExpectedMatchBlockOpen": return "Expected '{' to start match-block.";
|
||||||
case "ExpectedMatchBlockClose": return "Expected '}' to close match-block.";
|
case "ExpectedMatchBlockClose": return "Expected '}' to close match-block.";
|
||||||
case "ExpectedLambdaBlockOpen": return "Expected '{' to start lambda body.";
|
case "ExpectedLambdaBlockOpen": return "Expected '{' to start lambda body.";
|
||||||
|
|
@ -54,11 +97,8 @@ export function formatErrorMesage(err: ParseError): string {
|
||||||
case "ExpectedApplySeparator": return "Expected '!' inside 'apply'.";
|
case "ExpectedApplySeparator": return "Expected '!' inside 'apply'.";
|
||||||
case "UnexpectedTagPattern": return "Unexpected tag pattern (expected product pattern).";
|
case "UnexpectedTagPattern": return "Unexpected tag pattern (expected product pattern).";
|
||||||
case "ExpectedPattern": return "Expected a pattern here.";
|
case "ExpectedPattern": return "Expected a pattern here.";
|
||||||
case "ExpectedRecordPatternOpen": return "Expected '(' for record pattern.";
|
case "ExpectedRecordPatternOpen": return "Expected a ':(' at start of record pattern here.";
|
||||||
case "ExpectedRecordField": return "Expected a field name in record pattern.";
|
case "ExpectedRecordField": return "Expected a field name in record pattern.";
|
||||||
|
|
||||||
default:
|
|
||||||
return `Unknown error: ${(err as any).tag}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { spawnNewFunctionDraftDigith } from "src/ui/Scrowl/scrowlStore";
|
import { spawnNewFunctionDraftDigith, spawnNewSignalDraftDigith } from "src/ui/Scrowl/scrowlStore";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
@ -21,8 +21,8 @@ export function Controls(props: Props) {
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="outline secondary"
|
class="outline secondary"
|
||||||
|
onClick={spawnNewSignalDraftDigith}
|
||||||
style={{ padding: "2px 8px", "font-size": "0.8rem", width: "auto" }}
|
style={{ padding: "2px 8px", "font-size": "0.8rem", width: "auto" }}
|
||||||
disabled
|
|
||||||
>
|
>
|
||||||
+signal
|
+signal
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { CodeEditor } from "src/ui/Component/CodeEditor";
|
||||||
import { sourceText } from "src/lang/parser/source_text";
|
import { sourceText } from "src/lang/parser/source_text";
|
||||||
import { Program } from "src/lang/program";
|
import { Program } from "src/lang/program";
|
||||||
import { V, Validation, letValidate } from "src/ui/validation";
|
import { V, Validation, letValidate } from "src/ui/validation";
|
||||||
import { validateExprRaw, validateParamsRaw } from "./Helpers";
|
import { validateExprRaw, validateParamsRaw } from "src/ui/validation/helpers";
|
||||||
import { updateDigith } from "src/ui/Scrowl/scrowlStore";
|
import { updateDigith } from "src/ui/Scrowl/scrowlStore";
|
||||||
import { DigithError } from "src/ui/Digith/DigithError";
|
import { DigithError } from "src/ui/Digith/DigithError";
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ export function FunctionDigith(props: { function: Digith.Function }) {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="grid">
|
<div>
|
||||||
<label>
|
<label>
|
||||||
Name
|
Name
|
||||||
<input
|
<input
|
||||||
|
|
@ -131,7 +131,6 @@ export function FunctionDigith(props: { function: Digith.Function }) {
|
||||||
<button
|
<button
|
||||||
onClick={handleRedefine}
|
onClick={handleRedefine}
|
||||||
disabled={!isDirty()}
|
disabled={!isDirty()}
|
||||||
class={isDirty() ? "" : "secondary outline"}
|
|
||||||
>
|
>
|
||||||
Redefine
|
Redefine
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,10 @@ import { CodeEditor } from "src/ui/Component/CodeEditor";
|
||||||
import { sourceText } from "src/lang/parser/source_text";
|
import { sourceText } from "src/lang/parser/source_text";
|
||||||
import { Program } from "src/lang/program";
|
import { Program } from "src/lang/program";
|
||||||
import { V, Validation, letValidate } from "src/ui/validation";
|
import { V, Validation, letValidate } from "src/ui/validation";
|
||||||
import { validateExprRaw, validateNameRaw, validateParamsRaw } from "./Helpers";
|
import { validateExprRaw, validateNameRaw, validateParamsRaw } from "src/ui/validation/helpers";
|
||||||
import { spawnFunctionDigith } from "src/ui/Scrowl/scrowlStore";
|
import { spawnFunctionDigith } from "src/ui/Scrowl/scrowlStore";
|
||||||
import { DigithError } from "src/ui/Digith/DigithError";
|
import { DigithError } from "src/ui/Digith/DigithError";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type Input = {
|
type Input = {
|
||||||
raw_name: string,
|
raw_name: string,
|
||||||
raw_params: string,
|
raw_params: string,
|
||||||
|
|
|
||||||
107
src/ui/Digith/Signal/NewSignalDraftDigith.tsx
Normal file
107
src/ui/Digith/Signal/NewSignalDraftDigith.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { createSignal } from "solid-js";
|
||||||
|
import { Digith } from "src/ui/Digith";
|
||||||
|
import { useProgram } from "src/ui/ProgramProvider";
|
||||||
|
import { CodeEditor } from "src/ui/Component/CodeEditor";
|
||||||
|
import { sourceText } from "src/lang/parser/source_text";
|
||||||
|
import { Program } from "src/lang/program";
|
||||||
|
import { V, Validation, letValidate } from "src/ui/validation";
|
||||||
|
import { validateNameRaw, validateSignalExprRaw } from "src/ui/validation/helpers";
|
||||||
|
import { DigithError } from "src/ui/Digith/DigithError";
|
||||||
|
import { spawnSignalDigith } from "src/ui/Scrowl/scrowlStore";
|
||||||
|
|
||||||
|
type Input = {
|
||||||
|
raw_name: string,
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validator: Validation<Input, Program.CreateSignal, DigithError> = letValidate(
|
||||||
|
(input) =>({
|
||||||
|
name: V.elseErr(validateNameRaw(input.raw_name), err =>({
|
||||||
|
payload: { tag: "Parse", err, src: sourceText(input.raw_name) },
|
||||||
|
ids: ["name"],
|
||||||
|
tags: ["footer"],
|
||||||
|
config: { title: "Signal Name", display: "flat" },
|
||||||
|
})),
|
||||||
|
body: V.elseErr(validateSignalExprRaw(input.raw_body), err => ({
|
||||||
|
payload: { tag: "Parse", err, src: sourceText(input.raw_body) },
|
||||||
|
ids: ["body"],
|
||||||
|
tags: ["footer"],
|
||||||
|
config: { title: "Signal Body", display: "flat" },
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
(fields, input) => {
|
||||||
|
const createSignal: Program.CreateSignal = {
|
||||||
|
name: fields.name,
|
||||||
|
body: fields.body,
|
||||||
|
raw_body: input.raw_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
return V.ok(createSignal);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export function NewSignalDraftDigith(props: { draft: Digith.NewSignalDraft }) {
|
||||||
|
const program = useProgram();
|
||||||
|
|
||||||
|
const [name, setName] = createSignal(props.draft.raw_name);
|
||||||
|
const [body, setBody] = createSignal(props.draft.raw_body);
|
||||||
|
|
||||||
|
const [errors, setErrors] = createSignal<DigithError[]>([]);
|
||||||
|
|
||||||
|
function handleCommit() {
|
||||||
|
setErrors([]);
|
||||||
|
const validRes = validator({ raw_name: name(), raw_body: body() });
|
||||||
|
if (validRes.tag === "errors") {
|
||||||
|
setErrors(validRes.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const createSignal = validRes.value;
|
||||||
|
|
||||||
|
const programRes = Program.registerSignal(program, createSignal);
|
||||||
|
if (programRes.tag === "error") {
|
||||||
|
setErrors([{
|
||||||
|
payload: { tag: "Program", err: programRes.error },
|
||||||
|
ids: ["program"],
|
||||||
|
tags: ["footer"],
|
||||||
|
config: { title: "Registration Failed" },
|
||||||
|
}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const signalName = programRes.value;
|
||||||
|
|
||||||
|
spawnSignalDigith(program, signalName, props.draft.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article>
|
||||||
|
<header><strong>Signal (Draft)</strong></header>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<label>
|
||||||
|
Name
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="my_sig"
|
||||||
|
value={name()}
|
||||||
|
onInput={(e) => setName(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>Body</label>
|
||||||
|
<CodeEditor
|
||||||
|
value={body()}
|
||||||
|
onUpdate={setBody}
|
||||||
|
onRun={handleCommit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<button class="primary" onClick={handleCommit}>Commit</button>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div style={{ "margin-top": "1rem" }}>
|
||||||
|
<DigithError.ByTag errors={errors()} tag="footer" />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
154
src/ui/Digith/Signal/SignalDigith.tsx
Normal file
154
src/ui/Digith/Signal/SignalDigith.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
import { createEffect, createSignal, onCleanup, Show } from "solid-js";
|
||||||
|
import { Digith } from "src/ui/Digith";
|
||||||
|
import { useProgram } from "src/ui/ProgramProvider";
|
||||||
|
import { CodeEditor } from "src/ui/Component/CodeEditor";
|
||||||
|
import { sourceText } from "src/lang/parser/source_text";
|
||||||
|
import { Program } from "src/lang/program";
|
||||||
|
import { V, Validation, letValidate } from "src/ui/validation";
|
||||||
|
import { validateSignalExprRaw } from "src/ui/validation/helpers";
|
||||||
|
import { updateDigith } from "src/ui/Scrowl/scrowlStore";
|
||||||
|
import { DigithError } from "src/ui/Digith/DigithError";
|
||||||
|
import { Value } from "src/lang/eval/value";
|
||||||
|
import { Val } from "src/ui/Component/Value";
|
||||||
|
|
||||||
|
type Input = {
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validator: Validation<Input, Program.UpdateSignal, DigithError> = letValidate(
|
||||||
|
(input) => ({
|
||||||
|
body: V.elseErr(validateSignalExprRaw(input.raw_body), err => ({
|
||||||
|
payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body) },
|
||||||
|
ids: ["body"],
|
||||||
|
tags: ["footer"],
|
||||||
|
config: { title: "Signal Body" },
|
||||||
|
})),
|
||||||
|
}),
|
||||||
|
(fields, input) => {
|
||||||
|
return V.ok({
|
||||||
|
body: fields.body,
|
||||||
|
raw_body: input.raw_body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export function SignalDigith(props: { signal: Digith.Signal }) {
|
||||||
|
const program = useProgram();
|
||||||
|
|
||||||
|
const [value, setValue] = createSignal<Value | null>(null);
|
||||||
|
|
||||||
|
const [body, setBody] = createSignal(props.signal.raw_body);
|
||||||
|
|
||||||
|
const [errors, setErrors] = createSignal<DigithError[]>([]);
|
||||||
|
|
||||||
|
const isDirty = () =>
|
||||||
|
body() !== props.signal.raw_body;
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
// TODO: Improve runtime error view
|
||||||
|
try {
|
||||||
|
const signal = Program.get_or_create_signal(program, props.signal.name);
|
||||||
|
|
||||||
|
// TODO: Not sure about this one. Setting a signal in `setValue` is discouraged.
|
||||||
|
setTimeout(() => setValue(signal.read()), 0);
|
||||||
|
// TODO: Handle cancelation...
|
||||||
|
signal.subscribe((newValue) => {
|
||||||
|
setValue(newValue);
|
||||||
|
});
|
||||||
|
onCleanup(() => {
|
||||||
|
// TODO:
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: setErrors... but how? Shouldn't this be independent of `errors`?
|
||||||
|
console.log("Failed to link Signal: ", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleRedefine() {
|
||||||
|
setErrors([]);
|
||||||
|
|
||||||
|
const validRes = validator({ raw_body: body() });
|
||||||
|
if (validRes.tag === "errors") {
|
||||||
|
setErrors(validRes.errors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const updateData = validRes.value;
|
||||||
|
|
||||||
|
const progRes = Program.updateSignal(program, props.signal.name, {
|
||||||
|
body: updateData.body,
|
||||||
|
raw_body: updateData.raw_body
|
||||||
|
});
|
||||||
|
|
||||||
|
if (progRes.tag === "error") {
|
||||||
|
setErrors([{
|
||||||
|
payload: { tag: "Program", err: progRes.error },
|
||||||
|
ids: ["program"],
|
||||||
|
tags: ["footer"],
|
||||||
|
config: { title: "Update Failed" },
|
||||||
|
}]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reloading the digith
|
||||||
|
updateDigith(props.signal.id, {
|
||||||
|
...props.signal,
|
||||||
|
raw_body: updateData.raw_body
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
return (
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<strong>Signal</strong>
|
||||||
|
|
||||||
|
{/* Dirty Indicator / Status */}
|
||||||
|
<div>
|
||||||
|
<Show when={isDirty()} fallback={<span style={{color: "var(--pico-muted-color)"}}>Synced</span>}>
|
||||||
|
<span style={{color: "var(--pico-primary)"}}>● Unsaved Changes</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Name
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={props.signal.name}
|
||||||
|
disabled
|
||||||
|
style={{ opacity: 0.7, cursor: "not-allowed" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label>Body</label>
|
||||||
|
<CodeEditor
|
||||||
|
value={body()}
|
||||||
|
onUpdate={setBody}
|
||||||
|
onRun={handleRedefine}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<footer style={{ display: "flex", "align-items": "center", gap: "1rem" }}>
|
||||||
|
<button
|
||||||
|
onClick={handleRedefine}
|
||||||
|
disabled={!isDirty()}
|
||||||
|
>
|
||||||
|
Redefine
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<div style={{ "margin-top": "1rem" }}>
|
||||||
|
<DigithError.ByTag errors={errors()} tag="footer" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Show when={ value() } fallback={<div>Initializing...</div>}>
|
||||||
|
{(value) => (
|
||||||
|
<div>
|
||||||
|
<label>Current Value</label>
|
||||||
|
<Val value={ value() } />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
import { FunctionName } from "src/lang/expr";
|
import { FunctionName, SignalName } from "src/lang/expr";
|
||||||
import { DigithId } from "src/ui/Scrowl/scrowlStore";
|
import { DigithId } from "src/ui/Scrowl/scrowlStore";
|
||||||
|
|
||||||
export type Digith =
|
export type Digith =
|
||||||
| Digith.Repl
|
| Digith.Repl
|
||||||
| Digith.NewFunctionDraft
|
| Digith.NewFunctionDraft
|
||||||
| Digith.Function
|
| Digith.Function
|
||||||
|
| Digith.NewSignalDraft
|
||||||
|
| Digith.Signal
|
||||||
|
|
||||||
|
|
||||||
export namespace Digith {
|
export namespace Digith {
|
||||||
export type Repl = {
|
export type Repl = {
|
||||||
|
|
@ -28,5 +31,18 @@ export namespace Digith {
|
||||||
raw_body: string,
|
raw_body: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NewSignalDraft = {
|
||||||
|
id: DigithId,
|
||||||
|
tag: "new-signal-draft",
|
||||||
|
raw_name: string,
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Signal = {
|
||||||
|
id: DigithId,
|
||||||
|
tag: "signal",
|
||||||
|
name: SignalName,
|
||||||
|
raw_body: string,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import { clearFocus, DigithId, scrowl } from "./scrowlStore";
|
||||||
import { NewFunctionDraftDigith } from "src/ui/Digith/Function/NewFunctionDraftDigith";
|
import { NewFunctionDraftDigith } from "src/ui/Digith/Function/NewFunctionDraftDigith";
|
||||||
import { FunctionDigith } from "src/ui/Digith/Function/FunctionDigith";
|
import { FunctionDigith } from "src/ui/Digith/Function/FunctionDigith";
|
||||||
import { Digith } from "src/ui/Digith";
|
import { Digith } from "src/ui/Digith";
|
||||||
|
import { NewSignalDraftDigith } from "../Digith/Signal/NewSignalDraftDigith";
|
||||||
|
import { SignalDigith } from "../Digith/Signal/SignalDigith";
|
||||||
|
|
||||||
// WTF are these names?
|
// WTF are these names?
|
||||||
// Scrowl
|
// Scrowl
|
||||||
|
|
@ -41,6 +43,14 @@ export function Scrowl() {
|
||||||
<Match when={digith.tag === 'fn'}>
|
<Match when={digith.tag === 'fn'}>
|
||||||
<FunctionDigith function={digith as Digith.Function} />
|
<FunctionDigith function={digith as Digith.Function} />
|
||||||
</Match>
|
</Match>
|
||||||
|
|
||||||
|
<Match when={digith.tag === 'new-signal-draft'}>
|
||||||
|
<NewSignalDraftDigith draft={digith as Digith.NewSignalDraft} />
|
||||||
|
</Match>
|
||||||
|
|
||||||
|
<Match when={digith.tag === 'signal'}>
|
||||||
|
<SignalDigith signal={digith as Digith.Signal} />
|
||||||
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
</DigithWrapper>
|
</DigithWrapper>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { createStore } from "solid-js/store";
|
import { createStore } from "solid-js/store";
|
||||||
import { Digith } from "src/ui/Digith";
|
import { Digith } from "src/ui/Digith";
|
||||||
import { Program } from "src/lang/program";
|
import { Program } from "src/lang/program";
|
||||||
import { FunctionName } from "src/lang/expr";
|
import { FunctionName, SignalName } from "src/lang/expr";
|
||||||
|
|
||||||
export type DigithId = number;
|
export type DigithId = number;
|
||||||
|
|
||||||
|
|
@ -112,3 +112,49 @@ export function spawnFunctionDigith(program: Program, name: FunctionName, target
|
||||||
return Scrowl.Result.ok(newDigith);
|
return Scrowl.Result.ok(newDigith);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function spawnNewSignalDraftDigith() {
|
||||||
|
const id = generateId();
|
||||||
|
|
||||||
|
const newDraft: Digith = {
|
||||||
|
id,
|
||||||
|
tag: 'new-signal-draft',
|
||||||
|
raw_name: '',
|
||||||
|
raw_body: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
requestFocus(id);
|
||||||
|
setScrowl("digiths", (prev) => [newDraft, ...prev]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function spawnSignalDigith(program: Program, name: SignalName, targetId?: DigithId): Scrowl.Result<Digith.Signal> {
|
||||||
|
const lookupRes = Program.getSignal(program, name);
|
||||||
|
if (lookupRes.tag === "error") {
|
||||||
|
return Scrowl.Result.error(lookupRes.error);
|
||||||
|
}
|
||||||
|
const sigDef = lookupRes.value;
|
||||||
|
|
||||||
|
// TODO: Maybe consider representing some read-only Digith for primitive (it would just display the name, it wouldn't have code).
|
||||||
|
if (sigDef.tag === "primitive") {
|
||||||
|
return Scrowl.Result.error({ tag: "CannotEditPrimitiveSignal", name });
|
||||||
|
}
|
||||||
|
const userDef = sigDef.def;
|
||||||
|
const id = targetId ?? generateId();
|
||||||
|
|
||||||
|
const newDigith: Digith.Signal = {
|
||||||
|
id: id,
|
||||||
|
tag: "signal",
|
||||||
|
|
||||||
|
name: userDef.name,
|
||||||
|
raw_body: userDef.raw_body,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (targetId === undefined) {
|
||||||
|
prependDigith(newDigith);
|
||||||
|
} else {
|
||||||
|
// Swap with old signal draft.
|
||||||
|
updateDigith(targetId, newDigith);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scrowl.Result.ok(newDigith);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ParseError, parseExpr, parseFunctionName, parseFunctionParameters } from "src/lang/parser/parser";
|
import { ParseError, parseExpr, parseFunctionName, parseFunctionParameters, parseSignalExpr } from "src/lang/parser/parser";
|
||||||
import { sourceText } from "src/lang/parser/source_text";
|
import { sourceText } from "src/lang/parser/source_text";
|
||||||
import { Expr, FunctionName, ProductPattern } from "src/lang/expr";
|
import { Expr, FunctionName, ProductPattern, SignalExpr } from "src/lang/expr";
|
||||||
import { V } from "src/ui/validation";
|
import { V } from "./";
|
||||||
|
|
||||||
// === Parser wrappers ===
|
// === Parser wrappers ===
|
||||||
export function validateNameRaw(input: string): V<FunctionName, ParseError> {
|
export function validateNameRaw(input: string): V<FunctionName, ParseError> {
|
||||||
|
|
@ -22,3 +22,9 @@ export function validateExprRaw(input: string): V<Expr, ParseError> {
|
||||||
return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]);
|
return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function validateSignalExprRaw(input: string): V<SignalExpr, ParseError> {
|
||||||
|
const src = sourceText(input);
|
||||||
|
const res = parseSignalExpr(src);
|
||||||
|
return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]);
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue