Skip to content

Commit

Permalink
Improve converter tool (#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
netalondon authored Feb 26, 2024
1 parent f6cc8fa commit 5beae4a
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 43 deletions.
2 changes: 2 additions & 0 deletions simulator/src/util/asm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ function cop(asm: string): number {
assign = assign ?? (assignExists ? undefined : "");
jump = jump ?? (jumpExists ? undefined : "");
if (
parts?.[0] != asm || // match is not exhaustive
!isAssignAsm(assign) ||
!isJumpAsm(jump) ||
(!isCommandAsm(operation) && !isCommandAsm(operation.replace("M", "A")))
) {
// TODO: This should return Result<> instead of throw
throw new Error("Invalid c instruction");
}

Expand Down
2 changes: 1 addition & 1 deletion simulator/src/util/twos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export function dec(i: number): string {
return `${i}`;
}

export function uns(i: number): string {
export function unsigned(i: number): string {
i = i & 0xffff;
return `${i}`;
}
Expand Down
156 changes: 114 additions & 42 deletions web/src/pages/util.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ChangeEvent, useState } from "react";
import { asm, op } from "@nand2tetris/simulator/util/asm.js";
import {
bin,
Expand All @@ -7,34 +6,91 @@ import {
int10,
int16,
int2,
uns,
unsigned,
} from "@nand2tetris/simulator/util/twos.js";
import { ChangeEvent, Dispatch, SetStateAction, useState } from "react";

import "./util.scss";

export const Util = () => {
const [value, setValue] = useState(0);
const [asmValue, setAsmValue] = useState("@0");
function validBin(value: string) {
return /^[01]+$/.test(value) && value.length <= 16;
}

function validDec(value: string) {
return (
/^-?\d+$/.test(value) && Number(value) <= 32767 && Number(value) >= -32768
);
}

function validUnsigned(value: string) {
return /^\d+$/.test(value) && Number(value) <= 65535;
}

function validHex(value: string) {
return /^0x[0-9a-fA-F]+$/.test(value) && value.length <= 6;
}

const doSetValue =
(conv: (arg: string) => number) =>
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
value = value === "-" ? "-1" : value;
const iValue = conv(value);
setValue(iValue);
setAsmValue(asm(iValue));
};
function validAsm(value: string) {
try {
op(value);
return true;
} catch {
return false;
}
}

const setBin = doSetValue(int2);
const setInt = doSetValue(int10);
const setUns = doSetValue(int10);
const setHex = doSetValue(int16);
export const FormattedInput = (props: {
id: string;
value?: number;
setValue: Dispatch<SetStateAction<number | undefined>>;
setError: Dispatch<SetStateAction<string | undefined>>;
isValid: (value: string) => boolean;
parse: (value: string) => number;
format: (value: number) => string;
}) => {
const [selected, setSelected] = useState(false);
const [rawValue, setRawValue] = useState("");

const setAsm = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
setAsmValue(value);
setValue(op(value));
const onChange = ({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
if (!selected) {
return;
}
setRawValue(value);
if (!props.isValid(value)) {
props.setError("Invalid value");
props.setValue(undefined);
} else {
props.setError(undefined);
const parsed = props.parse(value);
props.setValue(parsed);
}
};

return (
<input
id="util_setBin"
type="text"
value={
selected
? rawValue
: props.value !== undefined
? props.format(props.value)
: ""
}
onChange={onChange}
onFocus={() => {
setSelected(true);
setRawValue(props.value !== undefined ? props.format(props.value) : "");
}}
onBlur={() => setSelected(false)}
/>
);
};

export const Util = () => {
const [value, setValue] = useState<number | undefined>();
const [error, setError] = useState<string | undefined>();

return (
<article>
<header>
Expand All @@ -46,58 +102,74 @@ export const Util = () => {
<label htmlFor="util_setBin">Binary</label>
</dt>
<dd>
<input
<FormattedInput
id="util_setBin"
type="text"
value={bin(value)}
onChange={setBin}
value={value}
setValue={setValue}
setError={setError}
parse={int2}
format={bin}
isValid={validBin}
/>
</dd>
<dt>
<label htmlFor="util_setInt">Decimal</label>
</dt>
<dd>
<input
<FormattedInput
id="util_setInt"
type="text"
value={dec(value)}
onChange={setInt}
value={value}
setValue={setValue}
setError={setError}
parse={int10}
format={dec}
isValid={validDec}
/>
</dd>
<dt>
<label htmlFor="util_setUns">Unsigned</label>
<label htmlFor="util_setUnsigned">Unsigned</label>
</dt>
<dd>
<input
id="util_setUns"
type="text"
value={uns(value)}
onChange={setUns}
<FormattedInput
id="util_setUnsigned"
value={value}
setValue={setValue}
setError={setError}
parse={int10}
format={unsigned}
isValid={validUnsigned}
/>
</dd>
<dt>
<label htmlFor="util_setHex">Hex</label>
</dt>
<dd>
<input
<FormattedInput
id="util_setHex"
type="text"
value={hex(value)}
onChange={setHex}
value={value}
setValue={setValue}
setError={setError}
parse={int16}
format={hex}
isValid={validHex}
/>
</dd>
<dt>
<label htmlFor="util_setAsm">HACK ASM</label>
</dt>
<dd>
<input
<FormattedInput
id="util_setAsm"
type="text"
value={asmValue}
onChange={setAsm}
value={value}
setValue={setValue}
setError={setError}
parse={op}
format={asm}
isValid={validAsm}
/>
</dd>
</dl>
{error && <p>{error}</p>}
</main>
</article>
);
Expand Down

0 comments on commit 5beae4a

Please sign in to comment.