Refactor to make language selection dynamic
This commit is contained in:
		| @ -1,3 +1,4 @@ | |||||||
|  | import { setupWorker } from "../setup-worker"; | ||||||
| import { DocumentRange, LanguageEngine, StepExecutionResult } from "../types"; | import { DocumentRange, LanguageEngine, StepExecutionResult } from "../types"; | ||||||
| import { BFAstStep, BFInstruction, BFRS, BF_OP } from "./constants"; | import { BFAstStep, BFInstruction, BFRS, BF_OP } from "./constants"; | ||||||
|  |  | ||||||
| @ -178,4 +179,4 @@ class BrainfuckLanguageEngine implements LanguageEngine<BFRS> { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| export default BrainfuckLanguageEngine; | setupWorker(new BrainfuckLanguageEngine()); | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { setupWorker } from "../setup-worker"; | ||||||
| import { LanguageEngine, StepExecutionResult } from "../types"; | import { LanguageEngine, StepExecutionResult } from "../types"; | ||||||
| import { RS } from "./constants"; | import { RS } from "./constants"; | ||||||
|  |  | ||||||
| @ -120,4 +121,4 @@ class SampleLanguageEngine implements LanguageEngine<RS> { | |||||||
|   }; |   }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export default SampleLanguageEngine; | setupWorker(new SampleLanguageEngine()); | ||||||
|  | |||||||
							
								
								
									
										103
									
								
								engines/setup-worker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								engines/setup-worker.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | |||||||
|  | import ExecutionController from "./execution-controller"; | ||||||
|  | import { LanguageEngine, StepExecutionResult } from "./types"; | ||||||
|  | import { | ||||||
|  |   WorkerAckType, | ||||||
|  |   WorkerRequestData, | ||||||
|  |   WorkerResponseData, | ||||||
|  | } from "./worker-constants"; | ||||||
|  |  | ||||||
|  | /** Create a worker response for update acknowledgement */ | ||||||
|  | const ackMessage = <RS>(state: WorkerAckType): WorkerResponseData<RS> => ({ | ||||||
|  |   type: "ack", | ||||||
|  |   data: state, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** Create a worker response for execution result */ | ||||||
|  | const resultMessage = <RS>( | ||||||
|  |   result: StepExecutionResult<RS> | ||||||
|  | ): WorkerResponseData<RS> => ({ | ||||||
|  |   type: "result", | ||||||
|  |   data: result, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /** 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 = <RS>(controller: ExecutionController<RS>) => { | ||||||
|  |   controller.resetState(); | ||||||
|  |   postMessage(ackMessage("reset")); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Load program code into the engine. | ||||||
|  |  * @param code Code content of the program | ||||||
|  |  */ | ||||||
|  | const prepare = <RS>( | ||||||
|  |   controller: ExecutionController<RS>, | ||||||
|  |   { code, input }: { code: string; input: string } | ||||||
|  | ) => { | ||||||
|  |   controller.prepare(code, input); | ||||||
|  |   postMessage(ackMessage("prepare")); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Update debugging breakpoints | ||||||
|  |  * @param points List of line numbers having breakpoints | ||||||
|  |  */ | ||||||
|  | const updateBreakpoints = <RS>( | ||||||
|  |   controller: ExecutionController<RS>, | ||||||
|  |   points: number[] | ||||||
|  | ) => { | ||||||
|  |   controller.updateBreakpoints(points); | ||||||
|  |   postMessage(ackMessage("bp-update")); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Execute the entire program loaded on engine, | ||||||
|  |  * and return result of execution. | ||||||
|  |  */ | ||||||
|  | const execute = <RS>(controller: ExecutionController<RS>, interval: number) => { | ||||||
|  |   controller.executeAll({ | ||||||
|  |     interval, | ||||||
|  |     onResult: (res) => postMessage(resultMessage(res)), | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** Trigger pause in program execution */ | ||||||
|  | const pauseExecution = async <RS>(controller: ExecutionController<RS>) => { | ||||||
|  |   await controller.pauseExecution(); | ||||||
|  |   postMessage(ackMessage("pause")); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** Run a single execution step */ | ||||||
|  | const executeStep = <RS>(controller: ExecutionController<RS>) => { | ||||||
|  |   const result = controller.executeStep(); | ||||||
|  |   postMessage(resultMessage(result)); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Create an execution controller worker script with the given engine. | ||||||
|  |  * @param engine Language engine to create worker for | ||||||
|  |  */ | ||||||
|  | export const setupWorker = <RS>(engine: LanguageEngine<RS>) => { | ||||||
|  |   const controller = new ExecutionController(engine); | ||||||
|  |  | ||||||
|  |   addEventListener("message", async (ev: MessageEvent<WorkerRequestData>) => { | ||||||
|  |     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 === "Execute") | ||||||
|  |       return 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); | ||||||
|  |     throw new Error("Invalid worker message type"); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
| @ -1,99 +0,0 @@ | |||||||
| import BrainfuckLanguageEngine from "./brainfuck/engine"; |  | ||||||
| import ExecutionController from "./execution-controller"; |  | ||||||
| import { StepExecutionResult } from "./types"; |  | ||||||
| import { |  | ||||||
|   WorkerAckType, |  | ||||||
|   WorkerRequestData, |  | ||||||
|   WorkerResponseData, |  | ||||||
| } from "./worker-constants"; |  | ||||||
|  |  | ||||||
| let _controller: ExecutionController<any> | null = null; |  | ||||||
|  |  | ||||||
| /** Create a worker response for update acknowledgement */ |  | ||||||
| const ackMessage = <RS>(state: WorkerAckType): WorkerResponseData<RS> => ({ |  | ||||||
|   type: "ack", |  | ||||||
|   data: state, |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| /** Create a worker response for execution result */ |  | ||||||
| const resultMessage = <RS>( |  | ||||||
|   result: StepExecutionResult<RS> |  | ||||||
| ): WorkerResponseData<RS> => ({ |  | ||||||
|   type: "result", |  | ||||||
|   data: result, |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Initialize the execution controller. |  | ||||||
|  */ |  | ||||||
| const initController = () => { |  | ||||||
|   const engine = new BrainfuckLanguageEngine(); |  | ||||||
|   _controller = new ExecutionController(engine); |  | ||||||
|   postMessage(ackMessage("init")); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Reset the state of the controller and engine, to |  | ||||||
|  * prepare for execution of a new program. |  | ||||||
|  */ |  | ||||||
| const resetController = () => { |  | ||||||
|   _controller!.resetState(); |  | ||||||
|   postMessage(ackMessage("reset")); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Load program code into the engine. |  | ||||||
|  * @param code Code content of the program |  | ||||||
|  */ |  | ||||||
| const prepare = ({ code, input }: { code: string; input: string }) => { |  | ||||||
|   _controller!.prepare(code, input); |  | ||||||
|   postMessage(ackMessage("prepare")); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Update debugging breakpoints |  | ||||||
|  * @param points List of line numbers having breakpoints |  | ||||||
|  */ |  | ||||||
| const updateBreakpoints = (points: number[]) => { |  | ||||||
|   _controller!.updateBreakpoints(points); |  | ||||||
|   postMessage(ackMessage("bp-update")); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Execute the entire program loaded on engine, |  | ||||||
|  * and return result of execution. |  | ||||||
|  */ |  | ||||||
| const execute = (interval: number) => { |  | ||||||
|   _controller!.executeAll({ |  | ||||||
|     interval, |  | ||||||
|     onResult: (res) => postMessage(resultMessage(res)), |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Trigger pause in program execution |  | ||||||
|  */ |  | ||||||
| const pauseExecution = async () => { |  | ||||||
|   await _controller!.pauseExecution(); |  | ||||||
|   postMessage(ackMessage("pause")); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Run a single execution step |  | ||||||
|  */ |  | ||||||
| const executeStep = () => { |  | ||||||
|   const result = _controller!.executeStep(); |  | ||||||
|   postMessage(resultMessage(result)); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| addEventListener("message", async (ev: MessageEvent<WorkerRequestData>) => { |  | ||||||
|   if (ev.data.type === "Init") return initController(); |  | ||||||
|   if (ev.data.type === "Reset") return resetController(); |  | ||||||
|   if (ev.data.type === "Prepare") return prepare(ev.data.params); |  | ||||||
|   if (ev.data.type === "Execute") return execute(ev.data.params.interval); |  | ||||||
|   if (ev.data.type === "Pause") return await pauseExecution(); |  | ||||||
|   if (ev.data.type === "ExecuteStep") return executeStep(); |  | ||||||
|   if (ev.data.type === "UpdateBreakpoints") |  | ||||||
|     return updateBreakpoints(ev.data.params.points); |  | ||||||
|   throw new Error("Invalid worker message type"); |  | ||||||
| }); |  | ||||||
							
								
								
									
										26
									
								
								pages/ide/brainfuck.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pages/ide/brainfuck.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | import React from "react"; | ||||||
|  | import { NextPage } from "next"; | ||||||
|  | import Head from "next/head"; | ||||||
|  | import { Mainframe } from "../../ui/Mainframe"; | ||||||
|  | import { Header } from "../../ui/header"; | ||||||
|  | import LangProvider from "../../engines/brainfuck"; | ||||||
|  | const LANG_ID = "brainfuck"; | ||||||
|  | const LANG_NAME = "Brainfuck"; | ||||||
|  |  | ||||||
|  | const IDE: NextPage = () => { | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <Head> | ||||||
|  |         <title>{LANG_NAME} | Esolang Park</title> | ||||||
|  |       </Head> | ||||||
|  |       <div style={{ height: "100%", display: "flex", flexDirection: "column" }}> | ||||||
|  |         <Header /> | ||||||
|  |         <div style={{ flexGrow: 1 }}> | ||||||
|  |           <Mainframe langName={LANG_ID} provider={LangProvider} /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default IDE; | ||||||
							
								
								
									
										26
									
								
								pages/ide/sample-lang.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								pages/ide/sample-lang.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,26 @@ | |||||||
|  | import React from "react"; | ||||||
|  | import { NextPage } from "next"; | ||||||
|  | import Head from "next/head"; | ||||||
|  | import { Mainframe } from "../../ui/Mainframe"; | ||||||
|  | import { Header } from "../../ui/header"; | ||||||
|  | import LangProvider from "../../engines/sample-lang"; | ||||||
|  | const LANG_ID = "sample-lang"; | ||||||
|  | const LANG_NAME = "Sample"; | ||||||
|  |  | ||||||
|  | const IDE: NextPage = () => { | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       <Head> | ||||||
|  |         <title>{LANG_NAME} | Esolang Park</title> | ||||||
|  |       </Head> | ||||||
|  |       <div style={{ height: "100%", display: "flex", flexDirection: "column" }}> | ||||||
|  |         <Header /> | ||||||
|  |         <div style={{ flexGrow: 1 }}> | ||||||
|  |           <Mainframe langName={LANG_ID} provider={LangProvider} /> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default IDE; | ||||||
| @ -3,6 +3,7 @@ import { NextPage } from "next"; | |||||||
| import { Mainframe } from "../ui/Mainframe"; | import { Mainframe } from "../ui/Mainframe"; | ||||||
| import Head from "next/head"; | import Head from "next/head"; | ||||||
| import { Header } from "../ui/header"; | import { Header } from "../ui/header"; | ||||||
|  | import BrainfuckProvider from "../engines/brainfuck"; | ||||||
|  |  | ||||||
| const Index: NextPage = () => { | const Index: NextPage = () => { | ||||||
|   return ( |   return ( | ||||||
| @ -13,7 +14,7 @@ const Index: NextPage = () => { | |||||||
|       <div style={{ height: "100%", display: "flex", flexDirection: "column" }}> |       <div style={{ height: "100%", display: "flex", flexDirection: "column" }}> | ||||||
|         <Header /> |         <Header /> | ||||||
|         <div style={{ flexGrow: 1 }}> |         <div style={{ flexGrow: 1 }}> | ||||||
|           <Mainframe /> |           <Mainframe langName="brainfuck" provider={BrainfuckProvider} /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </> |     </> | ||||||
|  | |||||||
| @ -4,11 +4,15 @@ import { InputEditor, InputEditorRef } from "../ui/input-editor"; | |||||||
| import { MainLayout } from "../ui/MainLayout"; | import { MainLayout } from "../ui/MainLayout"; | ||||||
| import { useExecController } from "../ui/use-exec-controller"; | import { useExecController } from "../ui/use-exec-controller"; | ||||||
| import { LanguageProvider, StepExecutionResult } from "../engines/types"; | import { LanguageProvider, StepExecutionResult } from "../engines/types"; | ||||||
| import BrainfuckProvider from "../engines/brainfuck"; |  | ||||||
| import { OutputViewer, OutputViewerRef } from "../ui/output-viewer"; | import { OutputViewer, OutputViewerRef } from "../ui/output-viewer"; | ||||||
| import { ExecutionControls } from "./execution-controls"; | import { ExecutionControls } from "./execution-controls"; | ||||||
| import { RendererRef, RendererWrapper } from "./renderer-wrapper"; | import { RendererRef, RendererWrapper } from "./renderer-wrapper"; | ||||||
|  |  | ||||||
|  | type Props<RS> = { | ||||||
|  |   langName: string; | ||||||
|  |   provider: LanguageProvider<RS>; | ||||||
|  | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * React component that contains and controls the entire IDE. |  * React component that contains and controls the entire IDE. | ||||||
|  * |  * | ||||||
| @ -17,10 +21,10 @@ import { RendererRef, RendererWrapper } from "./renderer-wrapper"; | |||||||
|  * small execution intervals if rendered on every execution. All state management |  * small execution intervals if rendered on every execution. All state management | ||||||
|  * is delegated to imperatively controlled child components. |  * is delegated to imperatively controlled child components. | ||||||
|  */ |  */ | ||||||
| export const Mainframe = () => { | export const Mainframe = <RS extends {}>({ langName, provider }: Props<RS>) => { | ||||||
|   // Language provider and engine |   // Language provider and engine | ||||||
|   const providerRef = React.useRef<LanguageProvider<any>>(BrainfuckProvider); |   const providerRef = React.useRef(provider); | ||||||
|   const execController = useExecController(); |   const execController = useExecController(langName); | ||||||
|  |  | ||||||
|   // Refs for controlling UI components |   // Refs for controlling UI components | ||||||
|   const codeEditorRef = React.useRef<CodeEditorRef>(null); |   const codeEditorRef = React.useRef<CodeEditorRef>(null); | ||||||
| @ -137,7 +141,7 @@ export const Mainframe = () => { | |||||||
|       renderRenderer={() => ( |       renderRenderer={() => ( | ||||||
|         <RendererWrapper |         <RendererWrapper | ||||||
|           ref={rendererRef} |           ref={rendererRef} | ||||||
|           renderer={providerRef.current.Renderer} |           renderer={providerRef.current.Renderer as any} | ||||||
|         /> |         /> | ||||||
|       )} |       )} | ||||||
|       renderInput={() => <InputEditor ref={inputEditorRef} />} |       renderInput={() => <InputEditor ref={inputEditorRef} />} | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ type WorkerState = | |||||||
|  * Also abstracts away the details of message-passing and exposes |  * Also abstracts away the details of message-passing and exposes | ||||||
|  * an imperative API to the parent component. |  * an imperative API to the parent component. | ||||||
|  */ |  */ | ||||||
| export const useExecController = <RS>() => { | export const useExecController = <RS>(langName: string) => { | ||||||
|   const workerRef = React.useRef<Worker | null>(null); |   const workerRef = React.useRef<Worker | null>(null); | ||||||
|   const [workerState, setWorkerState] = React.useState<WorkerState>("loading"); |   const [workerState, setWorkerState] = React.useState<WorkerState>("loading"); | ||||||
|  |  | ||||||
| @ -74,9 +74,7 @@ export const useExecController = <RS>() => { | |||||||
|   React.useEffect(() => { |   React.useEffect(() => { | ||||||
|     (async () => { |     (async () => { | ||||||
|       if (workerRef.current) throw new Error("Tried to reinitialize worker"); |       if (workerRef.current) throw new Error("Tried to reinitialize worker"); | ||||||
|       workerRef.current = new Worker( |       workerRef.current = new Worker(`../workers/${langName}.js`); | ||||||
|         new URL("../engines/worker.ts", import.meta.url) |  | ||||||
|       ); |  | ||||||
|       const res = await requestWorker({ type: "Init" }); |       const res = await requestWorker({ type: "Init" }); | ||||||
|       if (res.type === "ack" && res.data === "init") setWorkerState("empty"); |       if (res.type === "ack" && res.data === "init") setWorkerState("empty"); | ||||||
|       else throwUnexpectedRes("init", res); |       else throwUnexpectedRes("init", res); | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Nilay Majorwar
					Nilay Majorwar