diff --git a/momonga/Cargo.toml b/momonga/Cargo.toml index 72d95fc..6ef02c4 100644 --- a/momonga/Cargo.toml +++ b/momonga/Cargo.toml @@ -11,6 +11,7 @@ wasm-bindgen = "0.2" lazy_static = "1.4.0" pest = "2.6" pest_derive = "2.6" +console_error_panic_hook = "0.1.7" [dependencies.web-sys] version = "0.3" diff --git a/momonga/src/data.rs b/momonga/src/data.rs index 3d90bc0..4db736f 100644 --- a/momonga/src/data.rs +++ b/momonga/src/data.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::env::Store; use crate::error::EvalError; -use crate::{emit_print_event, PrintEvent}; +use crate::{emit_output_event, OutputEvent}; #[derive(Debug, PartialEq, Clone)] pub enum Value<'a> { @@ -100,6 +100,6 @@ pub fn momonga_pop(args: BuiltinArgs) -> BuiltinReturn { } pub fn momonga_print(args: BuiltinArgs) -> BuiltinReturn { - emit_print_event(PrintEvent::Stdout, &(*args[0].borrow()).to_string()); + emit_output_event(OutputEvent::Stdout, &(*args[0].borrow()).to_string()); Ok(Rc::new(RefCell::new(Value::None))) } diff --git a/momonga/src/lib.rs b/momonga/src/lib.rs index 02d2ff6..235ea32 100644 --- a/momonga/src/lib.rs +++ b/momonga/src/lib.rs @@ -24,17 +24,17 @@ pub fn interpret(src: &str) -> Option { } #[wasm_bindgen] -pub enum PrintEvent { +pub enum OutputEvent { Stdout, Stderr, } #[wasm_bindgen] -pub fn emit_print_event(print_event: PrintEvent, data: &str) { +pub fn emit_output_event(output_event: OutputEvent, data: &str) { let window = web_sys::window().unwrap(); - let type_ = match print_event { - PrintEvent::Stdout => "printstdout", - PrintEvent::Stderr => "printstderr", + let type_ = match output_event { + OutputEvent::Stdout => "stdout", + OutputEvent::Stderr => "stderr", }; let event = CustomEvent::new(type_).unwrap(); event.init_custom_event_with_can_bubble_and_cancelable_and_detail( @@ -48,11 +48,25 @@ pub fn emit_print_event(print_event: PrintEvent, data: &str) { #[wasm_bindgen] pub fn momonga_run(source: &str) { + #[cfg(debug_assertions)] + console_error_panic_hook::set_once(); + match parse(source) { Ok(ast) => match eval(&ast, Rc::new(RefCell::new(Env::new_with_builtins()))) { Ok(_) => (), - Err(eval_err) => emit_print_event(PrintEvent::Stderr, &eval_err.to_string()), + Err(eval_err) => emit_output_event(OutputEvent::Stderr, &eval_err.to_string()), }, - Err(parse_err) => emit_print_event(PrintEvent::Stderr, &parse_err.to_string()), + Err(parse_err) => emit_output_event(OutputEvent::Stderr, &parse_err.to_string()), + } +} + +#[wasm_bindgen] +pub fn is_momonga_parse_error(source: &str) -> bool { + #[cfg(debug_assertions)] + console_error_panic_hook::set_once(); + + match parse(source) { + Ok(_ast) => false, + Err(_parse_err) => true, } } diff --git a/package.json b/package.json index 255d2d1..b2b1bc8 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "node": "18.x" }, "scripts": { - "dev": "vite", + "dev": "cd ./momonga && wasm-pack build --dev --target web && cd ../ && vite", "build": "npm run build:core && npm run build:web", "build:core": "cd ./momonga && wasm-pack build --release --target web", "build:web": "tsc && vite build", @@ -16,7 +16,7 @@ "lint": "npm run lint:web && npm run lint:core", "lint:core": "cd momonga && cargo clippy -- -D warnings", "lint:web": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview", + "preview": "npm run build && vite preview", "prepare": "simple-git-hooks" }, "simple-git-hooks": { diff --git a/src/App.tsx b/src/App.tsx index 3037ed8..39c6280 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Box, @@ -14,10 +14,16 @@ import { Header } from "@/components/Header"; import { Output } from "@/components/Output"; import { snippets } from "@/constants"; import { Layout, SnippetKey, Stderr, Stdout } from "@/types/types"; -import init, { momonga_run } from "../momonga/pkg/momonga"; +import init, { + is_momonga_parse_error, + momonga_run, +} from "../momonga/pkg/momonga"; function App() { + const isWasmInitializedRef = useRef(false); + const srcRef = useRef(""); + const [isParseError, setIsParseError] = useState(false); const [stdout, setStdout] = useState([]); const [stderr, setStderr] = useState([]); const [snippetKey, setSnippetKey] = useState(snippets[0].key); @@ -31,51 +37,62 @@ function App() { ); const isHorizontalLayout = userLayout === "horizontal" || isMuiMdScreen; - const handleRunClick = () => { + const handleRunClick = useCallback(() => { setStdout([]); setStderr([]); - momonga_run(srcRef.current); - }; + momonga_run(srcRef.current); // NOTE: In order to run on Worker, it is necessary to change the way of passing its output data to main thread. + }, []); - const handleSrcChange = (src: string) => { + const handleSrcChange = useCallback((src: string) => { srcRef.current = src; - }; - const handleLayoutClick = () => { + if (!isWasmInitializedRef.current) return; + setIsParseError(is_momonga_parse_error(src)); + }, []); + + const handleLayoutClick = useCallback(() => { setUserLayout((prev) => prev === "horizontal" ? "vertical" : "horizontal", ); - }; + }, []); - const handleSnippetChange = (event: SelectChangeEvent) => { - const snippet = snippets.find( - (snippet) => snippet.key === event.target.value, - ); - if (snippet) { - setSnippetKey(snippet.key); - } - }; + const handleSnippetChange = useCallback( + (event: SelectChangeEvent) => { + const snippet = snippets.find( + (snippet) => snippet.key === event.target.value, + ); + if (snippet) { + setSnippetKey(snippet.key); + } + }, + [], + ); useEffect(() => { - init(); + (async () => { + await init(); + isWasmInitializedRef.current = true; + })(); - const handlePrintStdoutEvent = (ev: Event) => { + const handleStdoutEvent = (ev: Event) => { const event = ev as CustomEvent; setStdout((prev) => [...prev, event.detail]); }; - const handlePrintstderrEvent = (ev: Event) => { + const handleStderrEvent = (ev: Event) => { const event = ev as CustomEvent; setStderr((prev) => [...prev, event.detail]); }; - window.addEventListener("printstderr", handlePrintstderrEvent); - window.addEventListener("printstdout", handlePrintStdoutEvent); - - localStorage.setItem("userLayout", userLayout); + window.addEventListener("stderr", handleStderrEvent); + window.addEventListener("stdout", handleStdoutEvent); return () => { - window.removeEventListener("printstdout", handlePrintStdoutEvent); - window.removeEventListener("printstderr", handlePrintstderrEvent); + window.removeEventListener("stdout", handleStdoutEvent); + window.removeEventListener("stderr", handleStderrEvent); }; + }, []); + + useEffect(() => { + localStorage.setItem("userLayout", userLayout); }, [userLayout]); return ( @@ -121,6 +138,7 @@ function App() { }} > ; snippetKey: SnippetKey; onSrcChange: (src: string) => void; }; -export const Editor = React.memo( - ({ srcRef, snippetKey, onSrcChange }: Props) => { +export const Editor = memo( + ({ isParseError, srcRef, snippetKey, onSrcChange }: Props) => { const [editor, setEditor] = useState(null); const monacoEl = useRef(null); @@ -157,6 +158,14 @@ export const Editor = React.memo( } }, [editor, monacoTheme]); - return ; + return ( + + ); }, ); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 0150ec7..eaf86a9 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,4 +1,4 @@ -import { useContext } from "react"; +import { memo, useContext } from "react"; import { AppBar, @@ -29,83 +29,85 @@ type Props = { onMainLayoutClick: () => void; }; -export const Header = ({ - isMuiMdScreen, - isHorizontalLayout, - snippetKey, - onMainLayoutClick, - onRunClick, - onSnippetChange, -}: Props) => { - const { mode, toggleMode } = useContext(ThemeContext); - return ( - - - - - - - Code Snippets - - - - - - + + + Code Snippets + + + + + - Grammar - - {!isMuiMdScreen && ( - - {isHorizontalLayout ? ( - - ) : ( - - )} + + {!isMuiMdScreen && ( + + {isHorizontalLayout ? ( + + ) : ( + + )} + + )} + + {mode === "light" ? : } - )} - - {mode === "light" ? : } - - - - - ); -}; + + + + ); + }, +); diff --git a/src/components/Output.tsx b/src/components/Output.tsx index d0b0820..2887cd7 100644 --- a/src/components/Output.tsx +++ b/src/components/Output.tsx @@ -1,3 +1,5 @@ +import { memo } from "react"; + import { Box, Divider } from "@mui/material"; import { Stderr, Stdout } from "@/types/types"; @@ -7,7 +9,7 @@ type Props = { stderr: Stderr; }; -export const Output = ({ stdout, stderr }: Props) => { +export const Output = memo(({ stdout, stderr }: Props) => { return ( { ); -}; +});