From 3d5f1f70ff37a942a27ace0a1aa1627638418c14 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight Date: Fri, 20 Oct 2023 19:06:01 -0400 Subject: [PATCH] Propagate errors up to root of script evaluation --- TODO | 2 +- src/lib/card.ts | 5 +++-- src/vm.ts | 50 ++++++++++++++++++++++++++++++------------------- src/words.ts | 13 +++++++++++-- 4 files changed, 46 insertions(+), 24 deletions(-) diff --git a/TODO b/TODO index 7b0fbe4..9c4ace7 100644 --- a/TODO +++ b/TODO @@ -2,6 +2,7 @@ - card fields (vars maybe not needed yet?) - set field command + - command buffer in VM - formatter helper for debug page - unit test - command @@ -16,7 +17,6 @@ - impl backslash escapes - eval words -- error handling - arithmatic - subst - standard argv -> name+switches+positionals helper diff --git a/src/lib/card.ts b/src/lib/card.ts index 43d5e10..79336ed 100644 --- a/src/lib/card.ts +++ b/src/lib/card.ts @@ -1,5 +1,5 @@ import { Vm } from "../vm"; -import { TextPiece } from "../words"; +import { ProcResult, TextPiece } from "../words"; import { getOpt } from "./options"; /** @@ -18,7 +18,8 @@ export type CardVm = Vm<{ card: Card; }>; -export function GetField(vm: CardVm, argv: TextPiece[]): TextPiece { +export function GetField(vm: CardVm, argv: TextPiece[]): ProcResult { const [{ error }, fieldName] = getOpt(argv, { $min: 1, $max: 1 }); + if (error) return { error }; return { text: vm.card.fields[fieldName] ?? error ?? "" }; } diff --git a/src/vm.ts b/src/vm.ts index 9221955..e88e725 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -1,13 +1,11 @@ import { - AsHtml, AsText, - BareWord, Concat, - HtmlWord, + ErrorResult, InterpolatedPiece, + ProcResult, Script, TextPiece, - TextWord, Word, } from "./words"; @@ -23,7 +21,7 @@ export type ScriptType = "action" | "render"; export type Proc = ( state: Vm, argv: TextPiece[] -) => TextPiece; +) => ProcResult; /** * State for running a script in. @@ -40,7 +38,7 @@ export type Vm = { function evaluateWord( state: Vm, word: Word | InterpolatedPiece -): TextPiece { +): TextPiece | ErrorResult { if ("bare" in word || "text" in word || "html" in word) { return word; } else if ("variable" in word) { @@ -48,11 +46,16 @@ function evaluateWord( } else if ("script" in word) { return runNoctl(state, word.script); } else { - return ( - word.pieces - .map((piece) => evaluateWord(state, piece)) - .reduce(Concat, null) ?? { text: "" } - ); + let fullWord = null; + for (const piece of word.pieces) { + const result = evaluateWord(state, piece); + if ("error" in result) { + return result; + } else { + fullWord = Concat(fullWord, result); + } + } + return fullWord ?? { text: "" }; } } @@ -65,21 +68,30 @@ function evaluateWord( export function runNoctl( state: Vm, script: Script, - onReturn?: (word: TextPiece) => void -): TextPiece { - let returnWord: TextPiece = { text: "" }; + onReturn?: (word: TextPiece | ErrorResult) => void +): TextPiece | ErrorResult { + let returnWord: TextPiece | ErrorResult = { text: "" }; + + for (const command of script) { + const argv: TextPiece[] = []; + for (const word of command) { + const processedWord = evaluateWord(state, word); + if ("error" in processedWord) { + onReturn?.(processedWord); + return processedWord; + } else { + argv.push(processedWord); + } + } - script.forEach((command) => { - const argv = command.map((word) => evaluateWord(state, word)); const name = AsText(argv[0]); if (name in state.commands) { returnWord = state.commands[name](state, argv); } else { - // TODO: implement error propagation - returnWord = { html: `UNKNOWN COMMAND: ${AsHtml(argv[0])}` }; + returnWord = { error: `Unknown Command: ${name}` }; } onReturn?.(returnWord); - }); + } return returnWord; } diff --git a/src/words.ts b/src/words.ts index d4503d0..c46e79f 100644 --- a/src/words.ts +++ b/src/words.ts @@ -1,4 +1,4 @@ -import { escapeHtml } from './helpers'; +import { escapeHtml } from "./helpers"; /** * A word whose value is text with provenance- this literal value appeared in the source code, @@ -32,11 +32,18 @@ export type HtmlWord = { html: string; }; +export type ErrorResult = { + error: string; +}; + export type TextPiece = BareWord | TextWord | HtmlWord; + export type VariablePiece = { variable: string }; export type ScriptPiece = { script: Script }; export type InterpolatedPiece = TextPiece | VariablePiece | ScriptPiece; +export type ProcResult = TextPiece | ErrorResult; + /** * A word whose value needs to be determined by evaluating some combination of variable and command * substitutions, and concatenating the results with any literal spans. @@ -56,13 +63,15 @@ export function AsText(word: TextPiece): string { return ""; } } -export function AsHtml(word: TextPiece): string { +export function AsHtml(word: TextPiece | ErrorResult): string { if ("bare" in word) { return escapeHtml(word.bare); } else if ("text" in word) { return escapeHtml(word.text); } else if ("html" in word) { return word.html; + } else if ("error" in word) { + return '' + escapeHtml(word.error) + ""; } else { return ""; }