apple2js/js/components/debugger/Applesoft.tsx

138 lines
4.4 KiB
TypeScript

import { h } from 'preact';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { toHex } from 'js/util';
import ApplesoftDecompiler from 'js/applesoft/decompiler';
import { ApplesoftHeap, ApplesoftVariable } from 'js/applesoft/heap';
import { Apple2 as Apple2Impl } from 'js/apple2';
import styles from './css/Applesoft.module.css';
import debuggerStyles from './css/Debugger.module.css';
export interface ApplesoftProps {
apple2: Apple2Impl | undefined;
}
interface ApplesoftData {
variables: ApplesoftVariable[];
internals: {
txttab?: number;
fac?: number;
arg?: number;
curline?: number;
};
listing: string;
}
const TYPE_SYMBOL = ['', '$', '()', '%'] as const;
const TYPE_NAME = ['Float', 'String', 'Function', 'Integer'] as const;
const formatArray = (value: unknown): string => {
if (Array.isArray(value)) {
if (Array.isArray(value[0])) {
return `[${value.map((x) => formatArray(x)).join(',\n ')}]`;
} else {
return `[${value.map((x) => formatArray(x)).join(', ')}]`;
}
} else {
return `${JSON.stringify(value)}`;
}
};
const Variable = ({ variable }: { variable: ApplesoftVariable }) => {
const { name, type, sizes, value } = variable;
const isArray = !!sizes;
const arrayStr = isArray ? `(${sizes.map((size) => size - 1).join(',')})` : '';
return (
<tr>
<td>{name}{TYPE_SYMBOL[type]}{arrayStr}</td>
<td>{TYPE_NAME[type]}{isArray ? ' Array' : ''}</td>
<td><pre tabIndex={-1}>{isArray ? formatArray(value) : value}</pre></td>
</tr>
);
};
export const Applesoft = ({ apple2 }: ApplesoftProps) => {
const animationRef = useRef<number>(0);
const [data, setData] = useState<ApplesoftData>({
listing: '',
variables: [],
internals: {}
});
const [heap, setHeap] = useState<ApplesoftHeap>();
const cpu = apple2?.getCPU();
useEffect(() => {
if (cpu) {
// setDecompiler();
setHeap(new ApplesoftHeap(cpu));
}
}, [cpu]);
const animate = useCallback(() => {
if (cpu && heap) {
try {
const decompiler = ApplesoftDecompiler.decompilerFromMemory(cpu);
setData({
variables: heap.dumpVariables(),
internals: heap.dumpInternals(),
listing: decompiler.decompile()
});
} catch (error) {
if (error instanceof Error) {
setData({
variables: [],
internals: {},
listing: error.message
});
} else {
throw error;
}
}
}
animationRef.current = requestAnimationFrame(animate);
}, [cpu, heap]);
useEffect(() => {
animationRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationRef.current);
}, [animate]);
const { listing, internals, variables } = data;
return (
<div className={styles.column}>
<span className={debuggerStyles.subHeading}>Listing</span>
<pre className={styles.listing} tabIndex={-1}>{listing}</pre>
<span className={debuggerStyles.subHeading}>Variables</span>
<div className={styles.variables}>
<table>
<tr>
<th>Name</th>
<th>Type</th>
<th>Value</th>
</tr>
{variables.map((variable, idx) => <Variable key={idx} variable={variable} />)}
</table>
</div>
<span className={debuggerStyles.subHeading}>Internals</span>
<div className={styles.internals}>
<table>
<tr>
<th>TXTTAB</th>
<td>{toHex(internals.txttab ?? 0)}</td>
<th>FAC</th>
<td>{internals.fac}</td>
</tr>
<tr>
<th>ARG</th>
<td>{internals.arg}</td>
<th>CURLINE</th>
<td>{internals.curline}</td>
</tr>
</table>
</div>
</div>
);
};