mirror of
https://github.com/Noratrieb/brainfuck.git
synced 2026-01-14 13:35:00 +01:00
works
This commit is contained in:
parent
e815fd8c66
commit
5f6d1d5b4b
8 changed files with 225 additions and 52 deletions
|
|
@ -35,6 +35,31 @@ $font-color: ghostwhite;
|
|||
}
|
||||
}
|
||||
|
||||
.code-display-wrapper {
|
||||
max-width: 80vw;
|
||||
|
||||
span {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.memory-display-table {
|
||||
$border: 1px solid $font-color;
|
||||
|
||||
th, .cell {
|
||||
border: $border;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
|
||||
}
|
||||
|
||||
th, td {
|
||||
width: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.bf-run {
|
||||
margin: 20px;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,31 @@
|
|||
import './App.scss';
|
||||
import CodeInput from "./components/CodeInput";
|
||||
import ProgramOutput from "./components/ProgramOutput";
|
||||
import React, {useState} from "react";
|
||||
import Interpreter from "./brainfuck/Interpreter";
|
||||
import RunInfo from "./components/RunInfo";
|
||||
import React, {useCallback, useState} from "react";
|
||||
import Runner from "./components/Runner";
|
||||
|
||||
function App() {
|
||||
const [interpreter, setInterpreter] = useState<Interpreter | null>(null);
|
||||
|
||||
const [out, setOut] = useState("");
|
||||
const [input, setInput] = useState("");
|
||||
const [running, setRunning] = useState(false);
|
||||
|
||||
const outHandler = (char: number) => {
|
||||
const outHandler = useCallback((char: number) => {
|
||||
setOut(out => out + String.fromCharCode(char))
|
||||
}
|
||||
}, []);
|
||||
|
||||
const inHandler = (): number => {
|
||||
const inHandler = useCallback((): number => {
|
||||
return 65;
|
||||
}
|
||||
|
||||
const start = () => setInterpreter(new Interpreter(input, outHandler, inHandler));
|
||||
const next = () => interpreter?.next();
|
||||
const prev = () => interpreter?.prev();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="App-header">
|
||||
<CodeInput setInput={input => setInput(input)}/>
|
||||
<RunInfo nextHandler={next} prevHandler={prev} startHandler={start} interpreter={interpreter}/>
|
||||
<ProgramOutput text={out}/>
|
||||
{
|
||||
!running && <CodeInput setInput={input => setInput(input)}/>
|
||||
}
|
||||
<Runner running={running} setRunning={setRunning} input={input} outHandler={outHandler} inHandler={inHandler}/>
|
||||
{
|
||||
running && <ProgramOutput text={out}/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ export default class Interpreter {
|
|||
private readonly _code: string;
|
||||
private _codePointer: number;
|
||||
|
||||
private _inHandler: (() => number);
|
||||
private _outHandler: ((char: number) => void);
|
||||
private readonly _inHandler: (() => number);
|
||||
private readonly _outHandler: ((char: number) => void);
|
||||
|
||||
constructor(code: string, outHandler: ((char: number) => void), inHandler: (() => number)) {
|
||||
const buf = new ArrayBuffer(32000);
|
||||
|
|
@ -18,7 +18,7 @@ export default class Interpreter {
|
|||
}
|
||||
|
||||
public next() {
|
||||
switch (this._code[++this._codePointer]) {
|
||||
switch (this._code[this._codePointer++]) {
|
||||
case '+':
|
||||
this._array[this._pointer]++;
|
||||
break;
|
||||
|
|
@ -32,27 +32,47 @@ export default class Interpreter {
|
|||
this._pointer--;
|
||||
break;
|
||||
case '.':
|
||||
this._outHandler(this._array[this._pointer]);
|
||||
this._outHandler(this.value);
|
||||
break;
|
||||
case ',':
|
||||
this._array[this._pointer] = this._inHandler();
|
||||
break;
|
||||
case '[':
|
||||
console.error("does not support [ for now")
|
||||
if (this.value === 0) {
|
||||
let level = 0;
|
||||
while (this.instruction !== ']' || level > -1) {
|
||||
this._codePointer++;
|
||||
if (this.instruction === '[') level++;
|
||||
else if (this.instruction === ']') level--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
console.error("does not support ] for now")
|
||||
if (this.value !== 0) {
|
||||
let level = 0;
|
||||
while (this.instruction !== '[' || level > -1) {
|
||||
this._codePointer--;
|
||||
if (this.instruction === '[') level--;
|
||||
else if (this.instruction === ']') level++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case undefined:
|
||||
console.warn("reached end");
|
||||
break;
|
||||
default: {
|
||||
}
|
||||
}
|
||||
console.log("next step")
|
||||
console.log(`char: ${this.code[this.codePointer - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`)
|
||||
}
|
||||
|
||||
public prev() {
|
||||
|
||||
}
|
||||
|
||||
get instruction(): string {
|
||||
return this._code[this._codePointer];
|
||||
}
|
||||
|
||||
get value(): number {
|
||||
return this._array[this._pointer];
|
||||
|
|
|
|||
22
ibfi-ts/src/components/CodeDisplay.tsx
Normal file
22
ibfi-ts/src/components/CodeDisplay.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
|
||||
interface CodeDisplayProps {
|
||||
code: string,
|
||||
index: number,
|
||||
}
|
||||
|
||||
const CodeDisplay = ({code, index}: CodeDisplayProps) => {
|
||||
|
||||
const firstCodePart = code.substr(0, index);
|
||||
const secondCodePart = code.substr(index + 1, code.length - index + 1);
|
||||
|
||||
return (
|
||||
<div className="code-display-wrapper">
|
||||
<span>{firstCodePart}</span>
|
||||
<span style={{backgroundColor: "red"}}>{code[index]}</span>
|
||||
<span>{secondCodePart}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeDisplay;
|
||||
|
|
@ -7,14 +7,23 @@ interface CodeInputProps {
|
|||
const CodeInput = ({setInput}: CodeInputProps) => {
|
||||
const [fontSize, setFontSize] = useState(40);
|
||||
|
||||
const setStart = () => {
|
||||
setInput(
|
||||
"+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+."
|
||||
+ "+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+.+."
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bf-input">
|
||||
<div>
|
||||
<label htmlFor="bf-input-fontsize-range">Font Size</label>
|
||||
<input type="range" id="bf-input-fontsize-range" onChange={v => setFontSize(+v.target.value)}/>
|
||||
<button onClick={setStart}>set init</button>
|
||||
</div>
|
||||
<textarea onChange={e => setInput(e.target.value)} style={{fontSize}} className="code-input" placeholder="Input your code here..."/>
|
||||
<textarea onChange={e => setInput(e.target.value)} style={{fontSize}} className="code-input"
|
||||
placeholder="Input your code here..."/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
52
ibfi-ts/src/components/RunDisplay.tsx
Normal file
52
ibfi-ts/src/components/RunDisplay.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import React from 'react';
|
||||
import Interpreter from "../brainfuck/Interpreter";
|
||||
|
||||
const MAX_TABLE_COLUMNS = 30;
|
||||
|
||||
interface RunDisplayProps {
|
||||
interpreter: Interpreter,
|
||||
}
|
||||
|
||||
const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
||||
|
||||
const index = interpreter.pointer;
|
||||
|
||||
let offset: number;
|
||||
|
||||
if (index < MAX_TABLE_COLUMNS / 2) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = index - MAX_TABLE_COLUMNS / 2;
|
||||
}
|
||||
|
||||
const arrayWithIndex = Array(MAX_TABLE_COLUMNS).fill(0)
|
||||
.map((_, i) => i + offset);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="memory-display-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{
|
||||
arrayWithIndex.map((n => <th key={n}>{n}</th>))
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
{
|
||||
arrayWithIndex.map((n) => <td className="cell" key={n}>{interpreter.array[n]}</td>)
|
||||
}
|
||||
</tr>
|
||||
<tr>
|
||||
{
|
||||
arrayWithIndex.map((n) => <td className="pointer" key={n}>{interpreter.pointer === n && "^"}</td>)
|
||||
}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RunDisplay;
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react';
|
||||
import Interpreter from "../brainfuck/Interpreter";
|
||||
|
||||
interface RunInfoProps {
|
||||
nextHandler: () => void,
|
||||
prevHandler: () => void,
|
||||
startHandler: () => void,
|
||||
interpreter: Interpreter | null,
|
||||
}
|
||||
|
||||
const RunInfo = ({interpreter, ...props}: RunInfoProps) => {
|
||||
return (
|
||||
<div className="bf-run">
|
||||
<div>
|
||||
<button onClick={props.startHandler}>Start</button>
|
||||
<button onClick={props.nextHandler}>Next</button>
|
||||
<button onClick={props.prevHandler}>Previous</button>
|
||||
</div>
|
||||
|
||||
{interpreter &&
|
||||
<div>Pointer: {interpreter.pointer} value {interpreter.value}</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
export default RunInfo;
|
||||
75
ibfi-ts/src/components/Runner.tsx
Normal file
75
ibfi-ts/src/components/Runner.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import Interpreter from "../brainfuck/Interpreter";
|
||||
import CodeDisplay from "./CodeDisplay";
|
||||
import RunDisplay from "./RunDisplay";
|
||||
|
||||
interface RunInfoProps {
|
||||
input: string,
|
||||
setRunning: (running: boolean) => void,
|
||||
running: boolean
|
||||
inHandler: () => number,
|
||||
outHandler: (char: number) => void,
|
||||
}
|
||||
|
||||
const Runner = ({setRunning, running, inHandler, outHandler, input}: RunInfoProps) => {
|
||||
const [speed, setSpeed] = useState(0);
|
||||
const [interpreter, setInterpreter] = useState<Interpreter | null>(null);
|
||||
|
||||
const [, setRerenderNumber] = useState(0);
|
||||
|
||||
const startHandler = useCallback(() => {
|
||||
setSpeed(0);
|
||||
setInterpreter(new Interpreter(input, outHandler, inHandler));
|
||||
setRunning(true);
|
||||
}, [input, inHandler, outHandler, setRunning]);
|
||||
|
||||
const stopHandler = () => setRunning(false);
|
||||
|
||||
const nextHandler = useCallback(() => {
|
||||
interpreter?.next();
|
||||
setRerenderNumber(n => n + 1);
|
||||
}, [interpreter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (running) {
|
||||
if (speed === 0) {
|
||||
return;
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
nextHandler();
|
||||
}, 1000 / (speed));
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [running, nextHandler, speed]);
|
||||
|
||||
|
||||
return (
|
||||
<div className="bf-run">
|
||||
{running && interpreter && <>
|
||||
<CodeDisplay code={input} index={interpreter.codePointer}/>
|
||||
<RunDisplay interpreter={interpreter}/>
|
||||
</>
|
||||
}
|
||||
<div>
|
||||
<button onClick={startHandler}>Start</button>
|
||||
<button onClick={stopHandler}>Stop</button>
|
||||
<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>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
;
|
||||
|
||||
export default Runner;
|
||||
Loading…
Add table
Add a link
Reference in a new issue