Propagate errors up to root of script evaluation
This commit is contained in:
parent
6196f6c4aa
commit
3d5f1f70ff
4 changed files with 46 additions and 24 deletions
2
TODO
2
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
|
||||
|
|
|
@ -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 ?? "" };
|
||||
}
|
||||
|
|
50
src/vm.ts
50
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<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;
|
||||
}
|
||||
|
|
13
src/words.ts
13
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 '<span style="color: red;">' + escapeHtml(word.error) + "</span>";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue