import type { ProblemSpec } from "../spec/types"; import type { MethodDefinition } from "../methods/types"; import { generateParamsBlock } from "./params"; import { LIBRARY_FILES, type SourceFile } from "./library"; /** A complete, self-contained set of `.m` files plus the one to run. */ export interface SourceBundle { files: SourceFile[]; /** File executed as the script */ entry: string; /** Workspace variable read back after the run */ resultVariable: string; } export const MAIN_FILE = "main.m"; /** * The script a run executes: the params block, the modes stage and the * strike stage. The host runs the two stages as two scripts (buildModesBundle * and buildStrikeBundle) so that the modes can be kept between strikes; an * edited main script runs as written, in one go. */ export function generateMain(spec: ProblemSpec, method: MethodDefinition): string { return [ generateParamsBlock(spec), "", `% Solve. The modes depend only on the plate, its thread and the cutoff: the lab keeps them`, `% and reruns only the strike when the hammer, the strike point, the probe or the microphone change.`, `% ${method.modesFunction} and ${method.strikeFunction} and the functions they call are the library files in the other tabs.`, `modes = ${method.modesFunction}(params);`, `result = ${method.strikeFunction}(params, modes);`, "", ].join("\n"); } /** Bundle for running the method on a spec in one script. */ export function buildRunBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle { return { files: [{ name: MAIN_FILE, source: generateMain(spec, method) }, ...LIBRARY_FILES], entry: MAIN_FILE, resultVariable: "result", }; } /** Stage 1 of a run: the modes, read back by the host and kept. */ export function buildModesBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle { const main = [ generateParamsBlock(spec), "", `% Stage 1 of a run: the modes, which the lab keeps while the plate and its thread stay the same.`, `modes = ${method.modesFunction}(params);`, "", ].join("\n"); return { files: [{ name: "modes.m", source: main }, ...LIBRARY_FILES], entry: "modes.m", resultVariable: "modes", }; } /** Stage 2 of a run: the strike, with the modes passed in as the workspace variable `modes`. */ export function buildStrikeBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle { const main = [ generateParamsBlock(spec), "", `% Stage 2 of a run: the strike. The workspace variable modes is the result of stage 1, passed in by the host.`, `result = ${method.strikeFunction}(params, modes);`, "", ].join("\n"); return { files: [{ name: "strike.m", source: main }, ...LIBRARY_FILES], entry: "strike.m", resultVariable: "result", }; } /** Bundle that evaluates the plan: sizes and estimates before a run. */ export function buildPlanBundle(spec: ProblemSpec, method: MethodDefinition): SourceBundle { const main = [generateParamsBlock(spec), "", `plan = ${method.planFunction}(params);`, ""].join( "\n", ); return { files: [{ name: "plan.m", source: main }, ...LIBRARY_FILES], entry: "plan.m", resultVariable: "plan", }; } /** * Replace files in a bundle with edited versions, keeping everything else. Used * when the user edits source in the code view: the run then uses exactly the * text on screen. */ export function withEdits(bundle: SourceBundle, edits: Record): SourceBundle { return { ...bundle, files: bundle.files.map((f) => edits[f.name] !== undefined ? { ...f, source: edits[f.name] } : f, ), }; }