From a48eb4de0f79c7eeadc526f9337072963c31821d Mon Sep 17 00:00:00 2001 From: Wojciech Kwiatek Date: Mon, 9 Dec 2024 12:47:38 +0100 Subject: [PATCH 1/6] Fix multi-PVM visual glitch (#241) --- src/components/MemoryPreview/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MemoryPreview/index.tsx b/src/components/MemoryPreview/index.tsx index 23e7d9f4..d256a95a 100644 --- a/src/components/MemoryPreview/index.tsx +++ b/src/components/MemoryPreview/index.tsx @@ -71,7 +71,7 @@ const MemoryCell = ({ - {numeralSystem ? "??" : "??? "} + {numeralSystem ? "??" : "???"} From 7810d7ca5b13986f13a0c826e17a30f7a6e63414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomek=20Drwi=C4=99ga?= Date: Mon, 9 Dec 2024 13:08:01 +0100 Subject: [PATCH 2/6] Don't perform multiple steps when Stepping in the UI. (#240) --- src/store/workers/workersSlice.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/store/workers/workersSlice.ts b/src/store/workers/workersSlice.ts index 6014187b..200eca10 100644 --- a/src/store/workers/workersSlice.ts +++ b/src/store/workers/workersSlice.ts @@ -328,7 +328,9 @@ export const stepAllWorkers = createAsyncThunk("workers/stepAllWorkers", async ( command: Commands.STEP, payload: { program: new Uint8Array(debuggerState.program), - stepsToPerform: debuggerState.stepsToPerform, + // NOTE [ToDr] Despite settings "batched steps", when + // the user clicks "Step" we want just single step to happen. + stepsToPerform: 1, }, }); From 071e8f89002555cc9146d684451d65288b4c504b Mon Sep 17 00:00:00 2001 From: Wojciech Kwiatek Date: Mon, 9 Dec 2024 16:50:48 +0100 Subject: [PATCH 3/6] Fix multi-PVM visual glitch (#243) --- src/components/MemoryPreview/index.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/MemoryPreview/index.tsx b/src/components/MemoryPreview/index.tsx index d256a95a..06036ed4 100644 --- a/src/components/MemoryPreview/index.tsx +++ b/src/components/MemoryPreview/index.tsx @@ -71,7 +71,15 @@ const MemoryCell = ({ - {numeralSystem ? "??" : "???"} + From 7bda3fbd38a012c2075065c736b349547d0978a3 Mon Sep 17 00:00:00 2001 From: Wojciech Kwiatek Date: Mon, 9 Dec 2024 17:32:09 +0100 Subject: [PATCH 4/6] Modify program editor to be available only in edit mode; improve error handling (#242) * Move program code input to edit mode * Handle errors in program text editor * Allow for hex in array as a program input --- src/App.tsx | 48 ++++++++--- .../ProgramLoader/BinaryFileUpload.tsx | 2 +- src/components/ProgramLoader/Bytecode.tsx | 40 --------- src/components/ProgramLoader/Examples.tsx | 28 +++++++ src/components/ProgramLoader/Loader.tsx | 24 +----- src/components/ProgramTextLoader/index.tsx | 83 ++++++++++++++++--- src/hooks/useDebuggerActions.ts | 19 +++-- src/store/debugger/debuggerSlice.ts | 12 +-- 8 files changed, 157 insertions(+), 99 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 92d8d661..10cbf5de 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,13 +21,19 @@ import { MobileRegisters } from "./components/MobileRegisters"; import { MobileKnowledgeBase } from "./components/KnowledgeBase/Mobile"; import { Assembly } from "./components/ProgramLoader/Assembly"; import { useAppDispatch, useAppSelector } from "@/store/hooks.ts"; -import { setClickedInstruction, setInstructionMode, setIsProgramEditMode } from "@/store/debugger/debuggerSlice.ts"; +import { + setClickedInstruction, + setInstructionMode, + setIsProgramEditMode, + setIsProgramInvalid, +} from "@/store/debugger/debuggerSlice.ts"; import { MemoryPreview } from "@/components/MemoryPreview"; import { DebuggerControlls } from "./components/DebuggerControlls"; import { useDebuggerActions } from "./hooks/useDebuggerActions"; import { Loader } from "./components/ProgramLoader/Loader"; import classNames from "classnames"; import { DebuggerSettings } from "./components/DebuggerSettings"; +import { ProgramTextLoader } from "@/components/ProgramTextLoader"; const DebuggerContent = () => { const dispatch = useAppDispatch(); @@ -36,7 +42,7 @@ const DebuggerContent = () => { program, initialState, isProgramEditMode, - isAsmError, + isProgramInvalid, programPreviewResult, clickedInstruction, instructionMode, @@ -78,12 +84,31 @@ const DebuggerContent = () => { {!!program.length && ( <> {isProgramEditMode && ( -
- +
+ {instructionMode === InstructionMode.ASM ? ( + + ) : ( + { + if (error) { + dispatch(setIsProgramInvalid(true)); + } + + if (!error && program) { + debuggerActions.handleProgramLoad({ + initial: initialState, + program: program || [], + name: "custom", + }); + } + }} + /> + )}
)} @@ -144,7 +169,6 @@ const DebuggerContent = () => {
@@ -158,7 +182,7 @@ const DebuggerContent = () => { variant="link" size="icon" className={!program.length ? "invisible" : "visible"} - disabled={!program.length || isAsmError} + disabled={!program.length || isProgramInvalid} title="Edit the code" onClick={() => { if (isProgramEditMode) { @@ -180,7 +204,7 @@ const DebuggerContent = () => { }; function App() { - const { pvmInitialized, initialState, program } = useAppSelector((state) => state.debugger); + const { pvmInitialized } = useAppSelector((state) => state.debugger); return ( <> @@ -210,7 +234,7 @@ function App() { ) : (
- +
)} diff --git a/src/components/ProgramLoader/BinaryFileUpload.tsx b/src/components/ProgramLoader/BinaryFileUpload.tsx index bc3dab52..be9cf390 100644 --- a/src/components/ProgramLoader/BinaryFileUpload.tsx +++ b/src/components/ProgramLoader/BinaryFileUpload.tsx @@ -44,7 +44,7 @@ export const BinaryFileUpload = ({ return (
-

or upload program as a binary file

+

Upload program as a binary file

void; - program: number[]; }) => { - const [tempProgram, setTempProgram] = useState(program); const handleFileUpload = (val: ProgramUploadFileOutput) => { - setTempProgram(val.program); onProgramLoad(val); }; return (
-

Edit program code bytes

- { - if (program) { - setTempProgram(program); - onProgramLoad({ initial, program, name: "custom" }, error); - } else { - onProgramLoad(undefined, error); - } - }} - />
); diff --git a/src/components/ProgramLoader/Examples.tsx b/src/components/ProgramLoader/Examples.tsx index ad0355d8..94f416ed 100644 --- a/src/components/ProgramLoader/Examples.tsx +++ b/src/components/ProgramLoader/Examples.tsx @@ -79,6 +79,28 @@ const programs: { memory: [], gas: 10000n, }, + empty: { + program: [0, 0, 0], + regs: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as [ + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + number, + ], + pc: 0, + pageMap: [], + memory: [], + gas: 10000n, + }, }; export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUploadFileOutput) => void }) => { @@ -131,6 +153,12 @@ export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUpload Store U16 instruction
+
+ + +
); diff --git a/src/components/ProgramLoader/Loader.tsx b/src/components/ProgramLoader/Loader.tsx index 6c5ca05e..097335c1 100644 --- a/src/components/ProgramLoader/Loader.tsx +++ b/src/components/ProgramLoader/Loader.tsx @@ -1,27 +1,17 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; import { Button } from "../ui/button"; -import { Assembly } from "./Assembly"; import { Bytecode } from "./Bytecode"; import { Examples } from "./Examples"; import { TextFileUpload } from "./TextFileUpload"; import { useState, useCallback, useEffect } from "react"; import { ProgramUploadFileOutput } from "./types"; -import { InitialState } from "@/types/pvm"; import { useDebuggerActions } from "@/hooks/useDebuggerActions"; import { useAppDispatch, useAppSelector } from "@/store/hooks.ts"; import { setIsProgramEditMode } from "@/store/debugger/debuggerSlice.ts"; import { selectIsAnyWorkerLoading } from "@/store/workers/workersSlice"; import { isSerializedError } from "@/store/utils"; -export const Loader = ({ - initialState, - program, - setIsDialogOpen, -}: { - initialState: InitialState; - program: number[]; - setIsDialogOpen?: (val: boolean) => void; -}) => { +export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) => void }) => { const dispatch = useAppDispatch(); const [programLoad, setProgramLoad] = useState(); const [error, setError] = useState(); @@ -57,7 +47,6 @@ export const Loader = ({ JSON tests Examples RAW bytecode - Assembly
@@ -83,17 +72,6 @@ export const Loader = ({ setIsSubmitted(false); setError(error); }} - program={program} - /> - - - { - setProgramLoad(val); - setIsSubmitted(false); - }} - program={program} - initialState={initialState} /> {error && isSubmitted &&

{error}

} diff --git a/src/components/ProgramTextLoader/index.tsx b/src/components/ProgramTextLoader/index.tsx index f47cf0d1..9c534052 100644 --- a/src/components/ProgramTextLoader/index.tsx +++ b/src/components/ProgramTextLoader/index.tsx @@ -1,8 +1,29 @@ import { Textarea } from "@/components/ui/textarea.tsx"; -import React, { useEffect, useState } from "react"; +import React, { useMemo, useState } from "react"; import classNames from "classnames"; import { bytes } from "@typeberry/block"; import { logger } from "@/utils/loggerService"; +import { useAppSelector } from "@/store/hooks.ts"; +import { selectIsProgramInvalid } from "@/store/debugger/debuggerSlice.ts"; + +const parseArrayLikeString = (input: string): (number | string)[] => { + // Remove the brackets and split the string by commas + const items = input + .replace(/^\[|\]$/g, "") + .split(",") + .map((item) => item.trim()); + + // Process each item + return items.map((item) => { + if (/^(?:0x)?[0-9a-fA-F]+$/i.test(item)) { + return parseInt(item, 16); + } else if (!isNaN(Number(item))) { + return Number(item); + } else { + return item; + } + }); +}; export const ProgramTextLoader = ({ program, @@ -11,11 +32,14 @@ export const ProgramTextLoader = ({ program?: number[]; setProgram: (val?: number[], error?: string) => void; }) => { - const [programInput, setProgramInput] = useState(program?.length ? JSON.stringify(program) : ""); - useEffect(() => { - setProgramInput(program?.length ? JSON.stringify(program) : ""); + const defaultProgram = useMemo(() => { + return program; }, [program]); + const [programInput, setProgramInput] = useState(defaultProgram?.length ? JSON.stringify(defaultProgram) : ""); + const [programError, setProgramError] = useState(""); + const isProgramInvalid = useAppSelector(selectIsProgramInvalid); + const handleOnChange = (e: React.ChangeEvent) => { const newInput = e.target.value.trim(); setProgramInput(newInput); @@ -23,33 +47,68 @@ export const ProgramTextLoader = ({ if (!newInput.startsWith("[")) { try { const parsedBlob = bytes.BytesBlob.parseBlob(newInput); - setProgram(Array.prototype.slice.call(parsedBlob.raw)); - } catch (error) { - logger.error("Wrong binary file", { error, hideToast: true }); - setProgram(undefined, "Wrong binary file"); + + const parsedBlobArray = Array.prototype.slice.call(parsedBlob.raw); + + if (parsedBlobArray.length) { + setProgram(parsedBlobArray); + } + + setProgramError(""); + } catch (error: unknown) { + logger.error("Wrong binary program", { error, hideToast: true }); + + setProgram(undefined, "Wrong binary program"); + + if (error instanceof Error) { + if (error?.message) { + setProgramError(error.message); + } + } } } else { try { - JSON.parse(newInput); - setProgram(JSON.parse(newInput)); + // Make sure that hex strings are parsed as strings for JSON.parse validation + const parseTest = newInput.replace(/0x([a-fA-F0-9]+)/g, '"0x$1"'); + // Parse it just to check if it's a valid JSON + JSON.parse(parseTest); + + const parsedJson = parseArrayLikeString(newInput); + const programArray = parsedJson.filter((item) => typeof item === "number") as number[]; + + if (programArray.length) { + setProgram(programArray); + } + setProgramError(""); } catch (error) { logger.error("Wrong JSON", { error, hideToast: true }); + setProgram(undefined, "Wrong JSON"); + + if (error) { + setProgramError(error.toString()); + } } } }; return (
-
+
+

+ Edit program code bytes +