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; /** 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: `UNKNOWN COMMAND: ${AsHtml(argv[0])}` }; } onReturn?.(returnWord); }); return returnWord; }