Implement basic execution system and UI

This is a rather large commit that includes all of the following:
- React UI with code editor, runtime renderer and input-output panes
- Language providers for a sample language and Brainfuck
- Implementation of code execution in a web worker
- All-at-once unabortable execution of program fully functional
This commit is contained in:
Nilay Majorwar
2021-12-14 21:58:13 +05:30
parent 8746c803d7
commit 01ba292b9f
35 changed files with 3532 additions and 3529 deletions

85
ui/code-editor/index.tsx Normal file
View File

@ -0,0 +1,85 @@
import React from "react";
import Editor, { useMonaco } from "@monaco-editor/react";
import monaco from "monaco-editor";
import { DocumentRange, MonacoTokensProvider } from "../../engines/types";
import { useEditorConfig } from "./use-editor-config";
// Type aliases for the Monaco editor
type EditorInstance = monaco.editor.IStandaloneCodeEditor;
/** Create Monaco decoration range object from highlights */
const createRange = (
monacoInstance: typeof monaco,
highlights: DocumentRange
) => {
const lineNum = highlights.line;
const startChar = highlights.charRange?.start || 0;
const endChar = highlights.charRange?.end || 1000;
const range = new monacoInstance.Range(lineNum, startChar, lineNum, endChar);
const isWholeLine = !highlights.charRange;
return { range, options: { isWholeLine, inlineClassName: "code-highlight" } };
};
// Interface for interacting with the editor
export interface CodeEditorRef {
/**
* Get the current text content of the editor.
*/
getValue: () => string;
}
type Props = {
/** ID of the active language */
languageId: string;
/** Default code to display in editor */
defaultValue: string;
/** Code range to highlight in the editor */
highlights?: DocumentRange;
/** Tokens provider for the language */
tokensProvider?: MonacoTokensProvider;
};
/**
* Wrapper around the Monaco editor that reveals
* only the required functionality to the parent container.
*/
const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
const editorRef = React.useRef<EditorInstance | null>(null);
const monacoInstance = useMonaco();
const { highlights } = props;
useEditorConfig({
languageId: props.languageId,
tokensProvider: props.tokensProvider,
});
// Change editor highlights when prop changes
React.useEffect(() => {
if (!editorRef.current || !highlights) return;
const range = createRange(monacoInstance!, highlights);
const decors = editorRef.current!.deltaDecorations([], [range]);
return () => {
editorRef.current!.deltaDecorations(decors, []);
};
}, [highlights]);
// Provide handle to parent for accessing editor contents
React.useImperativeHandle(
ref,
() => ({
getValue: () => editorRef.current!.getValue(),
}),
[]
);
return (
<Editor
theme="vs-dark"
defaultLanguage="brainfuck"
defaultValue={props.defaultValue}
onMount={(editor) => (editorRef.current = editor)}
options={{ minimap: { enabled: false } }}
/>
);
};
export const CodeEditor = React.forwardRef(CodeEditorComponent);

View File

@ -0,0 +1,28 @@
import React from "react";
import { useMonaco } from "@monaco-editor/react";
import { MonacoTokensProvider } from "../../engines/types";
type ConfigParams = {
languageId: string;
tokensProvider?: MonacoTokensProvider;
};
/** Add custom language and relevant providers to Monaco */
export const useEditorConfig = (params: ConfigParams) => {
const monaco = useMonaco();
React.useEffect(() => {
if (!monaco) return;
// Register language
monaco.languages.register({ id: params.languageId });
// If provided, register token provider for language
if (params.tokensProvider) {
monaco.languages.setMonarchTokensProvider(
params.languageId,
params.tokensProvider
);
}
}, [monaco]);
};