import ExecutionController from "./execution-controller"; import { LanguageEngine, StepExecutionResult } from "./types"; import * as E from "./worker-errors"; import * as C from "./worker-constants"; /** Create a worker response for acknowledgement */ const ackMessage = ( ackType: A, error?: C.WorkerAckError[A] ): C.WorkerResponseData => ({ type: "ack", data: ackType, error, }); /** Create a worker response for code validation result */ const validationMessage = ( error?: E.WorkerParseError ): C.WorkerResponseData => ({ type: "validate", error }); /** Create a worker response for execution result */ const resultMessage = ( result: StepExecutionResult, error?: E.WorkerRuntimeError ): C.WorkerResponseData => ({ type: "result", data: result, error, }); /** Create a worker response for unexpected errors */ const errorMessage = ( error: E.WorkerError ): C.WorkerResponseData => ({ type: "error", error }); /** Initialize the execution controller */ const initController = () => { postMessage(ackMessage("init")); }; /** * Reset the state of the controller and engine, to * prepare for execution of a new program. */ const resetController = (controller: ExecutionController) => { controller.resetState(); postMessage(ackMessage("reset")); }; /** * Load program code into the engine. * @param code Code content of the program */ const prepare = ( controller: ExecutionController, { code, input }: { code: string; input: string } ) => { try { controller.prepare(code, input); postMessage(ackMessage("prepare")); } catch (error) { if (E.isParseError(error)) postMessage(ackMessage("prepare", E.serializeParseError(error))); else throw error; } }; /** * Update debugging breakpoints * @param points List of line numbers having breakpoints */ const updateBreakpoints = ( controller: ExecutionController, points: number[] ) => { controller.updateBreakpoints(points); postMessage(ackMessage("bp-update")); }; /** Validate the user's program syntax */ const validateCode = ( controller: ExecutionController, code: string ) => { const error = controller.validateCode(code); postMessage(validationMessage(error)); }; /** * Execute the entire program loaded on engine, * and return result of execution. */ const execute = async ( controller: ExecutionController, interval: number ) => { const { result, error } = await controller.executeAll({ interval, onResult: (res) => postMessage(resultMessage(res)), }); if (error) postMessage(resultMessage(result, error)); }; /** Trigger pause in program execution */ const pauseExecution = async (controller: ExecutionController) => { await controller.pauseExecution(); postMessage(ackMessage("pause")); }; /** Run a single execution step */ const executeStep = (controller: ExecutionController) => { const { result, error } = controller.executeStep(); postMessage(resultMessage(result, error)); }; /** * Create an execution controller worker script with the given engine. * @param engine Language engine to create worker for */ export const setupWorker = (engine: LanguageEngine) => { const controller = new ExecutionController(engine); addEventListener("message", async (ev: MessageEvent) => { try { if (ev.data.type === "Init") return initController(); if (ev.data.type === "Reset") return resetController(controller); if (ev.data.type === "Prepare") return prepare(controller, ev.data.params); if (ev.data.type === "ValidateCode") return validateCode(controller, ev.data.params.code); if (ev.data.type === "Execute") return await execute(controller, ev.data.params.interval); if (ev.data.type === "Pause") return await pauseExecution(controller); if (ev.data.type === "ExecuteStep") return executeStep(controller); if (ev.data.type === "UpdateBreakpoints") return updateBreakpoints(controller, ev.data.params.points); } catch (error) { // Error here indicates an implementation bug const err = error as Error; postMessage(errorMessage(E.serializeError(err))); return; } throw new Error("Invalid worker message type"); }); };