mirror of
https://github.com/Noratrieb/brainfuck.git
synced 2026-01-14 13:35:00 +01:00
better control
This commit is contained in:
parent
d1141cc6a6
commit
2202c79521
7 changed files with 158 additions and 98 deletions
|
|
@ -17,9 +17,10 @@ stopping it at any point in your program to see what went wrong.
|
|||
* manual stepping
|
||||
* breakpoints
|
||||
* error messages
|
||||
|
||||
* seeing ASCII characters in memory
|
||||
* manual code input
|
||||
|
||||
### Future features
|
||||
* seeing ASCII characters in memory
|
||||
* fast excecution mode (no debugging info)
|
||||
* better speed control
|
||||
* better speed control
|
||||
* (limited) backstepping
|
||||
|
|
@ -4,6 +4,8 @@ import ProgramOutput from "./components/ProgramOutput";
|
|||
import React, {useCallback, useState} from "react";
|
||||
import Runner from "./components/Runner";
|
||||
|
||||
export const OptionContext = React.createContext<CodeOptions>({});
|
||||
|
||||
function App() {
|
||||
const [out, setOut] = useState("");
|
||||
const [input, setInput] = useState<[string, CodeOptions]>(["", {}]);
|
||||
|
|
@ -20,16 +22,19 @@ function App() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]);
|
||||
return (
|
||||
<div className="App-header">
|
||||
{
|
||||
!running && <CodeInput code={input[0]} setInput={inputHandler}/>
|
||||
}
|
||||
<Runner running={running} setRunning={runHandler} input={input} outHandler={outHandler}/>
|
||||
{
|
||||
running && <ProgramOutput text={out}/>
|
||||
}
|
||||
<OptionContext.Provider value={input[1]}>
|
||||
{
|
||||
!running && <CodeInput input={input} setInput={inputHandler}/>
|
||||
}
|
||||
<Runner running={running} setRunning={runHandler} code={input[0]} outHandler={outHandler}/>
|
||||
{
|
||||
running && <ProgramOutput text={out}/>
|
||||
}
|
||||
</OptionContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import {CodeOptions} from "../components/CodeInput";
|
|||
|
||||
type InHandler = (() => number);
|
||||
type OutHandler = ((char: number) => void);
|
||||
type ErrorHandler = ((msg: string) => void);
|
||||
|
||||
export default class Interpreter {
|
||||
private readonly _array: Uint8Array;
|
||||
|
|
@ -33,7 +32,11 @@ export default class Interpreter {
|
|||
}
|
||||
|
||||
public next() {
|
||||
switch (this._code[this._programCounter++]) {
|
||||
this.execute(this._code[this._programCounter++]);
|
||||
}
|
||||
|
||||
public execute(char: string) {
|
||||
switch (char) {
|
||||
case '+':
|
||||
this._array[this._pointer]++;
|
||||
break;
|
||||
|
|
@ -67,7 +70,7 @@ export default class Interpreter {
|
|||
}
|
||||
break;
|
||||
case undefined:
|
||||
this._pointer = this._code.length;
|
||||
this._programCounter = this._code.length;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
@ -112,7 +115,7 @@ export default class Interpreter {
|
|||
}
|
||||
|
||||
public prev() {
|
||||
// -
|
||||
// - will add some day
|
||||
}
|
||||
|
||||
get reachedEnd(): boolean {
|
||||
|
|
|
|||
|
|
@ -1,83 +1,79 @@
|
|||
import React, {useState} from 'react';
|
||||
import React, {ChangeEvent, useState} from 'react';
|
||||
import presets from "../presets.json";
|
||||
|
||||
export interface CodeOptions {
|
||||
minify?: boolean,
|
||||
directStart?: boolean,
|
||||
enableBreakpoints?: boolean
|
||||
asciiView?: boolean
|
||||
}
|
||||
|
||||
interface CodeInputProps {
|
||||
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 [codeOptions, setCodeOptions] = useState<CodeOptions>({});
|
||||
|
||||
|
||||
const setPreset = (name: keyof typeof presets) => () => {
|
||||
setInput(presets[name], codeOptions);
|
||||
setInput(presets[name], options);
|
||||
}
|
||||
|
||||
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setCodeOptions(old => ({...old, minify: e.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);
|
||||
const changeHandler = (name: keyof CodeOptions) => (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInput(code, {...options, [name]: event.target.checked})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bf-input">
|
||||
<div className="bf-input">
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
<label htmlFor="bf-input-fontsize-range">Font Size</label>
|
||||
<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>
|
||||
<textarea value={code} onChange={e => setInput(e.target.value, codeOptions)} style={{fontSize}}
|
||||
className="code-input"
|
||||
placeholder="Input your code here..."/>
|
||||
|
||||
<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"
|
||||
placeholder="Input your code here..."/>
|
||||
<div>
|
||||
<div>Presets</div>
|
||||
<div>
|
||||
<div>Presets</div>
|
||||
<div>
|
||||
<button onClick={setPreset("helloworld")}>Hello World</button>
|
||||
<button onClick={setPreset("hanoi")}>Towers of Hanoi</button>
|
||||
<button onClick={setPreset("quine")}>Quine</button>
|
||||
<button onClick={setPreset("gameoflife")}>Game Of Life</button>
|
||||
<button onClick={setPreset("benchmark")}>Benchmark</button>
|
||||
<button onClick={setPreset("fizzbuzz")}>Fizzbuzz</button>
|
||||
</div>
|
||||
<button onClick={setPreset("helloworld")}>Hello World</button>
|
||||
<button onClick={setPreset("hanoi")}>Towers of Hanoi</button>
|
||||
<button onClick={setPreset("quine")}>Quine</button>
|
||||
<button onClick={setPreset("gameoflife")}>Game Of Life</button>
|
||||
<button onClick={setPreset("benchmark")}>Benchmark</button>
|
||||
<button onClick={setPreset("fizzbuzz")}>Fizzbuzz</button>
|
||||
</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;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import React, {useRef, useState} from 'react';
|
||||
import React, {useContext, useRef, useState} from 'react';
|
||||
import Interpreter from "../brainfuck/Interpreter";
|
||||
import {OptionContext} from "../App";
|
||||
|
||||
const MAX_TABLE_COLUMNS = 20;
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ interface RunDisplayProps {
|
|||
}
|
||||
|
||||
const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
||||
const options = useContext(OptionContext);
|
||||
|
||||
const index = interpreter.pointer;
|
||||
|
||||
|
|
@ -38,6 +40,14 @@ const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
|||
arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter}/>)
|
||||
}
|
||||
</tr>
|
||||
{
|
||||
options.asciiView &&
|
||||
<tr>
|
||||
{
|
||||
arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter} ascii/>)
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
<tr>
|
||||
{
|
||||
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 [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 (
|
||||
<td onClick={click} className="cell">
|
||||
{
|
||||
isEditing ?
|
||||
isEditing && !ascii ?
|
||||
<input onKeyDown={keyDown}
|
||||
className="array-set-value-field"
|
||||
ref={inputField}
|
||||
|
|
@ -88,7 +109,7 @@ const MemoryCell = ({index, interpreter}: { index: number, interpreter: Interpre
|
|||
autoFocus
|
||||
/>
|
||||
:
|
||||
interpreter.array[index]
|
||||
content
|
||||
}
|
||||
</td>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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 CodeDisplay from "./CodeDisplay";
|
||||
import RunDisplay from "./RunDisplay";
|
||||
import {CodeOptions} from "./CodeInput";
|
||||
import {OptionContext} from "../App";
|
||||
|
||||
interface RunInfoProps {
|
||||
input: [string, CodeOptions],
|
||||
code: string,
|
||||
setRunning: (running: boolean) => void,
|
||||
running: boolean
|
||||
outHandler: (char: number) => void,
|
||||
}
|
||||
|
||||
const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
||||
const Runner = ({setRunning, running, outHandler, code}: RunInfoProps) => {
|
||||
const [speed, setSpeed] = useState(0);
|
||||
const [interpreter, setInterpreter] = useState<Interpreter | null>(null);
|
||||
const [info, setInfo] = useState<string | null>(null);
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
|
||||
|
||||
const [, setRerenderNumber] = useState(0);
|
||||
const options = useContext(OptionContext);
|
||||
|
||||
|
||||
const rerender = () => setRerenderNumber(n => n + 1);
|
||||
|
||||
const inputArea = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
|
|
@ -37,17 +40,17 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
}
|
||||
|
||||
const startHandler = useCallback(() => {
|
||||
if (input[1].directStart) {
|
||||
if (options.directStart) {
|
||||
setSpeed(100);
|
||||
} else {
|
||||
setSpeed(0);
|
||||
}
|
||||
|
||||
setStartTime(Date.now);
|
||||
setInterpreter(new Interpreter(input, outHandler, inputHandler));
|
||||
setInterpreter(new Interpreter([code, options], outHandler, inputHandler));
|
||||
setRunning(false);
|
||||
setRunning(true);
|
||||
}, [input, outHandler, setRunning]);
|
||||
}, [options, code, outHandler, setRunning]);
|
||||
|
||||
const stopHandler = () => {
|
||||
setRunning(false);
|
||||
|
|
@ -66,7 +69,7 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
setSpeed(0);
|
||||
setInfo(`Finished Execution. Took ${(Date.now() - startTime) / 1000}s`)
|
||||
}
|
||||
setRerenderNumber(n => n + 1);
|
||||
rerender();
|
||||
}, [interpreter, startTime]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -98,21 +101,11 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
{running && <button onClick={nextHandler}>Next</button>}
|
||||
</div>
|
||||
{
|
||||
running &&
|
||||
<div>
|
||||
<label htmlFor="run-info-speed-range">Speed</label>
|
||||
<input type="range" id="run-info-speed-range" value={speed}
|
||||
onChange={e => setSpeed(+e.target.value)}/>
|
||||
<span> {speed}</span>
|
||||
<span>
|
||||
<button onClick={() => setSpeed(s => s === 0 ? 0 : s - 1)}
|
||||
className="small-speed-button">-</button>
|
||||
<button onClick={() => setSpeed(0)}
|
||||
className="small-speed-button">0</button>
|
||||
<button onClick={() => setSpeed(s => s === 100 ? 100 : s + 1)}
|
||||
className="small-speed-button">+</button>
|
||||
</span>
|
||||
</div>
|
||||
running && interpreter &&
|
||||
<>
|
||||
<SpeedControl speed={speed} setSpeed={setSpeed}/>
|
||||
<ManualControlButtons interpreter={interpreter} rerender={rerender}/>
|
||||
</>
|
||||
}
|
||||
{info && <div className="info">{info}</div>}
|
||||
{
|
||||
|
|
@ -125,4 +118,50 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
interface SpeedControlProps {
|
||||
speed: number,
|
||||
setSpeed: React.Dispatch<React.SetStateAction<number>>,
|
||||
}
|
||||
|
||||
const SpeedControl = ({speed, setSpeed}: SpeedControlProps) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label htmlFor="run-info-speed-range">Speed</label>
|
||||
<input type="range" id="run-info-speed-range" value={speed}
|
||||
onChange={e => setSpeed(+e.target.value)}/>
|
||||
<span> {speed}</span>
|
||||
<span>
|
||||
<button onClick={() => setSpeed(s => s === 0 ? 0 : s - 1)}
|
||||
className="small-speed-button">-</button>
|
||||
<button onClick={() => setSpeed(0)}
|
||||
className="small-speed-button">0</button>
|
||||
<button onClick={() => setSpeed(s => s === 100 ? 100 : s + 1)}
|
||||
className="small-speed-button">+</button>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ManualControlButtons = ({interpreter, rerender}: { interpreter: Interpreter, rerender: (() => void) }) => {
|
||||
|
||||
const run = (char: string) => {
|
||||
try {
|
||||
interpreter.execute(char);
|
||||
} catch {
|
||||
}
|
||||
rerender();
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => run('<')} className="small-speed-button"><</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>
|
||||
<button onClick={() => run('.')} className="small-speed-button">.</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Runner;
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"gh-pages": "^3.2.3"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue