Create proper validation library. Rewrite new-function-draft component.

This commit is contained in:
Yura Dupyn 2026-02-14 15:59:49 +01:00
parent 0941756bf9
commit 8e4dcb5de7
8 changed files with 366 additions and 55 deletions

View file

@ -0,0 +1,155 @@
import { createSignal, For, Match, Show, Switch } 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 { Program } from "src/lang/program";
import { V, Validation, letValidate } from "../validation";
import { ProgramErrorDisplay, validateExprRaw, validateNameRaw, validateParamsRaw } from "./Helpers";
type NewFnError =
| { tag: "Parse", field: "name" | "params" | "body", err: ParseError, src: SourceText }
| { tag: "Program", err: Program.Error };
const fieldLabels: Record<string, string> = {
name: "Function Name",
params: "Parameters",
body: "Function Body"
};
export function SingleErrorDisplay(props: { error: NewFnError }) {
return (
<div style={{ "margin-bottom": "1rem" }}>
<Switch>
<Match
when={props.error.tag === "Parse" ? (props.error as Extract<NewFnError, { tag: "Parse" }>) : undefined}
>
{(err) => (
<article style={{ border: "1px solid var(--pico-del-color)", padding: "0.5rem 1rem" }}>
<header style={{ "margin-bottom": "0.5rem", color: "var(--pico-del-color)", "font-weight": "bold" }}>
{fieldLabels[err().field]} Error
</header>
<ShowParseError text={err().src} err={err().err} />
</article>
)}
</Match>
<Match
when={props.error.tag === "Program" ? (props.error as Extract<NewFnError, { tag: "Program" }>) : undefined}
>
{(err) => ( <ProgramErrorDisplay error={err().err} />)}
</Match>
</Switch>
</div>
);
}
function ErrorListDisplay(props: { errors: NewFnError[] }) {
return (
<div style={{ "margin-top": "2rem", "border-top": "1px solid var(--pico-muted-border-color)", "padding-top": "1rem" }}>
<For each={props.errors}>
{(error) => <SingleErrorDisplay error={error} />}
</For>
</div>
);
}
export function NewFunctionDraftDigith(props: { draft: Digith.NewFunctionDraft }) {
const program = useProgram();
const [name, setName] = createSignal(props.draft.raw_name);
const [params, setParams] = createSignal(props.draft.raw_parameters);
const [body, setBody] = createSignal(props.draft.raw_body);
const [validResult, setValidResult] = createSignal<V<void, NewFnError> | null>(null);
type Input = {
raw_name: string,
raw_params: string,
raw_body: string,
}
const validator: Validation<Input, void, NewFnError> = 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) })),
}),
(fields, input) => {
const createFunction: Program.CreateFunction = {
name: fields.name,
parameters: fields.parameters,
body: fields.body,
raw_parameters: input.raw_name,
raw_body: input.raw_body,
};
const regResult = Program.registerFunction(program, createFunction);
if (regResult.tag === "ok") {
// TODO: Side effects? Not sure about this. Ideally validator would be pure... but it is nice that we can return errors here.
// But then again... these are not really normal errors, right? or? But that's probably a misuse...
// For now we just return Ok
return V.ok(undefined);
} else {
return V.error({ tag: "Program", err: regResult.error });
}
});
// TODO: There's something wrong with this, it doesn't trigger when expected... WTF
function handleCommit() {
const result = validator({ raw_name: name(), raw_params: params(), raw_body: body() });
setValidResult(result);
if (result.tag === "ok") {
// Handle success closure here if needed
console.log("Function created successfully!");
}
};
return (
<article>
<header><strong>Fn Draft</strong></header>
<div class="grid">
<label>
Name
<input
type="text"
placeholder="my_func"
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
/>
</label>
<label>
Parameters (comma separated)
<input
type="text"
placeholder="x, y"
value={params()}
onInput={(e) => setParams(e.currentTarget.value)}
/>
</label>
</div>
<label>Body</label>
<CodeEditor
value={body()}
onUpdate={setBody}
onRun={handleCommit}
/>
<footer>
<button class="primary" onClick={handleCommit}>Commit</button>
</footer>
<Show when={validResult()?.tag === "errors"}>
<ErrorListDisplay
errors={(validResult() as { tag: "errors", errors: NewFnError[] }).errors}
/>
</Show>
</article>
);
}