Implement basic script evaluation

This commit is contained in:
Tangent Wantwight 2023-09-08 00:03:48 -04:00
parent 1b8929657a
commit ba3a3b5a26
2 changed files with 79 additions and 21 deletions

View file

@ -1,4 +1,6 @@
import { parse } from "./parser";
import { parse } from './parser';
import { runNoctl, Vm } from './vm';
import { AsHtml } from './words';
/**
* Basic unit of information, also an "actor" in the programming system
@ -12,25 +14,6 @@ type Card = {
code: string;
};
/**
* "Mode" of the environment a script runs in; determines access to mutability features and such.
*
* "action": response to a UI action; allowed to modify card fields and access time and random numbers.
*
* "render": deterministic generation of display markup from card and workspace state; can only modify temporary variables.
*/
type ScriptType = "action" | "render";
/**
* State for running a script in.
*/
type Vm = {
/** Mutability status */
mode: ScriptType;
/** Markup to render / output */
output: string;
};
/**
* @param state VM state
* @param code Script to run
@ -39,7 +22,7 @@ type Vm = {
function renderCard(state: Vm, code: string) {
const script = parse(code);
if (script[0]) {
state.output = JSON.stringify(script[1], null, 2);
runNoctl(state, script[1], (word) => (state.output += AsHtml(word) + "\n"));
} else {
state.output = script[1];
}
@ -101,6 +84,7 @@ const debugDisplay = document.createElement("pre");
function render() {
const vm: Vm = {
mode: "render",
commands: {},
output: "",
};
const html = renderCard(vm, theCard.code);

74
src/vm.ts Normal file
View file

@ -0,0 +1,74 @@
import {
AsHtml, AsText, Concat, EnchantedWord, HtmlWord, InterpolatedPiece, Script, TextPiece, TextWord,
Word
} from './words';
/**
* "Mode" of the environment a script runs in; determines access to mutability features and such.
*
* "action": response to a UI action; allowed to modify card fields and access time and random numbers.
*
* "render": deterministic generation of display markup from card and workspace state; can only modify temporary variables.
*/
export type ScriptType = "action" | "render";
export type Proc = (state: Vm, argv: TextPiece[]) => TextPiece;
/**
* State for running a script in.
*/
export type Vm = {
/** Mutability status */
mode: ScriptType;
/** Implementations of commands scripts can run */
commands: Record<string, Proc>;
/** Markup to render / output */
output: string;
};
function evaluateWord(
state: Vm,
word: Word | InterpolatedPiece
): TextWord | EnchantedWord | HtmlWord {
if ("enchanted" in word || "text" in word || "html" in word) {
return word;
} else if ("variable" in word) {
return { text: "" };
} else if ("script" in word) {
return runNoctl(state, word.script);
} else {
return (
word.pieces
.map((piece) => evaluateWord(state, piece))
.reduce(Concat, null) ?? { text: "" }
);
}
}
/**
* Runs a script in the context of a Noctl state. Potentially mutates the state.
*
* @param onReturn callback optionally invoked with the return word for each top-level command (not triggered by command substitutions)
* @returns the return word of the final command in the script, or empty text if the script is empty.
*/
export function runNoctl(
state: Vm,
script: Script,
onReturn?: (word: TextPiece) => void
): TextPiece {
let returnWord: TextPiece = { text: "" };
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>` };
}
onReturn?.(returnWord);
});
return returnWord;
}