Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/DebuggerSettings/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const DebuggerSettingsContent = () => {
</span>
<Input
className={commonClass}
placeholder="0x-prefixed, encoded operands"
onChange={(e) => {
const value = e.target?.value;
dispatch(setSpiArgs(value));
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProgramLoader/Assembly.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const Assembly = ({
try {
const programJson = compile_assembly(input);
const newProgram = JSON.parse(programJson);
const output = mapUploadFileInputToOutput(newProgram);
const output = mapUploadFileInputToOutput(newProgram, "assembly");
output.name = programName;
// avoid re-rendering when the code & state is the same.
if (isArrayEqual(program, output.program)) {
Expand Down
2 changes: 2 additions & 0 deletions src/components/ProgramLoader/Examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const Examples = ({ onProgramLoad }: { onProgramLoad: (val: ProgramUpload
memory: program.memory,
gas: program.gas,
},
isSpi: false,
kind: "Example",
program: programs[key].program,
name: program.name,
exampleName: key,
Expand Down
67 changes: 52 additions & 15 deletions src/components/ProgramLoader/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,24 @@ import { useState, useCallback, useEffect } from "react";
import { ProgramUploadFileOutput } from "./types";
import { useDebuggerActions } from "@/hooks/useDebuggerActions";
import { useAppDispatch, useAppSelector } from "@/store/hooks.ts";
import { setIsProgramEditMode } from "@/store/debugger/debuggerSlice.ts";
import { setIsProgramEditMode, setSpiArgs } from "@/store/debugger/debuggerSlice.ts";
import { selectIsAnyWorkerLoading } from "@/store/workers/workersSlice";
import { isSerializedError } from "@/store/utils";
import { ProgramFileUpload } from "@/components/ProgramLoader/ProgramFileUpload.tsx";
import { useNavigate } from "react-router";
import { Links } from "./Links";
import { Separator } from "../ui/separator";
import { TriangleAlert } from "lucide-react";
import { WithHelp } from "../WithHelp/WithHelp";
import { Input } from "../ui/input";

export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) => void }) => {
const dispatch = useAppDispatch();
const [programLoad, setProgramLoad] = useState<ProgramUploadFileOutput>();
const [error, setError] = useState<string>();
const [isSubmitted, setIsSubmitted] = useState(false);
const debuggerActions = useDebuggerActions();
const isLoading = useAppSelector(selectIsAnyWorkerLoading);
const debuggerState = useAppSelector((state) => state.debugger);
const navigate = useNavigate();

useEffect(() => {
Expand All @@ -28,8 +30,6 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =

const handleLoad = useCallback(
async (program?: ProgramUploadFileOutput) => {
setIsSubmitted(true);

if (!programLoad && !program) return;

dispatch(setIsProgramEditMode(false));
Expand Down Expand Up @@ -60,26 +60,63 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
<Examples
onProgramLoad={(val) => {
setProgramLoad(val);
setIsSubmitted(false);
handleLoad(val);
}}
/>

<div className="my-10">
<ProgramFileUpload
onFileUpload={(val) => {
setProgramLoad(val);
setIsSubmitted(false);
// handleLoad(val);
}}
/>
<ProgramFileUpload onFileUpload={setProgramLoad} isError={error !== undefined} setError={setError} />
</div>
<Links />
{error && isSubmitted && (
<p className="flex items-center text-destructive-foreground mt-10 text-[11px] whitespace-pre-line">
{error && (
<p className="flex items-top text-destructive-foreground h-[145px] overflow-auto text-[11px] whitespace-pre-line">
<TriangleAlert className="mr-2" height="18px" /> {error}
</p>
)}
{!error && programLoad && (
<div className="h-[145px] overflow-auto text-xs">
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">Detected:</span>
<code className="flex-1 ml-2"> {programLoad.kind}</code>
</div>
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">Name:</span>
<code className="flex-1 ml-2">{programLoad.name}</code>
</div>
<div className="mt-2 flex items-center">
<span className="block text-xs font-bold min-w-[150px]">Initial state:</span>
<details open={false} className="flex-1 ml-2">
<summary>view</summary>
<pre>{JSON.stringify(programLoad.initial, null, 2)}</pre>
</details>
</div>
{programLoad.isSpi && (
<>
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">
<WithHelp help="Hex-encoded JAM SPI arguments written to the heap">Arguments</WithHelp>
</span>
<Input
size={2}
className="text-xs m-2"
placeholder="0x-prefixed, encoded operands"
onChange={(e) => {
const value = e.target?.value;
dispatch(setSpiArgs(value));
}}
value={debuggerState.spiArgs ?? ""}
/>
</div>
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">
<WithHelp help="JSON containing instructions how to handle host calls">Host Calls Trace</WithHelp>
</span>
<p className="flex-1 ml-2">(coming soon)</p>
</div>
</>
)}
</div>
)}
{!error && !programLoad && <Links />}
</div>
<div className="px-5 mt-[30px]">
<Separator />
Expand Down
39 changes: 21 additions & 18 deletions src/components/ProgramLoader/ProgramFileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { SafeParseReturnType, z } from "zod";
import { useAppSelector } from "@/store/hooks";
import { selectInitialState } from "@/store/debugger/debuggerSlice";
import { cn, getAsChunks, getAsPageMap } from "@/lib/utils.ts";
import { TriangleAlert, UploadCloud } from "lucide-react";
import { UploadCloud } from "lucide-react";
import { Button } from "../ui/button";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Input } from "../ui/input";
import { decodeSpiWithMetadata } from "@/utils/spi";

type ProgramFileUploadProps = {
onFileUpload: (val: ProgramUploadFileOutput) => void;
setError: (e?: string) => void;
isError: boolean;
close?: () => void;
};

Expand Down Expand Up @@ -70,6 +72,9 @@ function loadFileFromUint8Array(
onFileUpload: (d: ProgramUploadFileOutput) => void,
initialState: ExpectedState,
) {
// reset error state
setError(undefined);

// Try to parse file as a JSON first
let jsonFile = null;
let stringContent = "";
Expand Down Expand Up @@ -104,7 +109,7 @@ function loadFileFromUint8Array(
return;
}

onFileUpload(mapUploadFileInputToOutput(jsonFile));
onFileUpload(mapUploadFileInputToOutput(jsonFile, "JSON test"));
return;
}

Expand All @@ -123,6 +128,8 @@ function loadFileFromUint8Array(
onFileUpload({
program: Array.from(code),
name: `${loadedFileName} [spi]`,
isSpi: true,
kind: "JAM SPI",
initial: {
regs: Array.from(registers).map((x) => BigInt(x as number | bigint)) as RegistersArray,
pc: 0,
Expand All @@ -146,20 +153,21 @@ function loadFileFromUint8Array(
onFileUpload({
program: Array.from(uint8Array),
name: `${loadedFileName} [generic]`,
isSpi: false,
kind: "Generic PVM",
initial: initialState,
});
} else {
setError("Unrecognized program format (see console).");
}
}

export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ onFileUpload, close }) => {
export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ isError, onFileUpload, close, setError }) => {
const initialState = useAppSelector(selectInitialState);
const spiArgs = useAppSelector((state) => state.debugger.spiArgs);

const [isUpload, setIsUpload] = useState(true);
const [manualInput, setManualInput] = useState("");
const [error, setError] = useState<string>();
const [loadedFileName, setLoadedFileName] = useState<string | undefined>(undefined);

const spiArgsAsBytes = useMemo(() => {
Expand All @@ -173,18 +181,17 @@ export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ onFileUplo
useEffect(() => {
// reset the state of upload
if (isUpload) {
setLoadedFileName(undefined);
setError(undefined);
return;
}

if (manualInput === "") {
setError(undefined);
return;
}

const buffer = new TextEncoder().encode(manualInput);
loadFileFromUint8Array("pasted", buffer, spiArgsAsBytes, setError, onFileUpload, initialState);
}, [manualInput, isUpload, initialState, onFileUpload, spiArgsAsBytes]);
}, [manualInput, isUpload, initialState, onFileUpload, spiArgsAsBytes, setError]);

const handleFileRead = (e: ProgressEvent<FileReader>) => {
setError(undefined);
Expand Down Expand Up @@ -227,9 +234,11 @@ export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ onFileUplo
open();
}, [open]);

const toggleUpload = useCallback(() => {
setIsUpload((upload) => !upload);
}, []);
const setNoUpload = useCallback(() => {
setIsUpload(false);
setLoadedFileName(undefined);
setError(undefined);
}, [setError]);

const isLoaded = loadedFileName !== undefined;

Expand All @@ -253,14 +262,14 @@ export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ onFileUplo
value={manualInput}
onChange={(e) => setManualInput(e.target.value)}
className={cn("flex-auto text-xs", {
"focus-visible:ring-red-500 ring-red-500 ring-2": error,
"focus-visible:ring-red-500 ring-red-500 ring-2": isError,
})}
/>
)}
</div>
<div className="flex space-x-2">
{isUpload && (
<Button className="text-[10px] py-1 h-9" variant="ghost" onClick={toggleUpload}>
<Button className="text-[10px] py-1 h-9" variant="ghost" onClick={setNoUpload}>
Paste manually
</Button>
)}
Expand All @@ -270,12 +279,6 @@ export const ProgramFileUpload: React.FC<ProgramFileUploadProps> = ({ onFileUplo
</div>
<input {...getInputProps()} className="hidden" />
</div>

{error && (
<p className="flex items-center text-destructive-foreground mt-3 text-[11px] whitespace-pre-line">
<TriangleAlert className="mr-2" height="18px" /> {error}
</p>
)}
</div>
);
};
2 changes: 2 additions & 0 deletions src/components/ProgramLoader/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export type ProgramUploadFileOutput = {
initial: InitialState;
program: number[];
exampleName?: string;
kind: string;
isSpi: boolean;
};

export type ProgramUploadFileInput = {
Expand Down
12 changes: 9 additions & 3 deletions src/components/ProgramLoader/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mapKeys, camelCase, pickBy } from "lodash";
import { ProgramUploadFileInput, ProgramUploadFileOutput } from "./types";
import { RegistersArray } from "@/types/pvm";

export function mapUploadFileInputToOutput(data: ProgramUploadFileInput): ProgramUploadFileOutput {
export function mapUploadFileInputToOutput(data: ProgramUploadFileInput, kind: string): ProgramUploadFileOutput {
const camelCasedData = mapKeys(data, (_value: unknown, key: string) => camelCase(key));

const initial = pickBy(camelCasedData, (_value: unknown, key) => key.startsWith("initial"));
Expand All @@ -14,9 +14,15 @@ export function mapUploadFileInputToOutput(data: ProgramUploadFileInput): Progra
...(mapKeys(initial, (_value: unknown, key) =>
camelCase(key.replace("initial", "")),
) as ProgramUploadFileOutput["initial"]),
// TODO is-writable has wrong case
// pageMap: data["initial-page-map"].map((val) => ({ address: val.address, length: val.length, isWritable: val["is-writable"] })),
// TODO [ToDr] is this okay?
pageMap: data["initial-page-map"].map((val) => ({
address: val.address,
length: val.length,
["is-writable"]: val["is-writable"],
})),
},
kind,
isSpi: false,
program: data.program,
// expected: mapKeys(expected, (_value: unknown, key) => camelCase(key.replace("expected", ""))) as unknown as ProgramUploadFileOutput["expected"],
};
Expand Down
4 changes: 4 additions & 0 deletions src/pages/DebuggerContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,17 @@ const DebuggerContent = () => {
program: Array.from(code) || [],
initial: { ...initialState, regs, pageMap, memory },
name: `${programNameWithoutSuffix} (SPI)`,
isSpi: true,
kind: "JAM SPI",
};
debuggerActions.handleProgramLoad(program);
} else {
debuggerActions.handleProgramLoad({
initial: initialState,
program: program || [],
name: `${programName} (generic)`,
isSpi: false,
kind: "Generic PVM",
});
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/pages/ProgramLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const ProgramLoader = () => {
await debuggerActions.handleProgramLoad({
program: program.program,
name: program.name,
isSpi: false,
kind: "Example",
initial: {
regs: program.regs.map((x) => BigInt(x)) as RegistersArray,
pc: program.pc,
Expand Down Expand Up @@ -75,6 +77,8 @@ const ProgramLoader = () => {
await debuggerActions.handleProgramLoad({
program: Array.from(code),
name: "loaded-from-url [SPI]",
isSpi: true,
kind: "JAM SPI",
initial: {
regs: Array.from(registers).map((x) => BigInt(x as number | bigint)) as RegistersArray,
pc: 0,
Expand All @@ -93,6 +97,8 @@ const ProgramLoader = () => {
program: parsedBlobArray,
name: "loaded-from-url [generic]",
initial: initialState,
isSpi: false,
kind: "Generic PVM",
});

navigate("/", { replace: true });
Expand Down
Loading