Skip to content

Commit

Permalink
Modify program editor to be available only in edit mode; improve erro…
Browse files Browse the repository at this point in the history
…r handling (#242)

* Move program code input to edit mode

* Handle errors in program text editor

* Allow for hex in array as a program input
  • Loading branch information
wkwiatek authored Dec 9, 2024
1 parent 071e8f8 commit 7bda3fb
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 99 deletions.
48 changes: 36 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -36,7 +42,7 @@ const DebuggerContent = () => {
program,
initialState,
isProgramEditMode,
isAsmError,
isProgramInvalid,
programPreviewResult,
clickedInstruction,
instructionMode,
Expand Down Expand Up @@ -78,12 +84,31 @@ const DebuggerContent = () => {
{!!program.length && (
<>
{isProgramEditMode && (
<div className="border-2 rounded-md h-full p-2 pt-8">
<Assembly
program={program}
onProgramLoad={debuggerActions.handleProgramLoad}
initialState={initialState}
/>
<div className="border-2 rounded-md h-full p-2">
{instructionMode === InstructionMode.ASM ? (
<Assembly
program={program}
onProgramLoad={debuggerActions.handleProgramLoad}
initialState={initialState}
/>
) : (
<ProgramTextLoader
program={program}
setProgram={(program, error) => {
if (error) {
dispatch(setIsProgramInvalid(true));
}

if (!error && program) {
debuggerActions.handleProgramLoad({
initial: initialState,
program: program || [],
name: "custom",
});
}
}}
/>
)}
</div>
)}

Expand Down Expand Up @@ -144,7 +169,6 @@ const DebuggerContent = () => {
<div className={`flex items-center space-x-2 ${!program.length ? "invisible" : "visible"}`}>
<Label htmlFor="instruction-mode">ASM</Label>
<Switch
disabled={isProgramEditMode}
id="instruction-mode"
checked={instructionMode === InstructionMode.BYTECODE}
onCheckedChange={(checked) =>
Expand All @@ -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) {
Expand All @@ -180,7 +204,7 @@ const DebuggerContent = () => {
};

function App() {
const { pvmInitialized, initialState, program } = useAppSelector((state) => state.debugger);
const { pvmInitialized } = useAppSelector((state) => state.debugger);

return (
<>
Expand Down Expand Up @@ -210,7 +234,7 @@ function App() {
) : (
<div className="col-span-12 flex justify-center h-[50vh] align-middle">
<div className="min-w-[50vw] max-md:w-[100%] min-h-[500px] h-[75vh] flex flex-col">
<Loader initialState={initialState} program={program} />
<Loader />
</div>
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProgramLoader/BinaryFileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const BinaryFileUpload = ({

return (
<div className="block">
<p className="mt-10 mb-3">or upload program as a binary file</p>
<p className="mb-3">Upload program as a binary file</p>
<Input
className="my-6 mr-3"
id="test-file"
Expand Down
40 changes: 0 additions & 40 deletions src/components/ProgramLoader/Bytecode.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,17 @@
import { ProgramTextLoader } from "../ProgramTextLoader";
import { ProgramUploadFileOutput } from "./types";
import { BinaryFileUpload } from "@/components/ProgramLoader/BinaryFileUpload.tsx";
import { useState } from "react";

const initial = {
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 Bytecode = ({
onProgramLoad,
program,
}: {
onProgramLoad: (val?: ProgramUploadFileOutput, error?: string) => void;
program: number[];
}) => {
const [tempProgram, setTempProgram] = useState<number[] | undefined>(program);
const handleFileUpload = (val: ProgramUploadFileOutput) => {
setTempProgram(val.program);
onProgramLoad(val);
};

return (
<div className="h-full flex flex-col">
<p className="mb-3">Edit program code bytes</p>
<ProgramTextLoader
program={tempProgram}
setProgram={(program, error) => {
if (program) {
setTempProgram(program);
onProgramLoad({ initial, program, name: "custom" }, error);
} else {
onProgramLoad(undefined, error);
}
}}
/>
<BinaryFileUpload onFileUpload={handleFileUpload} />
</div>
);
Expand Down
28 changes: 28 additions & 0 deletions src/components/ProgramLoader/Examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => {
Expand Down Expand Up @@ -131,6 +153,12 @@ export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUpload
Store U16 instruction
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="empty" id="option-empty" />
<Label htmlFor="option-empty" className="cursor-pointer">
Empty
</Label>
</div>
</RadioGroup>
</div>
);
Expand Down
24 changes: 1 addition & 23 deletions src/components/ProgramLoader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -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<ProgramUploadFileOutput>();
const [error, setError] = useState<string>();
Expand Down Expand Up @@ -57,7 +47,6 @@ export const Loader = ({
<TabsTrigger value="upload">JSON tests</TabsTrigger>
<TabsTrigger value="examples">Examples</TabsTrigger>
<TabsTrigger value="bytecode">RAW bytecode</TabsTrigger>
<TabsTrigger value="assembly">Assembly</TabsTrigger>
</TabsList>
<div className="border-2 rounded p-4 flex-1 flex flex-col w-full h-full overflow-auto md:px-5">
<TabsContent value="upload">
Expand All @@ -83,17 +72,6 @@ export const Loader = ({
setIsSubmitted(false);
setError(error);
}}
program={program}
/>
</TabsContent>
<TabsContent value="assembly">
<Assembly
onProgramLoad={(val) => {
setProgramLoad(val);
setIsSubmitted(false);
}}
program={program}
initialState={initialState}
/>
</TabsContent>
{error && isSubmitted && <p className="text-red-500">{error}</p>}
Expand Down
83 changes: 71 additions & 12 deletions src/components/ProgramTextLoader/index.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -11,45 +32,83 @@ 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<HTMLTextAreaElement>) => {
const newInput = e.target.value.trim();
setProgramInput(newInput);

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 (
<div className="h-full">
<div className={classNames("h-full flex-auto flex gap-1 flex-col border-2 rounded-md ")}>
<div className={classNames("h-full flex-auto flex gap-1 flex-col")}>
<p className="pb-2 mb-1">
<small>Edit program code bytes</small>
</p>
<Textarea
autoFocus
className={classNames("w-full flex-auto font-mono border-0 text-base")}
className={classNames("w-full flex-auto font-mono text-base border-2 rounded-md", {
"focus-visible:ring-3 focus-visible:outline-none active:outline-none border-red-500": isProgramInvalid,
})}
id="program"
placeholder="Paste the program as an array of numbers or hex string"
value={programInput}
onChange={handleOnChange}
/>
{isProgramInvalid && <span className="text-red-500">{programError || "Program is not valid"}</span>}
</div>
</div>
);
Expand Down
Loading

0 comments on commit 7bda3fb

Please sign in to comment.