gh-pages :(

This commit is contained in:
nora 2021-06-24 15:26:43 +02:00
parent 579d81021a
commit 296fd4a279
26 changed files with 533 additions and 381 deletions

View file

@ -1,131 +1,131 @@
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;
private _pointer: number;
private readonly _code: string;
private _programCounter: number;
private readonly _inHandler: InHandler;
private readonly _outHandler: OutHandler;
private readonly _errorHandler: ErrorHandler;
constructor(input: [string, CodeOptions], outHandler: OutHandler, inHandler: InHandler, errorHandler: ErrorHandler) {
const buf = new ArrayBuffer(32000);
this._array = new Uint8Array(buf);
this._pointer = 0;
if (input[1].minify) {
this._code = minify(input[0])
} else {
this._code = input[0];
}
this._programCounter = 0;
this._inHandler = inHandler;
this._outHandler = outHandler;
this._errorHandler = errorHandler;
}
public next() {
switch (this._code[this._programCounter++]) {
case '+':
this._array[this._pointer]++;
break;
case '-':
this._array[this._pointer]--;
break;
case '>':
this._pointer++;
break;
case '<':
if (this._pointer === 0) {
this._errorHandler("Cannot wrap left");
break;
}
this._pointer--;
break;
case '.':
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.")
}
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--;
}
}
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++;
}
}
break;
case undefined:
this._pointer = this._code.length;
console.warn("reached end");
break;
default: {
}
}
console.log(`char: ${this.code[this.programCounter - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`)
}
public prev() {
}
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;
}
}
const CHARS = ['+', '-', '<', '>', '.', ',', '[', ']'];
const minify = (code: string): string =>
code.split("")
.filter(c => CHARS.includes(c))
.join("");
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;
private _pointer: number;
private readonly _code: string;
private _programCounter: number;
private readonly _inHandler: InHandler;
private readonly _outHandler: OutHandler;
private readonly _errorHandler: ErrorHandler;
constructor(input: [string, CodeOptions], outHandler: OutHandler, inHandler: InHandler, errorHandler: ErrorHandler) {
const buf = new ArrayBuffer(32000);
this._array = new Uint8Array(buf);
this._pointer = 0;
if (input[1].minify) {
this._code = minify(input[0])
} else {
this._code = input[0];
}
this._programCounter = 0;
this._inHandler = inHandler;
this._outHandler = outHandler;
this._errorHandler = errorHandler;
}
public next() {
switch (this._code[this._programCounter++]) {
case '+':
this._array[this._pointer]++;
break;
case '-':
this._array[this._pointer]--;
break;
case '>':
this._pointer++;
break;
case '<':
if (this._pointer === 0) {
this._errorHandler("Cannot wrap left");
break;
}
this._pointer--;
break;
case '.':
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.")
}
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--;
}
}
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++;
}
}
break;
case undefined:
this._pointer = this._code.length;
console.warn("reached end");
break;
default: {
}
}
console.log(`char: ${this.code[this.programCounter - 1]} pointer: ${this.pointer} value: ${this.array[this.pointer]}`)
}
public prev() {
}
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;
}
}
const CHARS = ['+', '-', '<', '>', '.', ',', '[', ']'];
const minify = (code: string): string =>
code.split("")
.filter(c => CHARS.includes(c))
.join("");

View file

@ -1,22 +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>
);
};
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;

View file

@ -1,51 +1,51 @@
import React, {useState} from 'react';
export interface CodeOptions {
minify?: boolean
}
interface CodeInputProps {
setInput: ((code: string, options: CodeOptions) => void),
code: string
}
const CodeInput = ({code, setInput}: CodeInputProps) => {
const [fontSize, setFontSize] = useState(40);
const [codeOptions, setCodeOptions] = useState<CodeOptions>({});
const setStart = () => {
setInput(
"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
codeOptions);
}
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, minify: e.target.checked}))
setInput(code, codeOptions);
}
return (
<div>
<div className="bf-input">
<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>
<input type="checkbox" checked={codeOptions.minify} id="input-options-minify" onChange={changeMinify}/>
<label htmlFor="input-options-minify">Minify Code</label>
</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>
</div>
</div>
);
};
import React, {useState} from 'react';
export interface CodeOptions {
minify?: boolean
}
interface CodeInputProps {
setInput: ((code: string, options: CodeOptions) => void),
code: string
}
const CodeInput = ({code, setInput}: CodeInputProps) => {
const [fontSize, setFontSize] = useState(40);
const [codeOptions, setCodeOptions] = useState<CodeOptions>({});
const setStart = () => {
setInput(
"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
codeOptions);
}
const changeMinify = (e: React.ChangeEvent<HTMLInputElement>) => {
setCodeOptions(old => ({...old, minify: e.target.checked}))
setInput(code, codeOptions);
}
return (
<div>
<div className="bf-input">
<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>
<input type="checkbox" checked={codeOptions.minify} id="input-options-minify" onChange={changeMinify}/>
<label htmlFor="input-options-minify">Minify Code</label>
</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>
</div>
</div>
);
};
export default CodeInput;

View file

@ -1,15 +1,15 @@
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>
);
};
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;

View file

@ -1,53 +1,53 @@
import React from 'react';
import Interpreter from "../brainfuck/Interpreter";
const MAX_TABLE_COLUMNS = 20;
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>
);
};
import React from 'react';
import Interpreter from "../brainfuck/Interpreter";
const MAX_TABLE_COLUMNS = 20;
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;

View file

@ -1,107 +1,107 @@
import React, {useCallback, useEffect, useRef, useState} from 'react';
import Interpreter from "../brainfuck/Interpreter";
import CodeDisplay from "./CodeDisplay";
import RunDisplay from "./RunDisplay";
import {CodeOptions} from "./CodeInput";
interface RunInfoProps {
input: [string, CodeOptions],
setRunning: (running: boolean) => void,
running: boolean
outHandler: (char: number) => void,
}
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 [, setRerenderNumber] = useState(0);
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 errorHandler = (msg: string) => setError(msg);
const startHandler = useCallback(() => {
setSpeed(0);
setInterpreter(new Interpreter(input, outHandler, inputHandler, errorHandler));
setRunning(false);
setRunning(true);
}, [input, outHandler, setRunning]);
const stopHandler = () => setRunning(false);
const nextHandler = useCallback(() => {
setError(null);
interpreter?.next();
if (interpreter?.reachedEnd) {
setSpeed(0);
}
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={interpreter.code} index={interpreter.programCounter}/>
<RunDisplay interpreter={interpreter}/>
</>
}
<div>
<button onClick={stopHandler}>Back</button>
<button onClick={startHandler}>Start</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>
</>
}
{
error && <div className="error">{error}</div>
}
{
running && <div>
<div>Input:</div>
<textarea className="program-input-area" ref={inputArea}/>
</div>
}
</div>
);
};
import React, {useCallback, useEffect, useRef, useState} from 'react';
import Interpreter from "../brainfuck/Interpreter";
import CodeDisplay from "./CodeDisplay";
import RunDisplay from "./RunDisplay";
import {CodeOptions} from "./CodeInput";
interface RunInfoProps {
input: [string, CodeOptions],
setRunning: (running: boolean) => void,
running: boolean
outHandler: (char: number) => void,
}
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 [, setRerenderNumber] = useState(0);
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 errorHandler = (msg: string) => setError(msg);
const startHandler = useCallback(() => {
setSpeed(0);
setInterpreter(new Interpreter(input, outHandler, inputHandler, errorHandler));
setRunning(false);
setRunning(true);
}, [input, outHandler, setRunning]);
const stopHandler = () => setRunning(false);
const nextHandler = useCallback(() => {
setError(null);
interpreter?.next();
if (interpreter?.reachedEnd) {
setSpeed(0);
}
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={interpreter.code} index={interpreter.programCounter}/>
<RunDisplay interpreter={interpreter}/>
</>
}
<div>
<button onClick={stopHandler}>Back</button>
<button onClick={startHandler}>Start</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>
</>
}
{
error && <div className="error">{error}</div>
}
{
running && <div>
<div>Input:</div>
<textarea className="program-input-area" ref={inputArea}/>
</div>
}
</div>
);
};
export default Runner;

View file

@ -1 +1 @@
/// <reference types="react-scripts" />
/// <reference types="react-scripts" />