Implement basic script evaluation
This commit is contained in:
parent
1b8929657a
commit
ba3a3b5a26
2 changed files with 79 additions and 21 deletions
26
src/3x5.ts
26
src/3x5.ts
|
@ -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
74
src/vm.ts
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue