This commit is contained in:
nora 2021-06-24 14:42:26 +02:00
parent a96567038d
commit 579d81021a
7 changed files with 108 additions and 46 deletions

View file

@ -51,12 +51,8 @@ $medium-color: #78787f;
border: $border; border: $border;
} }
.pointer {
}
th, td { th, td {
width: 30px; min-width: 60px;
text-align: center; text-align: center;
} }
} }
@ -69,6 +65,13 @@ $medium-color: #78787f;
width: 200px; width: 200px;
} }
.program-input-area {
resize: none;
width: 80vw;
height: 50px;
font-size: 30px;
}
.error { .error {
background-color: #664242FF; background-color: #664242FF;
} }

View file

@ -1,22 +1,18 @@
import './App.scss'; import './App.scss';
import CodeInput from "./components/CodeInput"; import CodeInput, {CodeOptions} from "./components/CodeInput";
import ProgramOutput from "./components/ProgramOutput"; 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";
function App() { function App() {
const [out, setOut] = useState(""); const [out, setOut] = useState("");
const [input, setInput] = useState(""); const [input, setInput] = useState<[string, CodeOptions]>(["", {}]);
const [running, setRunning] = useState(false); const [running, setRunning] = useState(false);
const outHandler = useCallback((char: number) => { const outHandler = useCallback((char: number) => {
setOut(out => out + String.fromCharCode(char)) setOut(out => out + String.fromCharCode(char))
}, []); }, []);
const inHandler = useCallback((): number => {
return 65;
}, []);
const runHandler = (run: boolean) => { const runHandler = (run: boolean) => {
setRunning(run); setRunning(run);
if (!run) { if (!run) {
@ -24,12 +20,14 @@ function App() {
} }
} }
const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]);
return ( return (
<div className="App-header"> <div className="App-header">
{ {
!running && <CodeInput setInput={input => setInput(input)}/> !running && <CodeInput code={input[0]} setInput={inputHandler}/>
} }
<Runner running={running} setRunning={runHandler} input={input} outHandler={outHandler} inHandler={inHandler}/> <Runner running={running} setRunning={runHandler} input={input} outHandler={outHandler}/>
{ {
running && <ProgramOutput text={out}/> running && <ProgramOutput text={out}/>
} }

View file

@ -1,3 +1,5 @@
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); type ErrorHandler = ((msg: string) => void);
@ -6,25 +8,31 @@ export default class Interpreter {
private readonly _array: Uint8Array; private readonly _array: Uint8Array;
private _pointer: number; private _pointer: number;
private readonly _code: string; private readonly _code: string;
private _codePointer: number; private _programCounter: number;
private readonly _inHandler: InHandler; private readonly _inHandler: InHandler;
private readonly _outHandler: OutHandler; private readonly _outHandler: OutHandler;
private readonly _errorHandler: ErrorHandler; private readonly _errorHandler: ErrorHandler;
constructor(code: string, outHandler: OutHandler, inHandler: InHandler, errorHandler: ErrorHandler) { constructor(input: [string, CodeOptions], outHandler: OutHandler, inHandler: InHandler, errorHandler: ErrorHandler) {
const buf = new ArrayBuffer(32000); const buf = new ArrayBuffer(32000);
this._array = new Uint8Array(buf); this._array = new Uint8Array(buf);
this._pointer = 0; this._pointer = 0;
this._code = code;
this._codePointer = 0; if (input[1].minify) {
this._code = minify(input[0])
} else {
this._code = input[0];
}
this._programCounter = 0;
this._inHandler = inHandler; this._inHandler = inHandler;
this._outHandler = outHandler; this._outHandler = outHandler;
this._errorHandler = errorHandler; this._errorHandler = errorHandler;
} }
public next() { public next() {
switch (this._code[this._codePointer++]) { switch (this._code[this._programCounter++]) {
case '+': case '+':
this._array[this._pointer]++; this._array[this._pointer]++;
break; break;
@ -45,13 +53,18 @@ export default class Interpreter {
this._outHandler(this.value); this._outHandler(this.value);
break; break;
case ',': case ',':
this._array[this._pointer] = this._inHandler(); try {
this._array[this._pointer] = this._inHandler();
} catch {
this._programCounter--;
this._errorHandler("Could not read input, trying again next time.")
}
break; break;
case '[': case '[':
if (this.value === 0) { if (this.value === 0) {
let level = 0; let level = 0;
while (this.lastInstruction !== ']' || level > -1) { while (this.lastInstruction !== ']' || level > -1) {
this._codePointer++; this._programCounter++;
if (this.lastInstruction === '[') level++; if (this.lastInstruction === '[') level++;
else if (this.lastInstruction === ']') level--; else if (this.lastInstruction === ']') level--;
} }
@ -61,20 +74,20 @@ export default class Interpreter {
if (this.value !== 0) { if (this.value !== 0) {
let level = 0; let level = 0;
while (this.lastInstruction !== '[' || level > -1) { while (this.lastInstruction !== '[' || level > -1) {
this._codePointer--; this._programCounter--;
if (this.lastInstruction === '[') level--; if (this.lastInstruction === '[') level--;
else if (this.lastInstruction === ']') level++; else if (this.lastInstruction === ']') level++;
} }
} }
break; break;
case undefined: case undefined:
this._pointer--; this._pointer = this._code.length;
console.warn("reached end"); console.warn("reached end");
break; break;
default: { default: {
} }
} }
console.log(`char: ${this.code[this.codePointer - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`) console.log(`char: ${this.code[this.programCounter - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`)
} }
public prev() { public prev() {
@ -82,11 +95,11 @@ export default class Interpreter {
} }
get reachedEnd(): boolean { get reachedEnd(): boolean {
return this._codePointer === this._code.length - 1; return this._programCounter === this._code.length;
} }
get lastInstruction(): string { get lastInstruction(): string {
return this._code[this._codePointer - 1]; return this._code[this._programCounter - 1];
} }
get value(): number { get value(): number {
@ -105,7 +118,14 @@ export default class Interpreter {
return this._code; return this._code;
} }
get codePointer(): number { get programCounter(): number {
return this._codePointer; return this._programCounter;
} }
} }
const CHARS = ['+', '-', '<', '>', '.', ',', '[', ']'];
const minify = (code: string): string =>
code.split("")
.filter(c => CHARS.includes(c))
.join("");

View file

@ -13,7 +13,7 @@ const CodeDisplay = ({code, index}: CodeDisplayProps) => {
return ( return (
<div className="code-display-wrapper"> <div className="code-display-wrapper">
<span>{firstCodePart}</span> <span>{firstCodePart}</span>
<span style={{backgroundColor: "red"}}>{code[index]}</span> <span style={{backgroundColor: "red"}}>{code[index] || " "}</span>
<span>{secondCodePart}</span> <span>{secondCodePart}</span>
</div> </div>
); );

View file

@ -1,27 +1,48 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
interface CodeInputProps { export interface CodeOptions {
setInput: ((input: string) => void), minify?: boolean
} }
const CodeInput = ({setInput}: CodeInputProps) => { interface CodeInputProps {
setInput: ((code: string, options: CodeOptions) => void),
code: string
}
const CodeInput = ({code, setInput}: CodeInputProps) => {
const [fontSize, setFontSize] = useState(40); const [fontSize, setFontSize] = useState(40);
const [codeOptions, setCodeOptions] = useState<CodeOptions>({});
const setStart = () => { const setStart = () => {
setInput( setInput(
"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++."); "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
codeOptions);
}
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, minify: e.target.checked}))
setInput(code, codeOptions);
} }
return ( return (
<div> <div>
<div className="bf-input"> <div className="bf-input">
<div> <div>
<span>
<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)}/>
<button onClick={setStart}>set init</button> </span>
<input type="checkbox" checked={codeOptions.minify} id="input-options-minify" onChange={changeMinify}/>
<label htmlFor="input-options-minify">Minify Code</label>
</div> </div>
<textarea onChange={e => setInput(e.target.value)} style={{fontSize}} className="code-input" <textarea value={code} onChange={e => setInput(e.target.value, codeOptions)} style={{fontSize}}
className="code-input"
placeholder="Input your code here..."/> placeholder="Input your code here..."/>
<div>
<button onClick={setStart}>Set Hello World</button>
</div>
</div> </div>
</div> </div>
); );

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import Interpreter from "../brainfuck/Interpreter"; import Interpreter from "../brainfuck/Interpreter";
const MAX_TABLE_COLUMNS = 30; const MAX_TABLE_COLUMNS = 20;
interface RunDisplayProps { interface RunDisplayProps {
interpreter: Interpreter, interpreter: Interpreter,

View file

@ -1,33 +1,47 @@
import React, {useCallback, useEffect, useState} from 'react'; import React, {useCallback, 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";
interface RunInfoProps { interface RunInfoProps {
input: string, input: [string, CodeOptions],
setRunning: (running: boolean) => void, setRunning: (running: boolean) => void,
running: boolean running: boolean
inHandler: () => number,
outHandler: (char: number) => void, outHandler: (char: number) => void,
} }
const Runner = ({setRunning, running, inHandler, outHandler, input}: RunInfoProps) => { const Runner = ({setRunning, running, outHandler, input}: 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 [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [, setRerenderNumber] = useState(0); const [, setRerenderNumber] = useState(0);
const errorHandler = (msg: string) => { const inputArea = useRef<HTMLTextAreaElement>(null);
setError(msg);
const inputHandler = () => {
if (!inputArea.current) {
throw new Error("Could not read input")
}
const value = inputArea.current.value;
if (value.length < 1) {
throw new Error("No input found");
}
const char = value.charCodeAt(0);
inputArea.current.value = value.substr(1);
return char;
} }
const errorHandler = (msg: string) => setError(msg);
const startHandler = useCallback(() => { const startHandler = useCallback(() => {
setSpeed(0); setSpeed(0);
setInterpreter(new Interpreter(input, outHandler, inHandler, errorHandler)); setInterpreter(new Interpreter(input, outHandler, inputHandler, errorHandler));
setRunning(false); setRunning(false);
setRunning(true); setRunning(true);
}, [input, inHandler, outHandler, setRunning]); }, [input, outHandler, setRunning]);
const stopHandler = () => setRunning(false); const stopHandler = () => setRunning(false);
@ -58,7 +72,7 @@ const Runner = ({setRunning, running, inHandler, outHandler, input}: RunInfoProp
<div className="bf-run"> <div className="bf-run">
{ {
running && interpreter && <> running && interpreter && <>
<CodeDisplay code={input} index={interpreter.codePointer}/> <CodeDisplay code={interpreter.code} index={interpreter.programCounter}/>
<RunDisplay interpreter={interpreter}/> <RunDisplay interpreter={interpreter}/>
</> </>
} }
@ -78,7 +92,13 @@ const Runner = ({setRunning, running, inHandler, outHandler, input}: RunInfoProp
</> </>
} }
{ {
error && <div className="error">Error: '{error}'</div> error && <div className="error">{error}</div>
}
{
running && <div>
<div>Input:</div>
<textarea className="program-input-area" ref={inputArea}/>
</div>
} }
</div> </div>
); );