{(digith) => (
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
)}
@@ -52,3 +50,31 @@ export function Scrowl(props: Props) {
);
}
+// For focusing on a Digith
+// This works basically in two ways:
+// - We have the list of digiths, and each time any of them is mounted, there's `attemptScroll` check done on all of them.
+// - Whenever `scrowl.focusTarget` changes, it reactively broadcasts the change to each of the digith,
+// and each digith asks itself, "Am I focus-target?".
+function DigithWrapper(props: { id: DigithId, children: any }) {
+ let ref: HTMLDivElement | undefined;
+
+ // The Logic: Run this whenever 'scrowl.focusTarget' changes OR when this component mounts
+ const attemptScroll = () => {
+ if (scrowl.focusTarget === props.id && ref) {
+ ref.scrollIntoView({ behavior: "smooth", block: "start" });
+ setTimeout(clearFocus, 0); // This sets asynchronously the focus-target to null.
+ }
+ };
+
+ onMount(attemptScroll);
+ createEffect(attemptScroll);
+
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/src/ui/Sidebar.tsx b/src/ui/Sidebar.tsx
new file mode 100644
index 0000000..996f852
--- /dev/null
+++ b/src/ui/Sidebar.tsx
@@ -0,0 +1,19 @@
+import { Controls } from "./Controls";
+import { ProgramMeta } from "./ProgramMeta";
+
+export function Sidebar() {
+ return (
+
+ );
+}
diff --git a/src/ui/scrowlStore.ts b/src/ui/scrowlStore.ts
index 9f9d8d6..4c7756d 100644
--- a/src/ui/scrowlStore.ts
+++ b/src/ui/scrowlStore.ts
@@ -1,21 +1,114 @@
import { createStore } from "solid-js/store";
import { Digith } from "./Digith";
+import { Program } from "src/lang/program";
+import { FunctionName } from "src/lang/expr";
+
+export type DigithId = number;
export type Scrowl = {
- digiths: Digith[];
+ digiths: Digith[],
+ nextId: DigithId,
+ focusTarget: DigithId | null, // 99.99999% of time this is null, but it flickers briefly - and on that flicker focus can happen.
+}
+
+export namespace Scrowl {
+ export type Error =
+ | Program.Error
+
+ export type Result
=
+ | { tag: "ok", value: T }
+ | { tag: "error", error: Error }
+
+ export namespace Result {
+ export function ok(value: T): Result { return { tag: "ok", value } }
+ export function error(error: Error): Result { return { tag: "error", error } }
+ }
+
}
export const [scrowl, setScrowl] = createStore({
- digiths: [{ tag: 'repl' }]
+ digiths: [{ tag: 'repl', id: 0 }],
+ nextId: 1,
+ focusTarget: null,
});
-export function spawnFunctionDraft() {
+function prependDigith(newDigith: Digith) {
+ setScrowl("digiths", (prev) => [newDigith, ...prev]);
+}
+
+export function updateDigith(targetId: DigithId, newDigith: Digith) {
+ setScrowl("digiths", (items) =>
+ items.map((item) => item.id === targetId ? newDigith : item)
+ );
+}
+
+export function closeDigith(targetId: DigithId) {
+ setScrowl("digiths", (prev) => prev.filter((d) => d.id !== targetId));
+}
+
+export function closeAllDigiths() {
+ setScrowl("digiths", []);
+}
+
+export function requestFocus(id: DigithId) {
+ setScrowl("focusTarget", id);
+}
+
+export function clearFocus() {
+ setScrowl("focusTarget", null);
+}
+
+function generateId(): DigithId {
+ const id = scrowl.nextId;
+ setScrowl("nextId", (prev) => prev + 1);
+ return id;
+}
+
+export function spawnNewFunctionDraftDigith() {
+ const id = generateId();
+
const newDraft: Digith = {
+ id,
tag: 'new-fn-draft',
raw_name: '',
raw_parameters: '',
raw_body: '',
};
+
+ requestFocus(id);
setScrowl("digiths", (prev) => [newDraft, ...prev]);
};
+export function spawnFunctionDigith(program: Program, name: FunctionName, targetId?: DigithId): Scrowl.Result {
+ const lookupRes = Program.getFunction(program, name);
+ if (lookupRes.tag === "error") {
+ return Scrowl.Result.error(lookupRes.error);
+ }
+ const fnDef = lookupRes.value;
+
+ // TODO: Maybe consider representing some read-only Digith for primitive (it would just display the name, it wouldn't have code).
+ if (fnDef.tag === "primitive") {
+ return Scrowl.Result.error({ tag: "CannotEditPrimitiveFunction", name });
+ }
+ const userDef = fnDef.def;
+ const id = targetId ?? generateId();
+
+ const newDigith: Digith.Function = {
+ id: id,
+ tag: "fn",
+
+ name: userDef.name,
+ raw_parameters: userDef.raw_parameters,
+ raw_body: userDef.raw_body,
+ };
+
+ if (targetId === undefined) {
+ prependDigith(newDigith);
+ } else {
+ // Swap with old function draft.
+ updateDigith(targetId, newDigith);
+ }
+
+ return Scrowl.Result.ok(newDigith);
+}
+
From e8411060299f11c7ba2a16c096d081399f4435a9 Mon Sep 17 00:00:00 2001
From: Yura Dupyn <2153100+omedusyo@users.noreply.github.com>
Date: Sun, 15 Feb 2026 19:11:44 +0100
Subject: [PATCH 02/10] Improve errors in digiths
---
src/lang/program.ts | 13 +-
src/ui/DigithError.tsx | 134 +++++++++++++++++++++
src/ui/Function/FunctionDigith.tsx | 95 ++++++---------
src/ui/Function/Helpers.tsx | 27 -----
src/ui/Function/NewFunctionDraftDigith.tsx | 100 ++++++---------
5 files changed, 213 insertions(+), 156 deletions(-)
create mode 100644 src/ui/DigithError.tsx
diff --git a/src/lang/program.ts b/src/lang/program.ts
index d05b1c9..524dc2d 100644
--- a/src/lang/program.ts
+++ b/src/lang/program.ts
@@ -294,10 +294,18 @@ export namespace Program {
raw_body: string,
}
+ export function getFunction(program: Program, name: FunctionName): Result {
+ const fn = program.function_definitions.get(name);
+ if (fn === undefined) {
+ return Result.error({ tag: "FunctionNotFound", name });
+ }
+ return Result.ok(fn);
+ }
+
export function registerFunction(
program: Program,
{ name, body, parameters, raw_parameters, raw_body }: CreateFunction
- ): Result {
+ ): Result {
if (program.function_definitions.has(name)) {
return Result.error({ tag: "DuplicateFunctionName", name });
}
@@ -317,13 +325,12 @@ export namespace Program {
program.function_definitions.set(name, { tag: "user", def: newFunction });
program.function_definition_order.push(name);
- return Result.ok(undefined);
+ return Result.ok(name);
}
export type UpdateFunction = {
parameters: ProductPattern[],
body: Expr,
- raw_name: string,
raw_parameters: string,
raw_body: string,
}
diff --git a/src/ui/DigithError.tsx b/src/ui/DigithError.tsx
new file mode 100644
index 0000000..a395925
--- /dev/null
+++ b/src/ui/DigithError.tsx
@@ -0,0 +1,134 @@
+import { For, Match, Show, Switch } from "solid-js";
+import { ParseError } from "src/lang/parser/parser";
+import { SourceText } from "src/lang/parser/source_text";
+import { ShowParseError } from "./ParseError";
+import { Program } from "src/lang/program";
+
+export type DigithError = {
+ payload: DigithError.Payload,
+ ids: DigithError.Id[],
+ tags: DigithError.Tag[],
+ config: DigithError.Config,
+}
+
+export namespace DigithError {
+ export type Payload =
+ | { tag: "Parse", err: ParseError, src: SourceText }
+ | { tag: "Program", err: Program.Error };
+
+ export type Id = string;
+ export type Tag = string;
+
+ export type Config = {
+ title?: string,
+ display?: "box" | "flat",
+ }
+
+ function findById(errors: DigithError[], id: Id): DigithError | undefined {
+ return errors.find((e) => e.ids.includes(id));
+ }
+
+ function allWithTag(errors: DigithError[], tag: Tag): DigithError[] {
+ return errors.filter((e) => e.tags.includes(tag));
+ }
+
+ export function All(props: { errors: DigithError[] }) {
+ return (
+
+
+ {(error) => }
+
+
+ );
+ }
+
+ export function ById(props: { errors: DigithError[], id: Id }) {
+ const error = () => findById(props.errors, props.id);
+ return (
+
+ {(e) => }
+
+ );
+ }
+
+ export function ByTag(props: { errors: DigithError[], tag: Tag }) {
+ const matched = () => allWithTag(props.errors, props.tag);
+ return (
+ 0}>
+
+
+ );
+ }
+
+ function Single(props: { error: DigithError }) {
+ const display = () => props.error.config.display ?? "box";
+ return (
+
+
+
+
+
+
+ {props.error.config.title}
+
+
+
+
+
+
+
+
+
+ {props.error.config.title}:
+
+
+
+
+
+
+ );
+ }
+
+ function PayloadView(props: { payload: Payload }) {
+ return (
+
+ ) : undefined}
+ >
+ {(err) => }
+
+
+ ) : undefined}
+ >
+ {(err) => }
+
+
+ );
+ }
+
+}
+
+export function ProgramErrorDisplay(props: { error: Program.Error }) {
+ const message = () => {
+ switch (props.error.tag) {
+ case "DuplicateFunctionName":
+ return `A function named '${props.error.name}' already exists.`;
+ case "PrimitiveFunctionAlreadyExists":
+ return `Cannot overwrite the primitive function '${props.error.name}'.`;
+ // TODO: handle other cases
+ default:
+ return `Runtime Error: ${props.error.tag}`;
+ }
+ };
+
+ return (
+
+
+ Registration Failed
+
+ {message()}
+
+ );
+}
+
diff --git a/src/ui/Function/FunctionDigith.tsx b/src/ui/Function/FunctionDigith.tsx
index c31d49d..b5bbbbd 100644
--- a/src/ui/Function/FunctionDigith.tsx
+++ b/src/ui/Function/FunctionDigith.tsx
@@ -1,23 +1,33 @@
-import { createSignal, For, Match, Show, Switch } from "solid-js";
+import { createSignal, Show } from "solid-js";
import { Digith } from "../Digith";
import { useProgram } from "../ProgramProvider";
import { CodeEditor } from "../CodeEditor";
-import { ParseError } from "src/lang/parser/parser";
-import { sourceText, SourceText } from "src/lang/parser/source_text";
-import { ShowParseError } from "../ParseError";
+import { sourceText } from "src/lang/parser/source_text";
import { Program } from "src/lang/program";
-import { V, letValidate } from "../validation";
-import { ProgramErrorDisplay, validateExprRaw, validateParamsRaw } from "./Helpers";
+import { V, Validation, letValidate } from "../validation";
+import { validateExprRaw, validateParamsRaw } from "./Helpers";
import { updateDigith } from "../scrowlStore";
+import { DigithError } from "../DigithError";
-type UpdateFnError =
- | { tag: "Parse", field: "params" | "body", err: ParseError, src: SourceText }
- | { tag: "Program", err: Program.Error }
+type Input = {
+ raw_params: string,
+ raw_body: string,
+}
-const validator = letValidate(
- (input: { raw_params: string, raw_body: string }) => ({
- parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({ tag: "Parse", field: "params", err, src: sourceText(input.raw_params) })),
- body: V.elseErr(validateExprRaw(input.raw_body), err => ({ tag: "Parse", field: "body", err, src: sourceText(input.raw_body) })),
+const validator: Validation = letValidate(
+ (input) => ({
+ parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({
+ payload: { tag: "Parse", field: "params", err, src: sourceText(input.raw_params) },
+ ids: ["params"],
+ tags: ["footer"],
+ config: { title: "Parameters" },
+ })),
+ body: V.elseErr(validateExprRaw(input.raw_body), err => ({
+ payload: { tag: "Parse", field: "body", err, src: sourceText(input.raw_body) },
+ ids: ["body"],
+ tags: ["footer"],
+ config: { title: "Function Body" },
+ })),
}),
(fields, input) => {
return V.ok({
@@ -29,48 +39,6 @@ const validator = letValidate(
}
);
-const fieldLabels: Record = {
- params: "Parameters",
- body: "Function Body"
-};
-
-function SingleErrorDisplay(props: { error: UpdateFnError }) {
- return (
-
-
- ) : undefined}
- >
- {(err) => (
-
-
- {fieldLabels[err().field]} Error
-
-
-
- )}
-
-
- ) : undefined}
- >
- {(err) => ( )}
-
-
-
- );
-}
-
-function ErrorListDisplay(props: { errors: UpdateFnError[] }) {
- return (
-
-
- {(error) => }
-
-
- );
-}
-
// TODO: What about renaming?
export function FunctionDigith(props: { function: Digith.Function }) {
const program = useProgram();
@@ -78,7 +46,7 @@ export function FunctionDigith(props: { function: Digith.Function }) {
const [params, setParams] = createSignal(props.function.raw_parameters);
const [body, setBody] = createSignal(props.function.raw_body);
- const [errors, setErrors] = createSignal([]);
+ const [errors, setErrors] = createSignal([]);
const isDirty = () =>
params() !== props.function.raw_parameters ||
@@ -89,7 +57,7 @@ export function FunctionDigith(props: { function: Digith.Function }) {
const validRes = validator({ raw_params: params(), raw_body: body() });
if (validRes.tag === "errors") {
- setErrors(validRes.errors as UpdateFnError[]);
+ setErrors(validRes.errors);
return;
}
const updateData = validRes.value;
@@ -102,7 +70,12 @@ export function FunctionDigith(props: { function: Digith.Function }) {
});
if (progRes.tag === "error") {
- setErrors([{ tag: "Program", err: progRes.error }]);
+ setErrors([{
+ payload: { tag: "Program", err: progRes.error },
+ ids: ["program"],
+ tags: ["footer"],
+ config: { title: "Update Failed" },
+ }]);
return;
}
@@ -165,9 +138,9 @@ export function FunctionDigith(props: { function: Digith.Function }) {
- 0}>
-
-
+
+
+
);
diff --git a/src/ui/Function/Helpers.tsx b/src/ui/Function/Helpers.tsx
index bd0a41a..f2aa286 100644
--- a/src/ui/Function/Helpers.tsx
+++ b/src/ui/Function/Helpers.tsx
@@ -1,7 +1,6 @@
import { ParseError, parseExpr, parseFunctionName, parseFunctionParameters } from "src/lang/parser/parser";
import { sourceText } from "src/lang/parser/source_text";
import { Expr, FunctionName, ProductPattern } from "src/lang/expr";
-import { Program } from "src/lang/program";
import { V } from "../validation";
// === Parser wrappers ===
@@ -23,29 +22,3 @@ export function validateExprRaw(input: string): V {
return res.tag === "ok" ? V.ok(res.value) : V.errors([res.error]);
};
-
-// === Displaying Errors ===
-
-// TODO: Move this into more appropriate place
-export function ProgramErrorDisplay(props: { error: Program.Error }) {
- const message = () => {
- switch (props.error.tag) {
- case "DuplicateFunctionName":
- return `A function named '${props.error.name}' already exists.`;
- case "PrimitiveFunctionAlreadyExists":
- return `Cannot overwrite the primitive function '${props.error.name}'.`;
- // TODO: handle other cases
- default:
- return `Runtime Error: ${props.error.tag}`;
- }
- };
-
- return (
-
-
- Registration Failed
-
- {message()}
-
- );
-}
diff --git a/src/ui/Function/NewFunctionDraftDigith.tsx b/src/ui/Function/NewFunctionDraftDigith.tsx
index b9cf0e9..59868ac 100644
--- a/src/ui/Function/NewFunctionDraftDigith.tsx
+++ b/src/ui/Function/NewFunctionDraftDigith.tsx
@@ -1,25 +1,13 @@
-import { createSignal, For, Match, Show, Switch } from "solid-js";
+import { createSignal } from "solid-js";
import { Digith } from "../Digith";
import { useProgram } from "../ProgramProvider";
import { CodeEditor } from "../CodeEditor";
-import { ParseError } from "src/lang/parser/parser";
-import { sourceText, SourceText } from "src/lang/parser/source_text";
-import { ShowParseError } from "../ParseError";
+import { sourceText } from "src/lang/parser/source_text";
import { Program } from "src/lang/program";
import { V, Validation, letValidate } from "../validation";
-import { ProgramErrorDisplay, validateExprRaw, validateNameRaw, validateParamsRaw } from "./Helpers";
+import { validateExprRaw, validateNameRaw, validateParamsRaw } from "./Helpers";
import { spawnFunctionDigith } from "../scrowlStore";
-
-
-type NewFnError =
- | { tag: "Parse", field: "name" | "params" | "body", err: ParseError, src: SourceText }
- | { tag: "Program", err: Program.Error };
-
-const fieldLabels: Record = {
- name: "Function Name",
- params: "Parameters",
- body: "Function Body"
-};
+import { DigithError } from "../DigithError";
type Input = {
raw_name: string,
@@ -27,11 +15,26 @@ type Input = {
raw_body: string,
}
-const validator: Validation = letValidate(
- (input: Input) =>({
- name: V.elseErr(validateNameRaw(input.raw_name), err => ({ tag: "Parse", field: "name", err, src: sourceText(input.raw_name) })),
- parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({ tag: "Parse", field: "params", err, src: sourceText(input.raw_params) })),
- body: V.elseErr(validateExprRaw(input.raw_body), err => ({ tag: "Parse", field: "body", err, src: sourceText(input.raw_body) })),
+const validator: Validation = 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: "Function Name", display: "flat" },
+ })),
+ parameters: V.elseErr(validateParamsRaw(input.raw_params), err => ({
+ payload: { tag: "Parse", err, src: sourceText(input.raw_params) },
+ ids: ["params"],
+ tags: ["footer"],
+ config: { title: "Parameters", display: "flat" },
+ })),
+ body: V.elseErr(validateExprRaw(input.raw_body), err => ({
+ payload: { tag: "Parse", err, src: sourceText(input.raw_body) },
+ ids: ["body"],
+ tags: ["footer"],
+ config: { title: "Function Body", display: "flat" },
+ })),
}),
(fields, input) => {
const createFunction: Program.CreateFunction = {
@@ -44,44 +47,6 @@ const validator: Validation = letVali
return V.ok(createFunction);
})
-export function SingleErrorDisplay(props: { error: NewFnError }) {
- return (
-
-
- ) : undefined}
- >
- {(err) => (
-
-
- {fieldLabels[err().field]} Error
-
-
-
- )}
-
-
- ) : undefined}
- >
- {(err) => ( )}
-
-
-
- );
-}
-
-function ErrorListDisplay(props: { errors: NewFnError[] }) {
- return (
-
-
- {(error) => }
-
-
- );
-}
-
-
export function NewFunctionDraftDigith(props: { draft: Digith.NewFunctionDraft }) {
const program = useProgram();
@@ -89,20 +54,25 @@ export function NewFunctionDraftDigith(props: { draft: Digith.NewFunctionDraft }
const [params, setParams] = createSignal(props.draft.raw_parameters);
const [body, setBody] = createSignal(props.draft.raw_body);
- const [errors, setErrors] = createSignal([]);
+ const [errors, setErrors] = createSignal([]);
function handleCommit() {
setErrors([]);
const validRes = validator({ raw_name: name(), raw_params: params(), raw_body: body() });
if (validRes.tag === "errors") {
- setErrors(validRes.errors as NewFnError[]);
+ setErrors(validRes.errors);
return;
}
const createFunction = validRes.value;
const programRes = Program.registerFunction(program, createFunction);
if (programRes.tag === "error") {
- setErrors([{ tag: "Program", err: programRes.error }]);
+ setErrors([{
+ payload: { tag: "Program", err: programRes.error },
+ ids: ["program"],
+ tags: ["footer"],
+ config: { title: "Registration Failed" },
+ }]);
return;
}
const fnName = programRes.value;
@@ -146,9 +116,9 @@ export function NewFunctionDraftDigith(props: { draft: Digith.NewFunctionDraft }
- 0}>
-
-
+
+
+
);
}
From bf5eb54932ef9c20a251e9cdf9d1f89578eac91a Mon Sep 17 00:00:00 2001
From: Yura Dupyn <2153100+omedusyo@users.noreply.github.com>
Date: Sun, 15 Feb 2026 19:26:32 +0100
Subject: [PATCH 03/10] Reorganize ui
---
src/ui/{ => Component}/CodeEditor.tsx | 0
src/ui/{ => Component}/Expr.tsx | 2 +-
src/ui/{ => Component}/LineView.tsx | 0
src/ui/{ => Component}/ParseError.tsx | 0
src/ui/{ => Component}/Value.tsx | 2 +-
src/ui/{Controls.tsx => Controls/index.tsx} | 2 +-
src/ui/{ => Digith}/DigithError.tsx | 2 +-
src/ui/{ => Digith}/Function/FunctionDigith.tsx | 12 ++++++------
src/ui/{ => Digith}/Function/Helpers.tsx | 2 +-
.../Function/NewFunctionDraftDigith.tsx | 14 ++++++++------
src/ui/{ => Digith}/REPL.tsx | 8 ++++----
src/ui/{Digith.tsx => Digith/index.tsx} | 2 +-
src/ui/Function.tsx | 0
src/ui/FunctionList.tsx | 0
src/ui/{Scrowl.tsx => Scrowl/index.tsx} | 8 ++++----
src/ui/{ => Scrowl}/scrowlStore.ts | 2 +-
src/ui/Sidebar.tsx | 2 +-
src/ui/validation.ts | 1 +
18 files changed, 31 insertions(+), 28 deletions(-)
rename src/ui/{ => Component}/CodeEditor.tsx (100%)
rename src/ui/{ => Component}/Expr.tsx (98%)
rename src/ui/{ => Component}/LineView.tsx (100%)
rename src/ui/{ => Component}/ParseError.tsx (100%)
rename src/ui/{ => Component}/Value.tsx (96%)
rename src/ui/{Controls.tsx => Controls/index.tsx} (91%)
rename src/ui/{ => Digith}/DigithError.tsx (98%)
rename src/ui/{ => Digith}/Function/FunctionDigith.tsx (92%)
rename src/ui/{ => Digith}/Function/Helpers.tsx (95%)
rename src/ui/{ => Digith}/Function/NewFunctionDraftDigith.tsx (91%)
rename src/ui/{ => Digith}/REPL.tsx (92%)
rename src/ui/{Digith.tsx => Digith/index.tsx} (90%)
delete mode 100644 src/ui/Function.tsx
delete mode 100644 src/ui/FunctionList.tsx
rename src/ui/{Scrowl.tsx => Scrowl/index.tsx} (90%)
rename src/ui/{ => Scrowl}/scrowlStore.ts (98%)
diff --git a/src/ui/CodeEditor.tsx b/src/ui/Component/CodeEditor.tsx
similarity index 100%
rename from src/ui/CodeEditor.tsx
rename to src/ui/Component/CodeEditor.tsx
diff --git a/src/ui/Expr.tsx b/src/ui/Component/Expr.tsx
similarity index 98%
rename from src/ui/Expr.tsx
rename to src/ui/Component/Expr.tsx
index 427234f..afe2dae 100644
--- a/src/ui/Expr.tsx
+++ b/src/ui/Component/Expr.tsx
@@ -1,4 +1,4 @@
-import { Expr, FieldAssignment, FieldPattern, Literal, Pattern, ProductPattern } from "../lang/expr";
+import { Expr, FieldAssignment, FieldPattern, Literal, Pattern, ProductPattern } from "src/lang/expr";
export function Expression(prop: { expr: Expr }) {
return (
diff --git a/src/ui/LineView.tsx b/src/ui/Component/LineView.tsx
similarity index 100%
rename from src/ui/LineView.tsx
rename to src/ui/Component/LineView.tsx
diff --git a/src/ui/ParseError.tsx b/src/ui/Component/ParseError.tsx
similarity index 100%
rename from src/ui/ParseError.tsx
rename to src/ui/Component/ParseError.tsx
diff --git a/src/ui/Value.tsx b/src/ui/Component/Value.tsx
similarity index 96%
rename from src/ui/Value.tsx
rename to src/ui/Component/Value.tsx
index e41b350..08dec9c 100644
--- a/src/ui/Value.tsx
+++ b/src/ui/Component/Value.tsx
@@ -1,4 +1,4 @@
-import { Closure, Value, Env, EnvFrame } from '../lang/eval/value';
+import { Closure, Value, Env, EnvFrame } from 'src/lang/eval/value';
import { exprToString, productPatternToString } from './Expr';
export function Val(prop: { value: Value }) {
diff --git a/src/ui/Controls.tsx b/src/ui/Controls/index.tsx
similarity index 91%
rename from src/ui/Controls.tsx
rename to src/ui/Controls/index.tsx
index 97d5110..e8111ad 100644
--- a/src/ui/Controls.tsx
+++ b/src/ui/Controls/index.tsx
@@ -1,4 +1,4 @@
-import { spawnNewFunctionDraftDigith } from "./scrowlStore";
+import { spawnNewFunctionDraftDigith } from "src/ui/Scrowl/scrowlStore";
type Props = {
// TODO
diff --git a/src/ui/DigithError.tsx b/src/ui/Digith/DigithError.tsx
similarity index 98%
rename from src/ui/DigithError.tsx
rename to src/ui/Digith/DigithError.tsx
index a395925..231cd4a 100644
--- a/src/ui/DigithError.tsx
+++ b/src/ui/Digith/DigithError.tsx
@@ -1,7 +1,7 @@
import { For, Match, Show, Switch } from "solid-js";
import { ParseError } from "src/lang/parser/parser";
import { SourceText } from "src/lang/parser/source_text";
-import { ShowParseError } from "./ParseError";
+import { ShowParseError } from 'src/ui/Component/ParseError';
import { Program } from "src/lang/program";
export type DigithError = {
diff --git a/src/ui/Function/FunctionDigith.tsx b/src/ui/Digith/Function/FunctionDigith.tsx
similarity index 92%
rename from src/ui/Function/FunctionDigith.tsx
rename to src/ui/Digith/Function/FunctionDigith.tsx
index b5bbbbd..22cb4d6 100644
--- a/src/ui/Function/FunctionDigith.tsx
+++ b/src/ui/Digith/Function/FunctionDigith.tsx
@@ -1,13 +1,13 @@
import { createSignal, Show } from "solid-js";
-import { Digith } from "../Digith";
-import { useProgram } from "../ProgramProvider";
-import { CodeEditor } from "../CodeEditor";
+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 "../validation";
+import { V, Validation, letValidate } from "src/ui/validation";
import { validateExprRaw, validateParamsRaw } from "./Helpers";
-import { updateDigith } from "../scrowlStore";
-import { DigithError } from "../DigithError";
+import { updateDigith } from "src/ui/Scrowl/scrowlStore";
+import { DigithError } from "src/ui/Digith/DigithError";
type Input = {
raw_params: string,
diff --git a/src/ui/Function/Helpers.tsx b/src/ui/Digith/Function/Helpers.tsx
similarity index 95%
rename from src/ui/Function/Helpers.tsx
rename to src/ui/Digith/Function/Helpers.tsx
index f2aa286..2df31b4 100644
--- a/src/ui/Function/Helpers.tsx
+++ b/src/ui/Digith/Function/Helpers.tsx
@@ -1,7 +1,7 @@
import { ParseError, parseExpr, parseFunctionName, parseFunctionParameters } from "src/lang/parser/parser";
import { sourceText } from "src/lang/parser/source_text";
import { Expr, FunctionName, ProductPattern } from "src/lang/expr";
-import { V } from "../validation";
+import { V } from "src/ui/validation";
// === Parser wrappers ===
export function validateNameRaw(input: string): V {
diff --git a/src/ui/Function/NewFunctionDraftDigith.tsx b/src/ui/Digith/Function/NewFunctionDraftDigith.tsx
similarity index 91%
rename from src/ui/Function/NewFunctionDraftDigith.tsx
rename to src/ui/Digith/Function/NewFunctionDraftDigith.tsx
index 59868ac..8c9bef6 100644
--- a/src/ui/Function/NewFunctionDraftDigith.tsx
+++ b/src/ui/Digith/Function/NewFunctionDraftDigith.tsx
@@ -1,13 +1,15 @@
import { createSignal } from "solid-js";
-import { Digith } from "../Digith";
-import { useProgram } from "../ProgramProvider";
-import { CodeEditor } from "../CodeEditor";
+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 "../validation";
+import { V, Validation, letValidate } from "src/ui/validation";
import { validateExprRaw, validateNameRaw, validateParamsRaw } from "./Helpers";
-import { spawnFunctionDigith } from "../scrowlStore";
-import { DigithError } from "../DigithError";
+import { spawnFunctionDigith } from "src/ui/Scrowl/scrowlStore";
+import { DigithError } from "src/ui/Digith/DigithError";
+
+
type Input = {
raw_name: string,
diff --git a/src/ui/REPL.tsx b/src/ui/Digith/REPL.tsx
similarity index 92%
rename from src/ui/REPL.tsx
rename to src/ui/Digith/REPL.tsx
index 451f84b..7dc9cd0 100644
--- a/src/ui/REPL.tsx
+++ b/src/ui/Digith/REPL.tsx
@@ -1,13 +1,13 @@
import { createSignal, Match, Switch } from 'solid-js';
-import { useProgram } from './ProgramProvider';
+import { useProgram } from 'src/ui/ProgramProvider';
import { eval_start } from 'src/lang/eval/evaluator';
import { Value } from 'src/lang/eval/value';
import { RuntimeError } from 'src/lang/eval/error';
import { SourceText, sourceText } from 'src/lang/parser/source_text';
import { ParseError, parseExpr } from 'src/lang/parser/parser';
-import { ShowParseError } from './ParseError';
-import { Val } from './Value';
-import { CodeEditor } from './CodeEditor';
+import { ShowParseError } from 'src/ui/Component/ParseError';
+import { Val } from 'src/ui/Component/Value';
+import { CodeEditor } from 'src/ui/Component/CodeEditor';
namespace ReplResult {
export type Idle =
diff --git a/src/ui/Digith.tsx b/src/ui/Digith/index.tsx
similarity index 90%
rename from src/ui/Digith.tsx
rename to src/ui/Digith/index.tsx
index 93b8ae5..db13e64 100644
--- a/src/ui/Digith.tsx
+++ b/src/ui/Digith/index.tsx
@@ -1,5 +1,5 @@
import { FunctionName } from "src/lang/expr";
-import { DigithId } from "./scrowlStore";
+import { DigithId } from "src/ui/Scrowl/scrowlStore";
export type Digith =
| Digith.Repl
diff --git a/src/ui/Function.tsx b/src/ui/Function.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/ui/FunctionList.tsx b/src/ui/FunctionList.tsx
deleted file mode 100644
index e69de29..0000000
diff --git a/src/ui/Scrowl.tsx b/src/ui/Scrowl/index.tsx
similarity index 90%
rename from src/ui/Scrowl.tsx
rename to src/ui/Scrowl/index.tsx
index 197389e..8ca1bc5 100644
--- a/src/ui/Scrowl.tsx
+++ b/src/ui/Scrowl/index.tsx
@@ -1,9 +1,9 @@
import { createEffect, For, Match, onMount, Switch } from "solid-js";
-import { ExprREPL } from "./REPL";
+import { ExprREPL } from "src/ui/Digith/REPL";
import { clearFocus, DigithId, scrowl } from "./scrowlStore";
-import { NewFunctionDraftDigith } from "./Function/NewFunctionDraftDigith";
-import { FunctionDigith } from "./Function/FunctionDigith";
-import { Digith } from "./Digith";
+import { NewFunctionDraftDigith } from "src/ui/Digith/Function/NewFunctionDraftDigith";
+import { FunctionDigith } from "src/ui/Digith/Function/FunctionDigith";
+import { Digith } from "src/ui/Digith";
// WTF are these names?
// Scrowl
diff --git a/src/ui/scrowlStore.ts b/src/ui/Scrowl/scrowlStore.ts
similarity index 98%
rename from src/ui/scrowlStore.ts
rename to src/ui/Scrowl/scrowlStore.ts
index 4c7756d..ba85929 100644
--- a/src/ui/scrowlStore.ts
+++ b/src/ui/Scrowl/scrowlStore.ts
@@ -1,5 +1,5 @@
import { createStore } from "solid-js/store";
-import { Digith } from "./Digith";
+import { Digith } from "src/ui/Digith";
import { Program } from "src/lang/program";
import { FunctionName } from "src/lang/expr";
diff --git a/src/ui/Sidebar.tsx b/src/ui/Sidebar.tsx
index 996f852..c835aff 100644
--- a/src/ui/Sidebar.tsx
+++ b/src/ui/Sidebar.tsx
@@ -1,4 +1,4 @@
-import { Controls } from "./Controls";
+import { Controls } from "./Controls/index";
import { ProgramMeta } from "./ProgramMeta";
export function Sidebar() {
diff --git a/src/ui/validation.ts b/src/ui/validation.ts
index 6cc54cd..8c2148d 100644
--- a/src/ui/validation.ts
+++ b/src/ui/validation.ts
@@ -98,3 +98,4 @@ export function letValidate, E, B>(
}
};
}
+
From c0198d419f03ba887717edaf132e8bd2aca60f2f Mon Sep 17 00:00:00 2001
From: Yura Dupyn <2153100+omedusyo@users.noreply.github.com>
Date: Mon, 16 Feb 2026 19:11:57 +0100
Subject: [PATCH 04/10] Make basic Signal Digith work
---
src/lang/expr.ts | 1 +
src/lang/parser/parser.ts | 71 ++++----
src/lang/parser/scanner.ts | 15 +-
src/lang/program.ts | 14 +-
src/ui/Component/ParseError.tsx | 74 +++++++--
src/ui/Controls/index.tsx | 4 +-
src/ui/Digith/Function/FunctionDigith.tsx | 5 +-
.../Function/NewFunctionDraftDigith.tsx | 4 +-
src/ui/Digith/Signal/NewSignalDraftDigith.tsx | 107 ++++++++++++
src/ui/Digith/Signal/SignalDigith.tsx | 154 ++++++++++++++++++
src/ui/Digith/index.tsx | 18 +-
src/ui/Scrowl/index.tsx | 10 ++
src/ui/Scrowl/scrowlStore.ts | 48 +++++-
.../Helpers.tsx => validation/helpers.ts} | 12 +-
src/ui/{validation.ts => validation/index.ts} | 0
15 files changed, 464 insertions(+), 73 deletions(-)
create mode 100644 src/ui/Digith/Signal/NewSignalDraftDigith.tsx
create mode 100644 src/ui/Digith/Signal/SignalDigith.tsx
rename src/ui/{Digith/Function/Helpers.tsx => validation/helpers.ts} (67%)
rename src/ui/{validation.ts => validation/index.ts} (100%)
diff --git a/src/lang/expr.ts b/src/lang/expr.ts
index f7cbe05..21ebbcd 100644
--- a/src/lang/expr.ts
+++ b/src/lang/expr.ts
@@ -88,6 +88,7 @@ export namespace Expr {
export namespace SignalExpr {
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 let_ = (bindings: SignalExprBinding[], body: Expr, span: Span): SignalExpr => ({ tag: "let", bindings, body, span });
}
export namespace ProductPattern {
diff --git a/src/lang/parser/parser.ts b/src/lang/parser/parser.ts
index 194bd11..f88003d 100644
--- a/src/lang/parser/parser.ts
+++ b/src/lang/parser/parser.ts
@@ -2,7 +2,7 @@ import { Cursor } from './cursor';
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, 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.
// 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: "ExpectedLetBlockOpen", span: Span } // Expected '{' after 'let'
| { 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: "ExpectedMatchBlockClose", span: Span } // Expected '}' at end of 'match' expression
| { tag: "ExpectedLambdaBlockOpen", span: Span } // Expected '{' after `fn`
@@ -37,7 +39,7 @@ export type ParseError =
| { tag: "ExpectedApplySeparator", span: Span } // Expected '!' inside 'apply'
| { tag: "UnexpectedTagPattern", span: Span } // Found #tag where product pattern expected
| { 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
// TODO: Delete?
@@ -50,6 +52,8 @@ export type Expectation =
| "ExpectedRecordOpen"
| "ExpectedLetBlockOpen"
| "ExpectedLetBlockClose"
+ | "ExpectedLetSignalBlockOpen"
+ | "ExpectedLetSignalBlockClose"
| "ExpectedMatchBlockOpen"
| "ExpectedMatchBlockClose"
| "ExpectedApplyStart"
@@ -314,6 +318,7 @@ function expr(cursor: Cursor): Expr {
function signalExpr(cursor: Cursor): SignalExpr {
const start = cursor.currentLocation();
const token = signalExprStartToken(cursor);
+
switch (token.tag) {
case "EOF":
throw {
@@ -323,41 +328,27 @@ function signalExpr(cursor: Cursor): SignalExpr {
} 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);
+ // let { x := sig-expr, y := sig-expr . normal-expr }
+ // TODO: Decide if to introduce new keyword `:=` or just reuse `=`?
+ if (!tryConsume(cursor, char('{'))) {
+ throw {
+ tag: "ExpectedLetSignalBlockOpen",
+ span: cursor.makeSpan(cursor.currentLocation())
+ } as ParseError;
+ }
+ const bindings = delimitedTerminalSequence(cursor, DELIMITER_COMMA, TERMINATOR_DOT, productPatternSignalBinding);
+ 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;
+ if (!tryConsume(cursor, TERMINATOR_CLOSE_BRACE)) {
+ throw {
+ tag: "ExpectedLetSignalBlockClose",
+ span: cursor.makeSpan(cursor.currentLocation())
+ } as ParseError;
+ }
+ return SignalExpr.let_(bindings, body, cursor.makeSpan(start));
case "let":
case "fn":
case "match":
@@ -415,6 +406,20 @@ function productPatternBinding(cursor: Cursor): ExprBinding {
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 {
const start = cursor.currentLocation();
// `f = e`
diff --git a/src/lang/parser/scanner.ts b/src/lang/parser/scanner.ts
index 2f6a372..6ad1154 100644
--- a/src/lang/parser/scanner.ts
+++ b/src/lang/parser/scanner.ts
@@ -55,6 +55,7 @@ export type ExprScanError =
| NumberError
| StringError
| { 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?
export type IdentifierKind =
@@ -268,9 +269,11 @@ export function signalExprStart(cursor: Cursor): SignalExprStartToken {
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;
+ throw ({
+ tag: "UnexpectedIdentifier",
+ identifier: result.name,
+ span: result.span,
+ } as ExprScanError);
}
}
@@ -349,9 +352,6 @@ export function isNextTokenExprStart(cursor: Cursor): boolean {
case "EOF":
return false;
-
- default:
- return false;
}
} catch (e) {
@@ -386,7 +386,8 @@ export function isNextTokenProductPatternStart(cursor: Cursor): boolean {
case "!":
return false;
}
- default:
+ case "tag":
+ case "EOF":
return false;
}
} catch (e) {
diff --git a/src/lang/program.ts b/src/lang/program.ts
index 524dc2d..49ee449 100644
--- a/src/lang/program.ts
+++ b/src/lang/program.ts
@@ -377,6 +377,14 @@ export namespace Program {
}
// === Signals ===
+ export function getSignal(program: Program, name: FunctionName): Result {
+ const sigDef = program.signal_definitions.get(name);
+ if (sigDef === undefined) {
+ return Result.error({ tag: "SignalNotFound", name });
+ }
+ return Result.ok(sigDef);
+ }
+
export type CreateSignal = {
name: SignalName,
body: SignalExpr,
@@ -386,7 +394,7 @@ export namespace Program {
export function registerSignal(
program: Program,
{ name, body, raw_body }: CreateSignal
- ): Result {
+ ): Result {
if (program.signal_definitions.has(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.
// For that we will use `get_or_create_signal`
- return Result.ok(undefined);
+ return Result.ok(name);
}
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.
const newValue = eval_expr(program, Env.nil(), body);
- // 2. Find the existing runtime signal
+ // Find the existing runtime signal
if (def.signalId === undefined) {
// This should theoretically not happen for cells since we initialize them eagerly,
// but good to be safe.
diff --git a/src/ui/Component/ParseError.tsx b/src/ui/Component/ParseError.tsx
index db8accc..f49cb7c 100644
--- a/src/ui/Component/ParseError.tsx
+++ b/src/ui/Component/ParseError.tsx
@@ -4,12 +4,6 @@ import { DisplayLineViews } from "./LineView";
export function formatErrorMesage(err: ParseError): string {
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":
return `Unexpected character: ${formatChar(err.char)}`;
@@ -20,13 +14,12 @@ export function formatErrorMesage(err: ParseError): string {
return "Expected a number here.";
case "InvalidNumber":
- return err.reason === "NotFinite"
- ? "Number is too large or invalid."
- : "Invalid number format (missing fractional digits?).";
-
- case "InvalidIdentifier":
- // Handle nested reasons if needed, e.g. "Keyword 'let' cannot be used as an identifier"
- return `Invalid identifier '${err.text}': ${err.reason.tag}`;
+ switch (err.reason) {
+ case "NotFinite":
+ return "Number is too large or invalid.";
+ case "MissingFractionalDigits":
+ return "Invalid number format (missing fractional digits?).";
+ }
case "InvalidEscape":
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 "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
case "ExpectedExpression": return "Expected an expression here.";
+ case "ExpectedSignalExpression": return "Expected a signal expression here.";
case "ExpectedFieldAssignmentSymbol": return "Expected '=' for field assignment.";
case "ExpectedPatternAssignmentSymbol": return "Expected '=' for pattern assignment.";
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 "ExpectedLetBlockOpen": return "Expected '{' to start 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 "ExpectedMatchBlockClose": return "Expected '}' to close match-block.";
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 "UnexpectedTagPattern": return "Unexpected tag pattern (expected product pattern).";
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.";
-
- default:
- return `Unknown error: ${(err as any).tag}`;
}
}
diff --git a/src/ui/Controls/index.tsx b/src/ui/Controls/index.tsx
index e8111ad..3c47059 100644
--- a/src/ui/Controls/index.tsx
+++ b/src/ui/Controls/index.tsx
@@ -1,4 +1,4 @@
-import { spawnNewFunctionDraftDigith } from "src/ui/Scrowl/scrowlStore";
+import { spawnNewFunctionDraftDigith, spawnNewSignalDraftDigith } from "src/ui/Scrowl/scrowlStore";
type Props = {
// TODO
@@ -21,8 +21,8 @@ export function Controls(props: Props) {
diff --git a/src/ui/Digith/Function/FunctionDigith.tsx b/src/ui/Digith/Function/FunctionDigith.tsx
index 22cb4d6..9c56e7a 100644
--- a/src/ui/Digith/Function/FunctionDigith.tsx
+++ b/src/ui/Digith/Function/FunctionDigith.tsx
@@ -5,7 +5,7 @@ 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 { validateExprRaw, validateParamsRaw } from "./Helpers";
+import { validateExprRaw, validateParamsRaw } from "src/ui/validation/helpers";
import { updateDigith } from "src/ui/Scrowl/scrowlStore";
import { DigithError } from "src/ui/Digith/DigithError";
@@ -100,7 +100,7 @@ export function FunctionDigith(props: { function: Digith.Function }) {
-