Add automatic syntax checker, fix editor bugs
This commit is contained in:
		@ -28,6 +28,10 @@ export default class BrainfuckLanguageEngine implements LanguageEngine<BFRS> {
 | 
				
			|||||||
    this._pc = DEFAULT_PC;
 | 
					    this._pc = DEFAULT_PC;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validateCode(code: string) {
 | 
				
			||||||
 | 
					    this.parseCode(code);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  prepare(code: string, input: string) {
 | 
					  prepare(code: string, input: string) {
 | 
				
			||||||
    this._input = input;
 | 
					    this._input = input;
 | 
				
			||||||
    this._ast = this.parseCode(code);
 | 
					    this._ast = this.parseCode(code);
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,10 @@ export default class ChefLanguageEngine implements LanguageEngine<T.ChefRS> {
 | 
				
			|||||||
    this._input = DEFAULT_INPUT();
 | 
					    this._input = DEFAULT_INPUT();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validateCode(code: string) {
 | 
				
			||||||
 | 
					    parseProgram(code);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  prepare(code: string, input: string) {
 | 
					  prepare(code: string, input: string) {
 | 
				
			||||||
    this._ast = parseProgram(code);
 | 
					    this._ast = parseProgram(code);
 | 
				
			||||||
    this._input = new InputStream(input);
 | 
					    this._input = new InputStream(input);
 | 
				
			||||||
 | 
				
			|||||||
@ -21,6 +21,10 @@ export default class DeadfishLanguageEngine implements LanguageEngine<DFRS> {
 | 
				
			|||||||
    this._pc = DEFAULT_PC;
 | 
					    this._pc = DEFAULT_PC;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validateCode(code: string) {
 | 
				
			||||||
 | 
					    this.parseCode(code);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  prepare(code: string, _input: string) {
 | 
					  prepare(code: string, _input: string) {
 | 
				
			||||||
    this._ast = this.parseCode(code);
 | 
					    this._ast = this.parseCode(code);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
import { LanguageEngine, StepExecutionResult } from "./types";
 | 
					import { LanguageEngine, StepExecutionResult } from "./types";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
 | 
					  isParseError,
 | 
				
			||||||
  isRuntimeError,
 | 
					  isRuntimeError,
 | 
				
			||||||
 | 
					  serializeParseError,
 | 
				
			||||||
  serializeRuntimeError,
 | 
					  serializeRuntimeError,
 | 
				
			||||||
  WorkerRuntimeError,
 | 
					  WorkerRuntimeError,
 | 
				
			||||||
} from "./worker-errors";
 | 
					} from "./worker-errors";
 | 
				
			||||||
@ -58,6 +60,19 @@ class ExecutionController<RS> {
 | 
				
			|||||||
    this._breakpoints = points;
 | 
					    this._breakpoints = points;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Validate the syntax of the given code
 | 
				
			||||||
 | 
					   * @param code Code content, lines separated by '\n'
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  validateCode(code: string) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      this._engine.validateCode(code);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      if (isParseError(error)) return serializeParseError(error);
 | 
				
			||||||
 | 
					      else throw error;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Pause the ongoing execution.
 | 
					   * Pause the ongoing execution.
 | 
				
			||||||
   * - If already paused, returns immediately
 | 
					   * - If already paused, returns immediately
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,11 @@ const ackMessage = <RS, A extends C.WorkerAckType>(
 | 
				
			|||||||
  error,
 | 
					  error,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Create a worker response for code validation result */
 | 
				
			||||||
 | 
					const validationMessage = <RS, A extends C.WorkerAckType>(
 | 
				
			||||||
 | 
					  error?: E.WorkerParseError
 | 
				
			||||||
 | 
					): C.WorkerResponseData<RS, A> => ({ type: "validate", error });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Create a worker response for execution result */
 | 
					/** Create a worker response for execution result */
 | 
				
			||||||
const resultMessage = <RS, A extends C.WorkerAckType>(
 | 
					const resultMessage = <RS, A extends C.WorkerAckType>(
 | 
				
			||||||
  result: StepExecutionResult<RS>,
 | 
					  result: StepExecutionResult<RS>,
 | 
				
			||||||
@ -72,6 +77,15 @@ const updateBreakpoints = <RS>(
 | 
				
			|||||||
  postMessage(ackMessage("bp-update"));
 | 
					  postMessage(ackMessage("bp-update"));
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Validate the user's program syntax */
 | 
				
			||||||
 | 
					const validateCode = <RS>(
 | 
				
			||||||
 | 
					  controller: ExecutionController<RS>,
 | 
				
			||||||
 | 
					  code: string
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  const error = controller.validateCode(code);
 | 
				
			||||||
 | 
					  postMessage(validationMessage(error));
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Execute the entire program loaded on engine,
 | 
					 * Execute the entire program loaded on engine,
 | 
				
			||||||
 * and return result of execution.
 | 
					 * and return result of execution.
 | 
				
			||||||
@ -112,6 +126,8 @@ export const setupWorker = <RS>(engine: LanguageEngine<RS>) => {
 | 
				
			|||||||
      if (ev.data.type === "Reset") return resetController(controller);
 | 
					      if (ev.data.type === "Reset") return resetController(controller);
 | 
				
			||||||
      if (ev.data.type === "Prepare")
 | 
					      if (ev.data.type === "Prepare")
 | 
				
			||||||
        return prepare(controller, ev.data.params);
 | 
					        return prepare(controller, ev.data.params);
 | 
				
			||||||
 | 
					      if (ev.data.type === "ValidateCode")
 | 
				
			||||||
 | 
					        return validateCode(controller, ev.data.params.code);
 | 
				
			||||||
      if (ev.data.type === "Execute")
 | 
					      if (ev.data.type === "Execute")
 | 
				
			||||||
        return execute(controller, ev.data.params.interval);
 | 
					        return execute(controller, ev.data.params.interval);
 | 
				
			||||||
      if (ev.data.type === "Pause") return await pauseExecution(controller);
 | 
					      if (ev.data.type === "Pause") return await pauseExecution(controller);
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,9 @@ export type StepExecutionResult<RS> = {
 | 
				
			|||||||
 * execution and debugging API to the platform.
 | 
					 * execution and debugging API to the platform.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export interface LanguageEngine<RS> {
 | 
					export interface LanguageEngine<RS> {
 | 
				
			||||||
 | 
					  /** Validate the syntax of the given code. Throw ParseError if any */
 | 
				
			||||||
 | 
					  validateCode: (code: string) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Load code and user input into the engine and prepare for execution */
 | 
					  /** Load code and user input into the engine and prepare for execution */
 | 
				
			||||||
  prepare: (code: string, input: string) => void;
 | 
					  prepare: (code: string, input: string) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { DocumentRange, StepExecutionResult } from "./types";
 | 
					import { StepExecutionResult } from "./types";
 | 
				
			||||||
import * as E from "./worker-errors";
 | 
					import * as E from "./worker-errors";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Types of requests the worker handles */
 | 
					/** Types of requests the worker handles */
 | 
				
			||||||
@ -19,6 +19,10 @@ export type WorkerRequestData =
 | 
				
			|||||||
      type: "UpdateBreakpoints";
 | 
					      type: "UpdateBreakpoints";
 | 
				
			||||||
      params: { points: number[] };
 | 
					      params: { points: number[] };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: "ValidateCode";
 | 
				
			||||||
 | 
					      params: { code: string };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  | {
 | 
					  | {
 | 
				
			||||||
      type: "Execute";
 | 
					      type: "Execute";
 | 
				
			||||||
      params: { interval: number };
 | 
					      params: { interval: number };
 | 
				
			||||||
@ -57,6 +61,11 @@ export type WorkerResponseData<RS, A extends WorkerAckType> =
 | 
				
			|||||||
      data: A;
 | 
					      data: A;
 | 
				
			||||||
      error?: WorkerAckError[A];
 | 
					      error?: WorkerAckError[A];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  /** Result of code validation, containing parsing error (if any) */
 | 
				
			||||||
 | 
					  | {
 | 
				
			||||||
 | 
					      type: "validate";
 | 
				
			||||||
 | 
					      error?: E.WorkerParseError;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  /** Response containing step execution result, and runtime error (if any) */
 | 
					  /** Response containing step execution result, and runtime error (if any) */
 | 
				
			||||||
  | {
 | 
					  | {
 | 
				
			||||||
      type: "result";
 | 
					      type: "result";
 | 
				
			||||||
 | 
				
			|||||||
@ -139,6 +139,7 @@ export const Mainframe = <RS extends {}>({ langName, provider }: Props<RS>) => {
 | 
				
			|||||||
          languageId="brainfuck"
 | 
					          languageId="brainfuck"
 | 
				
			||||||
          defaultValue={providerRef.current.sampleProgram}
 | 
					          defaultValue={providerRef.current.sampleProgram}
 | 
				
			||||||
          tokensProvider={providerRef.current.editorTokensProvider}
 | 
					          tokensProvider={providerRef.current.editorTokensProvider}
 | 
				
			||||||
 | 
					          onValidateCode={execController.validateCode}
 | 
				
			||||||
          onUpdateBreakpoints={(newPoints) =>
 | 
					          onUpdateBreakpoints={(newPoints) =>
 | 
				
			||||||
            execController.updateBreakpoints(newPoints)
 | 
					            execController.updateBreakpoints(newPoints)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,8 @@ import { useEditorBreakpoints } from "./use-editor-breakpoints";
 | 
				
			|||||||
import darkTheme from "./themes/dark.json";
 | 
					import darkTheme from "./themes/dark.json";
 | 
				
			||||||
import lightTheme from "./themes/light.json";
 | 
					import lightTheme from "./themes/light.json";
 | 
				
			||||||
import { useDarkMode } from "../providers/dark-mode-provider";
 | 
					import { useDarkMode } from "../providers/dark-mode-provider";
 | 
				
			||||||
 | 
					import { WorkerParseError } from "../../engines/worker-errors";
 | 
				
			||||||
 | 
					import { useCodeValidator } from "./use-code-validator";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Interface for interacting with the editor
 | 
					// Interface for interacting with the editor
 | 
				
			||||||
export interface CodeEditorRef {
 | 
					export interface CodeEditorRef {
 | 
				
			||||||
@ -27,6 +29,8 @@ type Props = {
 | 
				
			|||||||
  defaultValue: string;
 | 
					  defaultValue: string;
 | 
				
			||||||
  /** Tokens provider for the language */
 | 
					  /** Tokens provider for the language */
 | 
				
			||||||
  tokensProvider?: MonacoTokensProvider;
 | 
					  tokensProvider?: MonacoTokensProvider;
 | 
				
			||||||
 | 
					  /** Callback to validate code syntax */
 | 
				
			||||||
 | 
					  onValidateCode: (code: string) => Promise<WorkerParseError | undefined>;
 | 
				
			||||||
  /** Callback to update debugging breakpoints */
 | 
					  /** Callback to update debugging breakpoints */
 | 
				
			||||||
  onUpdateBreakpoints: (newBreakpoints: number[]) => void;
 | 
					  onUpdateBreakpoints: (newBreakpoints: number[]) => void;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -36,15 +40,17 @@ type Props = {
 | 
				
			|||||||
 * only the required functionality to the parent container.
 | 
					 * only the required functionality to the parent container.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
 | 
					const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
 | 
				
			||||||
  const editorRef = React.useRef<EditorInstance | null>(null);
 | 
					  const [editor, setEditor] = React.useState<EditorInstance | null>(null);
 | 
				
			||||||
  const monacoRef = React.useRef<MonacoInstance | null>(null);
 | 
					  const [monaco, setMonaco] = React.useState<MonacoInstance | null>(null);
 | 
				
			||||||
 | 
					  // const editorRef = React.useRef<EditorInstance | null>(null);
 | 
				
			||||||
 | 
					  // const monacoRef = React.useRef<MonacoInstance | null>(null);
 | 
				
			||||||
  const highlightRange = React.useRef<string[]>([]);
 | 
					  const highlightRange = React.useRef<string[]>([]);
 | 
				
			||||||
  const { isDark } = useDarkMode();
 | 
					  const { isDark } = useDarkMode();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Breakpoints
 | 
					  // Breakpoints
 | 
				
			||||||
  useEditorBreakpoints({
 | 
					  useEditorBreakpoints({
 | 
				
			||||||
    editor: editorRef.current,
 | 
					    editor,
 | 
				
			||||||
    monaco: monacoRef.current,
 | 
					    monaco,
 | 
				
			||||||
    onUpdateBreakpoints: props.onUpdateBreakpoints,
 | 
					    onUpdateBreakpoints: props.onUpdateBreakpoints,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,16 +60,23 @@ const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
 | 
				
			|||||||
    tokensProvider: props.tokensProvider,
 | 
					    tokensProvider: props.tokensProvider,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Code validation
 | 
				
			||||||
 | 
					  useCodeValidator({
 | 
				
			||||||
 | 
					    editor,
 | 
				
			||||||
 | 
					    monaco,
 | 
				
			||||||
 | 
					    onValidateCode: props.onValidateCode,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** Update code highlights */
 | 
					  /** Update code highlights */
 | 
				
			||||||
  const updateHighlights = React.useCallback((hl: DocumentRange | null) => {
 | 
					  const updateHighlights = React.useCallback((hl: DocumentRange | null) => {
 | 
				
			||||||
    // Remove previous highlights
 | 
					    // Remove previous highlights
 | 
				
			||||||
    const prevRange = highlightRange.current;
 | 
					    const prevRange = highlightRange.current;
 | 
				
			||||||
    editorRef.current!.deltaDecorations(prevRange, []);
 | 
					    editor!.deltaDecorations(prevRange, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add new highlights
 | 
					    // Add new highlights
 | 
				
			||||||
    if (!hl) return;
 | 
					    if (!hl) return;
 | 
				
			||||||
    const newRange = createHighlightRange(monacoRef.current!, hl);
 | 
					    const newRange = createHighlightRange(monaco!, hl);
 | 
				
			||||||
    const rangeStr = editorRef.current!.deltaDecorations([], [newRange]);
 | 
					    const rangeStr = editor!.deltaDecorations([], [newRange]);
 | 
				
			||||||
    highlightRange.current = rangeStr;
 | 
					    highlightRange.current = rangeStr;
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -71,10 +84,10 @@ const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
 | 
				
			|||||||
  React.useImperativeHandle(
 | 
					  React.useImperativeHandle(
 | 
				
			||||||
    ref,
 | 
					    ref,
 | 
				
			||||||
    () => ({
 | 
					    () => ({
 | 
				
			||||||
      getValue: () => editorRef.current!.getValue(),
 | 
					      getValue: () => editor!.getValue(),
 | 
				
			||||||
      updateHighlights,
 | 
					      updateHighlights,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    []
 | 
					    [editor]
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@ -88,8 +101,8 @@ const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      onMount={(editor, monaco) => {
 | 
					      onMount={(editor, monaco) => {
 | 
				
			||||||
        if (!editor || !monaco) throw new Error("Error in initializing editor");
 | 
					        if (!editor || !monaco) throw new Error("Error in initializing editor");
 | 
				
			||||||
        editorRef.current = editor;
 | 
					        setEditor(editor);
 | 
				
			||||||
        monacoRef.current = monaco;
 | 
					        setMonaco(monaco);
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      options={{ minimap: { enabled: false }, glyphMargin: true }}
 | 
					      options={{ minimap: { enabled: false }, glyphMargin: true }}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import monaco from "monaco-editor";
 | 
					import monaco from "monaco-editor";
 | 
				
			||||||
import { DocumentRange } from "../../engines/types";
 | 
					import { DocumentRange } from "../../engines/types";
 | 
				
			||||||
 | 
					import { WorkerParseError } from "../../engines/worker-errors";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Type alias for an instance of Monaco editor */
 | 
					/** Type alias for an instance of Monaco editor */
 | 
				
			||||||
export type EditorInstance = monaco.editor.IStandaloneCodeEditor;
 | 
					export type EditorInstance = monaco.editor.IStandaloneCodeEditor;
 | 
				
			||||||
@ -41,6 +42,24 @@ export const createBreakpointRange = (
 | 
				
			|||||||
  return { range, options: { glyphMarginClassName: className } };
 | 
					  return { range, options: { glyphMarginClassName: className } };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Create Monaco syntax-error marker from message and document range */
 | 
				
			||||||
 | 
					export const createValidationMarker = (
 | 
				
			||||||
 | 
					  monacoInstance: MonacoInstance,
 | 
				
			||||||
 | 
					  error: WorkerParseError,
 | 
				
			||||||
 | 
					  range: DocumentRange
 | 
				
			||||||
 | 
					): monaco.editor.IMarkerData => {
 | 
				
			||||||
 | 
					  const location = get1IndexedLocation(range);
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    startLineNumber: location.line,
 | 
				
			||||||
 | 
					    endLineNumber: location.line,
 | 
				
			||||||
 | 
					    startColumn: location.charRange?.start || 0,
 | 
				
			||||||
 | 
					    endColumn: location.charRange?.end || 1000,
 | 
				
			||||||
 | 
					    severity: monacoInstance.MarkerSeverity.Error,
 | 
				
			||||||
 | 
					    message: error.message,
 | 
				
			||||||
 | 
					    source: error.name,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Convert a DocumentRange to use 1-indexed values. Used since language engines
 | 
					 * Convert a DocumentRange to use 1-indexed values. Used since language engines
 | 
				
			||||||
 * use 0-indexed ranges but Monaco requires 1-indexed ranges.
 | 
					 * use 0-indexed ranges but Monaco requires 1-indexed ranges.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										47
									
								
								ui/code-editor/use-code-validator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								ui/code-editor/use-code-validator.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					import { WorkerParseError } from "../../engines/worker-errors";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  createValidationMarker,
 | 
				
			||||||
 | 
					  EditorInstance,
 | 
				
			||||||
 | 
					  MonacoInstance,
 | 
				
			||||||
 | 
					} from "./monaco-utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Constant denoting "owner" of syntax error markers */
 | 
				
			||||||
 | 
					const MARKER_OWNER = "code-validation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Delay between user's last edit and sending validation request */
 | 
				
			||||||
 | 
					const VALIDATE_DELAY = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Args = {
 | 
				
			||||||
 | 
					  editor: EditorInstance | null;
 | 
				
			||||||
 | 
					  monaco: MonacoInstance | null;
 | 
				
			||||||
 | 
					  onValidateCode: (code: string) => Promise<WorkerParseError | undefined>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * React hook that sets up code validation lifecycle on the Monaco editor.
 | 
				
			||||||
 | 
					 * Code validation is done a fixed delay after user's last edit, and markers
 | 
				
			||||||
 | 
					 * for indicating syntax error are added to the editor.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export const useCodeValidator = ({ editor, monaco, onValidateCode }: Args) => {
 | 
				
			||||||
 | 
					  const timeoutRef = React.useRef<NodeJS.Timeout | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const runValidator = async () => {
 | 
				
			||||||
 | 
					    if (!editor || !monaco) return;
 | 
				
			||||||
 | 
					    const error = await onValidateCode(editor.getValue());
 | 
				
			||||||
 | 
					    if (error)
 | 
				
			||||||
 | 
					      monaco.editor.setModelMarkers(editor.getModel()!, MARKER_OWNER, [
 | 
				
			||||||
 | 
					        createValidationMarker(monaco, error, error.range),
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  React.useEffect(() => {
 | 
				
			||||||
 | 
					    if (!editor || !monaco) return;
 | 
				
			||||||
 | 
					    const disposer = editor.getModel()!.onDidChangeContent(() => {
 | 
				
			||||||
 | 
					      monaco.editor.setModelMarkers(editor.getModel()!, MARKER_OWNER, []);
 | 
				
			||||||
 | 
					      if (timeoutRef.current) clearTimeout(timeoutRef.current);
 | 
				
			||||||
 | 
					      timeoutRef.current = setTimeout(runValidator, VALIDATE_DELAY);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return () => disposer.dispose();
 | 
				
			||||||
 | 
					  }, [editor, monaco]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -77,9 +77,6 @@ export const useExecController = <RS>(langName: string) => {
 | 
				
			|||||||
    (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(`../workers/${langName}.js`);
 | 
					      workerRef.current = new Worker(`../workers/${langName}.js`);
 | 
				
			||||||
      workerRef.current!.addEventListener("error", (ev) =>
 | 
					 | 
				
			||||||
        console.log("Ev: ", ev)
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
      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);
 | 
				
			||||||
@ -137,6 +134,21 @@ export const useExecController = <RS>(langName: string) => {
 | 
				
			|||||||
    else throwUnexpectedRes("resetState", res);
 | 
					    else throwUnexpectedRes("resetState", res);
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Validate the syntax of the user's program code
 | 
				
			||||||
 | 
					   * @param code Code content of the user's program
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  const validateCode = React.useCallback(
 | 
				
			||||||
 | 
					    async (code: string): Promise<WorkerParseError | undefined> => {
 | 
				
			||||||
 | 
					      const res = await requestWorker(
 | 
				
			||||||
 | 
					        { type: "ValidateCode", params: { code } },
 | 
				
			||||||
 | 
					        (res) => res.type !== "validate"
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      return res.error;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    []
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Pause program execution
 | 
					   * Pause program execution
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
@ -206,6 +218,7 @@ export const useExecController = <RS>(langName: string) => {
 | 
				
			|||||||
    resetState,
 | 
					    resetState,
 | 
				
			||||||
    prepare,
 | 
					    prepare,
 | 
				
			||||||
    pauseExecution,
 | 
					    pauseExecution,
 | 
				
			||||||
 | 
					    validateCode,
 | 
				
			||||||
    execute,
 | 
					    execute,
 | 
				
			||||||
    executeStep,
 | 
					    executeStep,
 | 
				
			||||||
    updateBreakpoints,
 | 
					    updateBreakpoints,
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user