mirror of
https://github.com/Noratrieb/brainfuck.git
synced 2026-01-14 13:35:00 +01:00
wtf
wtf
This commit is contained in:
parent
d5520e1f42
commit
7d352e0b5c
12 changed files with 0 additions and 786 deletions
|
|
@ -1,128 +0,0 @@
|
||||||
$main-color: #282c34;
|
|
||||||
$main-color-brighter: #323942;
|
|
||||||
$light-color: ghostwhite;
|
|
||||||
$medium-color: #78787f;
|
|
||||||
|
|
||||||
.App {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-logo {
|
|
||||||
height: 40vmin;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-header {
|
|
||||||
background-color: $main-color;
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: $light-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-link {
|
|
||||||
color: #61dafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bf-input {
|
|
||||||
.code-input {
|
|
||||||
resize: none;
|
|
||||||
width: 80vw;
|
|
||||||
height: 400px;
|
|
||||||
font-size: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-options-wrapper > * {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.code-display-wrapper {
|
|
||||||
max-width: 80vw;
|
|
||||||
font-family: monospace;
|
|
||||||
|
|
||||||
span {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.memory-display-table {
|
|
||||||
$border: 1px solid $light-color;
|
|
||||||
|
|
||||||
th, .cell {
|
|
||||||
border: $border;
|
|
||||||
}
|
|
||||||
|
|
||||||
th, td {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.run-button {
|
|
||||||
height: 50px;
|
|
||||||
width: 200px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.bf-run {
|
|
||||||
margin: 20px;
|
|
||||||
|
|
||||||
|
|
||||||
.speed-control-wrapper > * {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-speed-button {
|
|
||||||
height: 40px;
|
|
||||||
width: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.program-input-area {
|
|
||||||
resize: none;
|
|
||||||
width: 80vw;
|
|
||||||
height: 50px;
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
background-color: #579ca7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bf-output {
|
|
||||||
.output-area {
|
|
||||||
resize: none;
|
|
||||||
width: 80vw;
|
|
||||||
height: 200px;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
background-color: $main-color-brighter;
|
|
||||||
color: $light-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: $medium-color;
|
|
||||||
font-size: 20px;
|
|
||||||
border: 1px solid $main-color;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: $light-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
import {CodeOptions} from "../components/CodeInput";
|
|
||||||
|
|
||||||
type InHandler = (() => number);
|
|
||||||
type OutHandler = ((char: number) => void);
|
|
||||||
|
|
||||||
export default class Interpreter {
|
|
||||||
private readonly _array: Uint8Array;
|
|
||||||
private _pointer: number;
|
|
||||||
private readonly _code: string;
|
|
||||||
private _programCounter: number;
|
|
||||||
|
|
||||||
private readonly _inHandler: InHandler;
|
|
||||||
private readonly _outHandler: OutHandler;
|
|
||||||
|
|
||||||
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 = this.minify(input[0])
|
|
||||||
} else {
|
|
||||||
this._code = input[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
this._programCounter = 0;
|
|
||||||
this._inHandler = inHandler;
|
|
||||||
this._outHandler = outHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public next() {
|
|
||||||
this.execute(this._code[this._programCounter++]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public execute(char: string) {
|
|
||||||
switch (char) {
|
|
||||||
case '+':
|
|
||||||
this._array[this._pointer]++;
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
this._array[this._pointer]--;
|
|
||||||
break;
|
|
||||||
case '>':
|
|
||||||
this._pointer++;
|
|
||||||
break;
|
|
||||||
case '<':
|
|
||||||
if (this._pointer === 0) {
|
|
||||||
throw new Error("Cannot wrap left");
|
|
||||||
}
|
|
||||||
this._pointer--;
|
|
||||||
break;
|
|
||||||
case '.':
|
|
||||||
this._outHandler(this.value);
|
|
||||||
break;
|
|
||||||
case ',':
|
|
||||||
this.input();
|
|
||||||
break;
|
|
||||||
case '[':
|
|
||||||
this.loopForwards();
|
|
||||||
break;
|
|
||||||
case ']':
|
|
||||||
this.loopBackwards();
|
|
||||||
break;
|
|
||||||
case '•':
|
|
||||||
if (this._options.enableBreakpoints) {
|
|
||||||
throw new Error("Breakpoint reached");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case undefined:
|
|
||||||
this._programCounter = this._code.length;
|
|
||||||
break;
|
|
||||||
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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get reachedEnd(): boolean {
|
|
||||||
return this._programCounter === this._code.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lastInstruction(): string {
|
|
||||||
return this._code[this._programCounter - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
get value(): number {
|
|
||||||
return this._array[this._pointer];
|
|
||||||
}
|
|
||||||
|
|
||||||
get array(): Uint8Array {
|
|
||||||
return this._array;
|
|
||||||
}
|
|
||||||
|
|
||||||
get pointer(): number {
|
|
||||||
return this._pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
get code(): string {
|
|
||||||
return this._code;
|
|
||||||
}
|
|
||||||
|
|
||||||
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("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import '../App.scss';
|
|
||||||
import CodeInput, {CodeOptions} from "./CodeInput";
|
|
||||||
import ProgramOutput from "./ProgramOutput";
|
|
||||||
import React, {useCallback, useState} from "react";
|
|
||||||
import Runner from "./Runner";
|
|
||||||
|
|
||||||
export const OptionContext = React.createContext<CodeOptions>({});
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [out, setOut] = useState("");
|
|
||||||
const [input, setInput] = useState<[string, CodeOptions]>(["", {}]);
|
|
||||||
const [running, setRunning] = useState(false);
|
|
||||||
|
|
||||||
const outHandler = useCallback((char: number) => {
|
|
||||||
setOut(oldOut => oldOut + String.fromCharCode(char))
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const runHandler = (run: boolean) => {
|
|
||||||
setRunning(run);
|
|
||||||
if (!run) {
|
|
||||||
setOut("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputHandler = (code: string, options: CodeOptions) => setInput([code, options]);
|
|
||||||
return (
|
|
||||||
<div className="App-header">
|
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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">
|
|
||||||
<code>{firstCodePart}</code>
|
|
||||||
<code style={{backgroundColor: "red"}}>{code[index] || " "}</code>
|
|
||||||
<code>{secondCodePart}</code>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CodeDisplay;
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
import React, {ChangeEvent, useState} from 'react';
|
|
||||||
import presets from "../presets.json";
|
|
||||||
|
|
||||||
export interface CodeOptions {
|
|
||||||
minify?: boolean,
|
|
||||||
directStart?: boolean,
|
|
||||||
startSuperSpeed?: boolean,
|
|
||||||
enableBreakpoints?: boolean
|
|
||||||
asciiView?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CodeInputProps {
|
|
||||||
setInput: ((code: string, options: CodeOptions) => void),
|
|
||||||
input: [string, CodeOptions]
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeOptions: Array<[string, keyof CodeOptions]> = [
|
|
||||||
["Minify Code", "minify"],
|
|
||||||
["Start directly", "directStart"],
|
|
||||||
["Start in blocking mode", "startSuperSpeed"],
|
|
||||||
["Breakpoints (•)", "enableBreakpoints"],
|
|
||||||
["Show ASCII in memory", "asciiView"]
|
|
||||||
]
|
|
||||||
|
|
||||||
const CodeInput = ({input: [code, options], setInput}: CodeInputProps) => {
|
|
||||||
const [fontSize, setFontSize] = useState(40);
|
|
||||||
|
|
||||||
const setPreset = (name: keyof typeof presets) => () => {
|
|
||||||
setInput(presets[name], options);
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeHandler = (name: keyof CodeOptions) => (event: ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setInput(code, {...options, [name]: event.target.checked})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bf-input">
|
|
||||||
<div className="code-options-wrapper">
|
|
||||||
<div>
|
|
||||||
<label htmlFor="bf-input-fontsize-range">Font Size</label>
|
|
||||||
<input type="range" id="bf-input-fontsize-range" onChange={v => setFontSize(+v.target.value)}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{codeOptions.map(([display, id]) =>
|
|
||||||
<CodeOption displayName={display} name={id} 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>
|
|
||||||
<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) => (
|
|
||||||
<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,15 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
interface ProgramOutputProps {
|
|
||||||
text: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProgramOutput = ({text}: ProgramOutputProps) => {
|
|
||||||
return (
|
|
||||||
<div className="bf-output">
|
|
||||||
<textarea readOnly className="output-area" value={text}/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ProgramOutput;
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
import React, {useContext, useRef, useState} from 'react';
|
|
||||||
import Interpreter from "../brainfuck/Interpreter";
|
|
||||||
import {OptionContext} from "./App";
|
|
||||||
|
|
||||||
const MAX_TABLE_COLUMNS = 20;
|
|
||||||
|
|
||||||
interface RunDisplayProps {
|
|
||||||
interpreter: Interpreter,
|
|
||||||
}
|
|
||||||
|
|
||||||
const RunDisplay = ({interpreter}: RunDisplayProps) => {
|
|
||||||
const options = useContext(OptionContext);
|
|
||||||
|
|
||||||
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) => <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"
|
|
||||||
key={n}>{interpreter.pointer === n && "^"}</td>)
|
|
||||||
}
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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] + "");
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = ascii ?
|
|
||||||
String.fromCharCode(interpreter.array[index])
|
|
||||||
:
|
|
||||||
interpreter.array[index];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<td onClick={click} className="cell">
|
|
||||||
{
|
|
||||||
isEditing && !ascii ?
|
|
||||||
<input onKeyDown={keyDown}
|
|
||||||
className="array-set-value-field"
|
|
||||||
ref={inputField}
|
|
||||||
onChange={e => setInput(e.target.value)}
|
|
||||||
value={input}
|
|
||||||
onBlur={saveAndQuit}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
:
|
|
||||||
content
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RunDisplay;
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
import React, {useCallback, useContext, useEffect, useRef, useState} from 'react';
|
|
||||||
import Interpreter from "../brainfuck/Interpreter";
|
|
||||||
import CodeDisplay from "./CodeDisplay";
|
|
||||||
import RunDisplay from "./RunDisplay";
|
|
||||||
import {OptionContext} from "./App";
|
|
||||||
|
|
||||||
|
|
||||||
interface RunInfoProps {
|
|
||||||
running: boolean,
|
|
||||||
setRunning: (running: boolean) => void,
|
|
||||||
code: string,
|
|
||||||
outHandler: (char: number) => void,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 rerender = () => setRerenderNumber(n => n + 1);
|
|
||||||
|
|
||||||
const options = useContext(OptionContext);
|
|
||||||
|
|
||||||
const inputArea = useRef<HTMLTextAreaElement>(null);
|
|
||||||
|
|
||||||
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 startHandler = useCallback(() => {
|
|
||||||
if (options.directStart) {
|
|
||||||
if (options.startSuperSpeed) {
|
|
||||||
setSpeed(-1);
|
|
||||||
} else {
|
|
||||||
setSpeed(100);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setSpeed(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
setStartTime(Date.now);
|
|
||||||
setInterpreter(new Interpreter([code, options], outHandler, inputHandler));
|
|
||||||
setRunning(false);
|
|
||||||
setRunning(true);
|
|
||||||
}, [options, code, outHandler, setRunning]);
|
|
||||||
|
|
||||||
const stopHandler = () => {
|
|
||||||
setRunning(false);
|
|
||||||
setInfo(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextHandler = useCallback(() => {
|
|
||||||
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`)
|
|
||||||
}
|
|
||||||
rerender();
|
|
||||||
}, [interpreter, startTime]);
|
|
||||||
|
|
||||||
const runBlocking = useCallback(() => {
|
|
||||||
try {
|
|
||||||
while (speed === -1 && !interpreter?.reachedEnd) {
|
|
||||||
interpreter?.next();
|
|
||||||
}
|
|
||||||
setSpeed(0);
|
|
||||||
setInfo(`Finished Execution. Took ${(Date.now() - startTime) / 1000}s`)
|
|
||||||
} catch (e) {
|
|
||||||
setInfo(e.message);
|
|
||||||
setSpeed(0);
|
|
||||||
}
|
|
||||||
}, [speed, interpreter, startTime]);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (running) {
|
|
||||||
if (speed === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speed > 0) {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
nextHandler();
|
|
||||||
}, 1000 / (speed * 10));
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}
|
|
||||||
runBlocking();
|
|
||||||
}
|
|
||||||
}, [runBlocking, running, nextHandler, speed]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="bf-run">
|
|
||||||
{
|
|
||||||
running && interpreter && <>
|
|
||||||
<CodeDisplay code={interpreter.code} index={interpreter.programCounter}/>
|
|
||||||
<RunDisplay interpreter={interpreter}/>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
<div>
|
|
||||||
{running && <button className="run-button" onClick={stopHandler}>Back</button>}
|
|
||||||
<button className="run-button" onClick={startHandler}>{running ? "Restart" : "Start"}</button>
|
|
||||||
{running && <button className="run-button" onClick={nextHandler}>Next</button>}
|
|
||||||
</div>
|
|
||||||
{
|
|
||||||
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 className="speed-control-wrapper">
|
|
||||||
<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>
|
|
||||||
<span>
|
|
||||||
<label>Superspeed Mode (blocking)</label>
|
|
||||||
<input id="superspeed-mode-check" type="checkbox" checked={speed === -1} onChange={() => setSpeed(-1)}/>
|
|
||||||
</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,13 +0,0 @@
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
||||||
monospace;
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import ReactDOM from 'react-dom';
|
|
||||||
import './index.css';
|
|
||||||
import App from './components/App';
|
|
||||||
|
|
||||||
ReactDOM.render(
|
|
||||||
<React.StrictMode>
|
|
||||||
<App />
|
|
||||||
</React.StrictMode>,
|
|
||||||
document.getElementById('root')
|
|
||||||
);
|
|
||||||
File diff suppressed because one or more lines are too long
1
ibfi-ts/src/src/react-app-env.d.ts
vendored
1
ibfi-ts/src/src/react-app-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
||||||
/// <reference types="react-scripts" />
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue