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}> - - +
+ +
); }