better control

This commit is contained in:
nora 2021-06-25 14:35:27 +02:00
parent d1141cc6a6
commit 2202c79521
7 changed files with 158 additions and 98 deletions

View file

@ -17,9 +17,10 @@ stopping it at any point in your program to see what went wrong.
* manual stepping * manual stepping
* breakpoints * breakpoints
* error messages * error messages
* seeing ASCII characters in memory
* manual code input
### Future features ### Future features
* seeing ASCII characters in memory
* fast excecution mode (no debugging info) * fast excecution mode (no debugging info)
* better speed control * better speed control
* (limited) backstepping

View file

@ -4,6 +4,8 @@ import ProgramOutput from "./components/ProgramOutput";
import React, {useCallback, useState} from "react"; import React, {useCallback, useState} from "react";
import Runner from "./components/Runner"; import Runner from "./components/Runner";
export const OptionContext = React.createContext<CodeOptions>({});
function App() { function App() {
const [out, setOut] = useState(""); const [out, setOut] = useState("");
const [input, setInput] = useState<[string, CodeOptions]>(["", {}]); const [input, setInput] = useState<[string, CodeOptions]>(["", {}]);
@ -20,16 +22,19 @@ function App() {
} }
} }
const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]); const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]);
return ( return (
<div className="App-header"> <div className="App-header">
<OptionContext.Provider value={input[1]}>
{ {
!running && <CodeInput code={input[0]} setInput={inputHandler}/> !running && <CodeInput input={input} setInput={inputHandler}/>
} }
<Runner running={running} setRunning={runHandler} input={input} outHandler={outHandler}/> <Runner running={running} setRunning={runHandler} code={input[0]} outHandler={outHandler}/>
{ {
running && <ProgramOutput text={out}/> running && <ProgramOutput text={out}/>
} }
</OptionContext.Provider>
</div> </div>
); );
} }

View file

@ -2,7 +2,6 @@ import {CodeOptions} from "../components/CodeInput";
type InHandler = (() => number); type InHandler = (() => number);
type OutHandler = ((char: number) => void); type OutHandler = ((char: number) => void);
type ErrorHandler = ((msg: string) => void);
export default class Interpreter { export default class Interpreter {
private readonly _array: Uint8Array; private readonly _array: Uint8Array;
@ -33,7 +32,11 @@ export default class Interpreter {
} }
public next() { public next() {
switch (this._code[this._programCounter++]) { this.execute(this._code[this._programCounter++]);
}
public execute(char: string) {
switch (char) {
case '+': case '+':
this._array[this._pointer]++; this._array[this._pointer]++;
break; break;
@ -67,7 +70,7 @@ export default class Interpreter {
} }
break; break;
case undefined: case undefined:
this._pointer = this._code.length; this._programCounter = this._code.length;
break; break;
default: default:
break; break;
@ -112,7 +115,7 @@ export default class Interpreter {
} }
public prev() { public prev() {
// - // - will add some day
} }
get reachedEnd(): boolean { get reachedEnd(): boolean {

View file

@ -1,67 +1,47 @@
import React, {useState} from 'react'; import React, {ChangeEvent, useState} from 'react';
import presets from "../presets.json"; import presets from "../presets.json";
export interface CodeOptions { export interface CodeOptions {
minify?: boolean, minify?: boolean,
directStart?: boolean, directStart?: boolean,
enableBreakpoints?: boolean enableBreakpoints?: boolean
asciiView?: boolean
} }
interface CodeInputProps { interface CodeInputProps {
setInput: ((code: string, options: CodeOptions) => void), setInput: ((code: string, options: CodeOptions) => void),
code: string input: [string, CodeOptions]
} }
const CodeInput = ({code, setInput}: CodeInputProps) => { const CodeInput = ({input: [code, options], setInput}: CodeInputProps) => {
const [fontSize, setFontSize] = useState(40); const [fontSize, setFontSize] = useState(40);
const [codeOptions, setCodeOptions] = useState<CodeOptions>({});
const setPreset = (name: keyof typeof presets) => () => { const setPreset = (name: keyof typeof presets) => () => {
setInput(presets[name], codeOptions); setInput(presets[name], options);
} }
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => { const changeHandler = (name: keyof CodeOptions) => (event: ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, minify: e.target.checked})) setInput(code, {...options, [name]: event.target.checked})
setInput(code, codeOptions);
}
const changeStart = (e: React.ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, directStart: e.target.checked}))
setInput(code, codeOptions);
}
const changeBreakpoint = (e: React.ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, enableBreakpoints: e.target.checked}))
setInput(code, codeOptions);
} }
return ( return (
<div>
<div className="bf-input"> <div className="bf-input">
<div> <div>
<span> <div>
<label htmlFor="bf-input-fontsize-range">Font Size</label> <label htmlFor="bf-input-fontsize-range">Font Size</label>
<input type="range" id="bf-input-fontsize-range" onChange={v => setFontSize(+v.target.value)}/> <input type="range" id="bf-input-fontsize-range" onChange={v => setFontSize(+v.target.value)}/>
</span>
<span>
<input type="checkbox" checked={codeOptions.minify} id="input-options-minify"
onChange={changeMinify}/>
<label htmlFor="input-options-minify">Minify Code</label>
</span>
<span>
<input type="checkbox" checked={codeOptions.directStart} id="input-options-directstart"
onChange={changeStart}/>
<label htmlFor="input-options-directstart">Start Directly</label>
</span>
<span>
<input type="checkbox" checked={codeOptions.enableBreakpoints} id="input-options-enableBreakpoints"
onChange={changeBreakpoint}/>
<label htmlFor="input-options-enableBreakpoints">Breakpoints ()</label>
</span>
</div> </div>
<textarea value={code} onChange={e => setInput(e.target.value, codeOptions)} style={{fontSize}}
<CodeOption displayName="Minify Code" name="minify" options={options} onChange={changeHandler}/>
<CodeOption displayName="Start Directly" name="directStart" options={options}
onChange={changeHandler}/>
<CodeOption displayName="Breakpoints (•)" name="enableBreakpoints" options={options}
onChange={changeHandler}/>
<CodeOption displayName="Show ASCII in memory" name="asciiView" options={options}
onChange={changeHandler}/>
</div>
<textarea value={code} onChange={e => setInput(e.target.value, options)} style={{fontSize}}
className="code-input" className="code-input"
placeholder="Input your code here..."/> placeholder="Input your code here..."/>
<div> <div>
@ -76,8 +56,24 @@ const CodeInput = ({code, setInput}: CodeInputProps) => {
</div> </div>
</div> </div>
</div> </div>
</div>
); );
}; };
interface CodeOptionProps {
displayName: string,
name: keyof CodeOptions,
options: CodeOptions,
onChange: (name: keyof CodeOptions) => (event: ChangeEvent<HTMLInputElement>) => void,
}
const CodeOption = ({displayName, name, options, onChange}: CodeOptionProps) => {
return (
<span>
<input type="checkbox" checked={options[name]} id={`input-options-${name}`}
onChange={onChange(name)}/>
<label htmlFor={`input-options-${name}`}>{displayName}</label>
</span>
);
}
export default CodeInput; export default CodeInput;

View file

@ -1,5 +1,6 @@
import React, {useRef, useState} from 'react'; import React, {useContext, useRef, useState} from 'react';
import Interpreter from "../brainfuck/Interpreter"; import Interpreter from "../brainfuck/Interpreter";
import {OptionContext} from "../App";
const MAX_TABLE_COLUMNS = 20; const MAX_TABLE_COLUMNS = 20;
@ -8,6 +9,7 @@ interface RunDisplayProps {
} }
const RunDisplay = ({interpreter}: RunDisplayProps) => { const RunDisplay = ({interpreter}: RunDisplayProps) => {
const options = useContext(OptionContext);
const index = interpreter.pointer; const index = interpreter.pointer;
@ -38,6 +40,14 @@ const RunDisplay = ({interpreter}: RunDisplayProps) => {
arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter}/>) arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter}/>)
} }
</tr> </tr>
{
options.asciiView &&
<tr>
{
arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter} ascii/>)
}
</tr>
}
<tr> <tr>
{ {
arrayWithIndex.map((n) => <td className="pointer" arrayWithIndex.map((n) => <td className="pointer"
@ -50,7 +60,13 @@ const RunDisplay = ({interpreter}: RunDisplayProps) => {
); );
}; };
const MemoryCell = ({index, interpreter}: { index: number, interpreter: Interpreter }) => { interface MemoryCellProps {
index: number,
interpreter: Interpreter,
ascii?: boolean,
}
const MemoryCell = ({index, interpreter, ascii}: MemoryCellProps) => {
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [input, setInput] = useState(interpreter.array[index] + ""); const [input, setInput] = useState(interpreter.array[index] + "");
@ -75,10 +91,15 @@ const MemoryCell = ({index, interpreter}: { index: number, interpreter: Interpre
} }
} }
const content = ascii ?
String.fromCharCode(interpreter.array[index])
:
interpreter.array[index];
return ( return (
<td onClick={click} className="cell"> <td onClick={click} className="cell">
{ {
isEditing ? isEditing && !ascii ?
<input onKeyDown={keyDown} <input onKeyDown={keyDown}
className="array-set-value-field" className="array-set-value-field"
ref={inputField} ref={inputField}
@ -88,7 +109,7 @@ const MemoryCell = ({index, interpreter}: { index: number, interpreter: Interpre
autoFocus autoFocus
/> />
: :
interpreter.array[index] content
} }
</td> </td>
); );

View file

@ -1,24 +1,27 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'; import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
import Interpreter from "../brainfuck/Interpreter"; import Interpreter from "../brainfuck/Interpreter";
import CodeDisplay from "./CodeDisplay"; import CodeDisplay from "./CodeDisplay";
import RunDisplay from "./RunDisplay"; import RunDisplay from "./RunDisplay";
import {CodeOptions} from "./CodeInput"; import {OptionContext} from "../App";
interface RunInfoProps { interface RunInfoProps {
input: [string, CodeOptions], code: string,
setRunning: (running: boolean) => void, setRunning: (running: boolean) => void,
running: boolean running: boolean
outHandler: (char: number) => void, outHandler: (char: number) => void,
} }
const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => { const Runner = ({setRunning, running, outHandler, code}: RunInfoProps) => {
const [speed, setSpeed] = useState(0); const [speed, setSpeed] = useState(0);
const [interpreter, setInterpreter] = useState<Interpreter | null>(null); const [interpreter, setInterpreter] = useState<Interpreter | null>(null);
const [info, setInfo] = useState<string | null>(null); const [info, setInfo] = useState<string | null>(null);
const [startTime, setStartTime] = useState(0); const [startTime, setStartTime] = useState(0);
const [, setRerenderNumber] = useState(0); const [, setRerenderNumber] = useState(0);
const options = useContext(OptionContext);
const rerender = () => setRerenderNumber(n => n + 1);
const inputArea = useRef<HTMLTextAreaElement>(null); const inputArea = useRef<HTMLTextAreaElement>(null);
@ -37,17 +40,17 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
} }
const startHandler = useCallback(() => { const startHandler = useCallback(() => {
if (input[1].directStart) { if (options.directStart) {
setSpeed(100); setSpeed(100);
} else { } else {
setSpeed(0); setSpeed(0);
} }
setStartTime(Date.now); setStartTime(Date.now);
setInterpreter(new Interpreter(input, outHandler, inputHandler)); setInterpreter(new Interpreter([code, options], outHandler, inputHandler));
setRunning(false); setRunning(false);
setRunning(true); setRunning(true);
}, [input, outHandler, setRunning]); }, [options, code, outHandler, setRunning]);
const stopHandler = () => { const stopHandler = () => {
setRunning(false); setRunning(false);
@ -66,7 +69,7 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
setSpeed(0); setSpeed(0);
setInfo(`Finished Execution. Took ${(Date.now() - startTime) / 1000}s`) setInfo(`Finished Execution. Took ${(Date.now() - startTime) / 1000}s`)
} }
setRerenderNumber(n => n + 1); rerender();
}, [interpreter, startTime]); }, [interpreter, startTime]);
useEffect(() => { useEffect(() => {
@ -98,7 +101,31 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
{running && <button onClick={nextHandler}>Next</button>} {running && <button onClick={nextHandler}>Next</button>}
</div> </div>
{ {
running && running && interpreter &&
<>
<SpeedControl speed={speed} setSpeed={setSpeed}/>
<ManualControlButtons interpreter={interpreter} rerender={rerender}/>
</>
}
{info && <div className="info">{info}</div>}
{
running && <div>
<div>Input:</div>
<textarea className="program-input-area" ref={inputArea}/>
</div>
}
</div>
);
};
interface SpeedControlProps {
speed: number,
setSpeed: React.Dispatch<React.SetStateAction<number>>,
}
const SpeedControl = ({speed, setSpeed}: SpeedControlProps) => {
return (
<div> <div>
<label htmlFor="run-info-speed-range">Speed</label> <label htmlFor="run-info-speed-range">Speed</label>
<input type="range" id="run-info-speed-range" value={speed} <input type="range" id="run-info-speed-range" value={speed}
@ -113,16 +140,28 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
className="small-speed-button">+</button> className="small-speed-button">+</button>
</span> </span>
</div> </div>
)
} }
{info && <div className="info">{info}</div>}
{ const ManualControlButtons = ({interpreter, rerender}: { interpreter: Interpreter, rerender: (() => void) }) => {
running && <div>
<div>Input:</div> const run = (char: string) => {
<textarea className="program-input-area" ref={inputArea}/> try {
</div> interpreter.execute(char);
} catch {
} }
rerender();
}
return (
<div>
<button onClick={() => run('<')} className="small-speed-button">&lt;</button>
<button onClick={() => run('>')} className="small-speed-button">&gt;</button>
<button onClick={() => run('-')} className="small-speed-button">-</button>
<button onClick={() => run('+')} className="small-speed-button">+</button>
<button onClick={() => run('.')} className="small-speed-button">.</button>
</div> </div>
); )
}; }
export default Runner; export default Runner;

View file

@ -1,5 +0,0 @@
{
"dependencies": {
"gh-pages": "^3.2.3"
}
}