mirror of
https://github.com/Noratrieb/brainfuck.git
synced 2026-01-15 13:55:02 +01:00
more settings
This commit is contained in:
parent
e13d82ff46
commit
dc79c96f63
10 changed files with 246 additions and 116 deletions
|
|
@ -38,6 +38,7 @@ $medium-color: #78787f;
|
|||
|
||||
.code-display-wrapper {
|
||||
max-width: 80vw;
|
||||
font-family: monospace;
|
||||
|
||||
span {
|
||||
word-wrap: break-word;
|
||||
|
|
@ -55,6 +56,15 @@ $medium-color: #78787f;
|
|||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.array-set-value-field {
|
||||
min-width: 50px;
|
||||
max-width: 100px;
|
||||
height: 40px;
|
||||
color: $light-color;
|
||||
font-size: 30px;
|
||||
background-color: $main-color-brighter;
|
||||
}
|
||||
}
|
||||
|
||||
.bf-run {
|
||||
|
|
@ -65,6 +75,11 @@ $medium-color: #78787f;
|
|||
width: 200px;
|
||||
}
|
||||
|
||||
.small-speed-button {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.program-input-area {
|
||||
resize: none;
|
||||
width: 80vw;
|
||||
|
|
@ -72,8 +87,8 @@ $medium-color: #78787f;
|
|||
font-size: 30px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #664242FF;
|
||||
.info {
|
||||
background-color: #579ca7;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ function App() {
|
|||
const [running, setRunning] = useState(false);
|
||||
|
||||
const outHandler = useCallback((char: number) => {
|
||||
setOut(out => out + String.fromCharCode(char))
|
||||
setOut(oldOut => oldOut + String.fromCharCode(char))
|
||||
}, []);
|
||||
|
||||
const runHandler = (run: boolean) => {
|
||||
|
|
@ -21,7 +21,6 @@ function App() {
|
|||
}
|
||||
|
||||
const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]);
|
||||
|
||||
return (
|
||||
<div className="App-header">
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,15 +12,17 @@ export default class Interpreter {
|
|||
|
||||
private readonly _inHandler: InHandler;
|
||||
private readonly _outHandler: OutHandler;
|
||||
private readonly _errorHandler: ErrorHandler;
|
||||
|
||||
constructor(input: [string, CodeOptions], outHandler: OutHandler, inHandler: InHandler, errorHandler: ErrorHandler) {
|
||||
private readonly _options: CodeOptions;
|
||||
|
||||
constructor(input: [string, CodeOptions], outHandler: OutHandler, inHandler: InHandler) {
|
||||
const buf = new ArrayBuffer(32000);
|
||||
this._array = new Uint8Array(buf);
|
||||
this._pointer = 0;
|
||||
|
||||
this._options = input[1];
|
||||
if (input[1].minify) {
|
||||
this._code = minify(input[0])
|
||||
this._code = this.minify(input[0])
|
||||
} else {
|
||||
this._code = input[0];
|
||||
}
|
||||
|
|
@ -28,7 +30,6 @@ export default class Interpreter {
|
|||
this._programCounter = 0;
|
||||
this._inHandler = inHandler;
|
||||
this._outHandler = outHandler;
|
||||
this._errorHandler = errorHandler;
|
||||
}
|
||||
|
||||
public next() {
|
||||
|
|
@ -44,8 +45,7 @@ export default class Interpreter {
|
|||
break;
|
||||
case '<':
|
||||
if (this._pointer === 0) {
|
||||
this._errorHandler("Cannot wrap left");
|
||||
break;
|
||||
throw new Error("Cannot wrap left");
|
||||
}
|
||||
this._pointer--;
|
||||
break;
|
||||
|
|
@ -53,45 +53,66 @@ export default class Interpreter {
|
|||
this._outHandler(this.value);
|
||||
break;
|
||||
case ',':
|
||||
try {
|
||||
this._array[this._pointer] = this._inHandler();
|
||||
} catch {
|
||||
this._programCounter--;
|
||||
this._errorHandler("Could not read input, trying again next time.")
|
||||
}
|
||||
this.input();
|
||||
break;
|
||||
case '[':
|
||||
if (this.value === 0) {
|
||||
let level = 0;
|
||||
while (this.lastInstruction !== ']' || level > -1) {
|
||||
this._programCounter++;
|
||||
if (this.lastInstruction === '[') level++;
|
||||
else if (this.lastInstruction === ']') level--;
|
||||
}
|
||||
}
|
||||
this.loopForwards();
|
||||
break;
|
||||
case ']':
|
||||
if (this.value !== 0) {
|
||||
let level = 0;
|
||||
while (this.lastInstruction !== '[' || level > -1) {
|
||||
this._programCounter--;
|
||||
if (this.lastInstruction === '[') level--;
|
||||
else if (this.lastInstruction === ']') level++;
|
||||
}
|
||||
this.loopBackwards();
|
||||
break;
|
||||
case '•':
|
||||
if (this._options.enableBreakpoints) {
|
||||
throw new Error("Breakpoint reached");
|
||||
}
|
||||
break;
|
||||
case undefined:
|
||||
this._pointer = this._code.length;
|
||||
console.warn("reached end");
|
||||
break;
|
||||
default: {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private loopForwards() {
|
||||
if (this.value === 0) {
|
||||
let level = 0;
|
||||
while (this.lastInstruction !== ']' || level > -1) {
|
||||
this._programCounter++;
|
||||
if (this._programCounter > this._code.length) {
|
||||
throw new Error("Reached end of code while searching ']'");
|
||||
}
|
||||
if (this.lastInstruction === '[') level++;
|
||||
else if (this.lastInstruction === ']') level--;
|
||||
}
|
||||
}
|
||||
console.log(`char: ${this.code[this.programCounter - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`)
|
||||
}
|
||||
|
||||
private loopBackwards() {
|
||||
if (this.value !== 0) {
|
||||
let level = 0;
|
||||
while (this.lastInstruction !== '[' || level > -1) {
|
||||
this._programCounter--;
|
||||
if (this._programCounter < 0) {
|
||||
throw new Error("Reached start of code while searching '['");
|
||||
}
|
||||
if (this.lastInstruction === '[') level--;
|
||||
else if (this.lastInstruction === ']') level++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private input() {
|
||||
try {
|
||||
this._array[this._pointer] = this._inHandler();
|
||||
} catch {
|
||||
this._programCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
public prev() {
|
||||
|
||||
// -
|
||||
}
|
||||
|
||||
get reachedEnd(): boolean {
|
||||
|
|
@ -121,11 +142,18 @@ export default class Interpreter {
|
|||
get programCounter(): number {
|
||||
return this._programCounter;
|
||||
}
|
||||
|
||||
private minify(code: string): string {
|
||||
const CHARS = ['+', '-', '<', '>', '.', ',', '[', ']'];
|
||||
if (this._options.enableBreakpoints) {
|
||||
CHARS.push('•');
|
||||
}
|
||||
|
||||
return code.split("")
|
||||
.filter(c => CHARS.includes(c))
|
||||
.join("");
|
||||
}
|
||||
}
|
||||
|
||||
const CHARS = ['+', '-', '<', '>', '.', ',', '[', ']'];
|
||||
const minify = (code: string): string =>
|
||||
code.split("")
|
||||
.filter(c => CHARS.includes(c))
|
||||
.join("");
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import React, {useState} from 'react';
|
||||
import presets from "../presets.json";
|
||||
|
||||
export interface CodeOptions {
|
||||
minify?: boolean
|
||||
minify?: boolean,
|
||||
directStart?: boolean,
|
||||
enableBreakpoints?: boolean
|
||||
}
|
||||
|
||||
interface CodeInputProps {
|
||||
|
|
@ -15,10 +18,8 @@ const CodeInput = ({code, setInput}: CodeInputProps) => {
|
|||
const [codeOptions, setCodeOptions] = useState<CodeOptions>({});
|
||||
|
||||
|
||||
const setStart = () => {
|
||||
setInput(
|
||||
"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
|
||||
codeOptions);
|
||||
const setPreset = (name: keyof typeof presets) => () => {
|
||||
setInput(presets[name], codeOptions);
|
||||
}
|
||||
|
||||
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
|
|
@ -26,6 +27,16 @@ const CodeInput = ({code, setInput}: CodeInputProps) => {
|
|||
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 (
|
||||
<div>
|
||||
<div className="bf-input">
|
||||
|
|
@ -34,14 +45,35 @@ const CodeInput = ({code, setInput}: CodeInputProps) => {
|
|||
<label htmlFor="bf-input-fontsize-range">Font Size</label>
|
||||
<input type="range" id="bf-input-fontsize-range" onChange={v => setFontSize(+v.target.value)}/>
|
||||
</span>
|
||||
<input type="checkbox" checked={codeOptions.minify} id="input-options-minify" onChange={changeMinify}/>
|
||||
<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..."/>
|
||||
<div>
|
||||
<button onClick={setStart}>Set Hello World</button>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, {useRef, useState} from 'react';
|
||||
import Interpreter from "../brainfuck/Interpreter";
|
||||
|
||||
const MAX_TABLE_COLUMNS = 20;
|
||||
|
|
@ -35,7 +35,7 @@ const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
|||
<tbody>
|
||||
<tr>
|
||||
{
|
||||
arrayWithIndex.map((n) => <td className="cell" key={n}>{interpreter.array[n]}</td>)
|
||||
arrayWithIndex.map((n) => <MemoryCell key={n} index={n} interpreter={interpreter}/>)
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
@ -50,4 +50,48 @@ const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
const MemoryCell = ({index, interpreter}: { index: number, interpreter: Interpreter }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [input, setInput] = useState(interpreter.array[index] + "");
|
||||
|
||||
const inputField = useRef<HTMLInputElement>(null);
|
||||
|
||||
const saveAndQuit = () => {
|
||||
interpreter.array[index] = +(input);
|
||||
setIsEditing(false);
|
||||
}
|
||||
|
||||
const click = () => {
|
||||
setIsEditing(true);
|
||||
inputField.current?.select();
|
||||
}
|
||||
|
||||
const keyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
console.log("key", e.key);
|
||||
if (e.key === "Escape") {
|
||||
setIsEditing(false);
|
||||
} else if (e.key === "Enter") {
|
||||
saveAndQuit();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<td onClick={click} className="cell">
|
||||
{
|
||||
isEditing ?
|
||||
<input onKeyDown={keyDown}
|
||||
className="array-set-value-field"
|
||||
ref={inputField}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
value={input}
|
||||
onBlur={saveAndQuit}
|
||||
autoFocus
|
||||
/>
|
||||
:
|
||||
interpreter.array[index]
|
||||
}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
export default RunDisplay;
|
||||
|
|
@ -14,7 +14,9 @@ interface RunInfoProps {
|
|||
const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
||||
const [speed, setSpeed] = useState(0);
|
||||
const [interpreter, setInterpreter] = useState<Interpreter | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [info, setInfo] = useState<string | null>(null);
|
||||
const [startTime, setStartTime] = useState(0);
|
||||
|
||||
|
||||
const [, setRerenderNumber] = useState(0);
|
||||
|
||||
|
|
@ -34,34 +36,48 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
return char;
|
||||
}
|
||||
|
||||
const errorHandler = (msg: string) => setError(msg);
|
||||
|
||||
const startHandler = useCallback(() => {
|
||||
setSpeed(0);
|
||||
setInterpreter(new Interpreter(input, outHandler, inputHandler, errorHandler));
|
||||
if (input[1].directStart) {
|
||||
setSpeed(100);
|
||||
} else {
|
||||
setSpeed(0);
|
||||
}
|
||||
|
||||
setStartTime(Date.now);
|
||||
setInterpreter(new Interpreter(input, outHandler, inputHandler));
|
||||
setRunning(false);
|
||||
setRunning(true);
|
||||
}, [input, outHandler, setRunning]);
|
||||
|
||||
const stopHandler = () => setRunning(false);
|
||||
const stopHandler = () => {
|
||||
setRunning(false);
|
||||
setInfo(null);
|
||||
}
|
||||
|
||||
const nextHandler = useCallback(() => {
|
||||
setError(null);
|
||||
interpreter?.next();
|
||||
if (interpreter?.reachedEnd) {
|
||||
setInfo(null);
|
||||
try {
|
||||
interpreter?.next();
|
||||
} catch (e) {
|
||||
setInfo(e.message);
|
||||
setSpeed(0);
|
||||
}
|
||||
if (interpreter?.reachedEnd) {
|
||||
setSpeed(0);
|
||||
setInfo(`Finished Execution. Took ${(Date.now() - startTime) / 1000}s`)
|
||||
}
|
||||
setRerenderNumber(n => n + 1);
|
||||
}, [interpreter]);
|
||||
}, [interpreter, startTime]);
|
||||
|
||||
useEffect(() => {
|
||||
if (running) {
|
||||
if (speed === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
nextHandler();
|
||||
}, 1000 / (speed));
|
||||
}, 1000 / (speed * 10));
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
|
|
@ -77,23 +93,28 @@ const Runner = ({setRunning, running, outHandler, input}: RunInfoProps) => {
|
|||
</>
|
||||
}
|
||||
<div>
|
||||
<button onClick={stopHandler}>Back</button>
|
||||
<button onClick={startHandler}>Start</button>
|
||||
<button onClick={nextHandler}>Next</button>
|
||||
{running && <button onClick={stopHandler}>Back</button>}
|
||||
<button onClick={startHandler}>{running ? "Restart" : "Start"}</button>
|
||||
{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>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
{
|
||||
error && <div className="error">{error}</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>
|
||||
}
|
||||
{info && <div className="info">{info}</div>}
|
||||
{
|
||||
running && <div>
|
||||
<div>Input:</div>
|
||||
|
|
|
|||
9
ibfi-ts/src/presets.json
Normal file
9
ibfi-ts/src/presets.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue