Propagate errors up to root of script evaluation

This commit is contained in:
Tangent Wantwight 2023-10-20 19:06:01 -04:00
parent 6196f6c4aa
commit 3d5f1f70ff
4 changed files with 46 additions and 24 deletions

2
TODO
View file

@ -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

View file

@ -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 ?? "" };
}

View file

@ -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<Context> = (
state: Vm<Context>,
argv: TextPiece[]
) => TextPiece;
) => ProcResult;
/**
* State for running a script in.
@ -40,7 +38,7 @@ export type Vm<Context = {}> = {
function evaluateWord<Context>(
state: Vm<Context>,
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<Context>(
} 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<Context>(
export function runNoctl<Context>(
state: Vm<Context>,
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: `<b>UNKNOWN COMMAND: ${AsHtml(argv[0])}</b>` };
returnWord = { error: `Unknown Command: ${name}` };
}
onReturn?.(returnWord);
});
}
return returnWord;
}

View file

@ -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 '<span style="color: red;">' + escapeHtml(word.error) + "</span>";
} else {
return "";
}