Replace sample language with deadfish
This commit is contained in:
parent
bb9e85f422
commit
3ace388d90
21
engines/deadfish/README.md
Normal file
21
engines/deadfish/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Brainfuck
|
||||
|
||||
## Allowed symbols
|
||||
|
||||
- `>`: Move the pointer to the right
|
||||
- `<`: Move the pointer to the left
|
||||
- `+`: Increment the memory cell at the pointer
|
||||
- `-`: Decrement the memory cell at the pointer
|
||||
- `.`: Output the character signified by the cell at the pointer
|
||||
- `,`: Input a character and store it in the cell at the pointer
|
||||
- `[`: Jump past the matching `]` if the cell at the pointer is 0
|
||||
- `]`: Jump back to the matching `[` if the cell at the pointer is nonzero
|
||||
|
||||
## Memory specifications
|
||||
|
||||
> These parameters will be configurable when engine configuration is added to the project
|
||||
|
||||
- For Turing-completeness, the number of cells is kept unbounded.
|
||||
- Cell size is 8 bits, and allows values in the range `[-128, 127]`.
|
||||
- Value `10` is designated for newlines.
|
||||
- The value `0` is returned on reaching `EOF`.
|
45
engines/deadfish/constants.ts
Normal file
45
engines/deadfish/constants.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { MonacoTokensProvider } from "../types";
|
||||
|
||||
export type DFRS = {
|
||||
value: number;
|
||||
};
|
||||
|
||||
export enum DF_OP {
|
||||
INCR = "i",
|
||||
DECR = "d",
|
||||
SQ = "s",
|
||||
OUT = "o",
|
||||
}
|
||||
|
||||
/** A single element of the program's AST */
|
||||
export type DFAstStep = {
|
||||
instr: DF_OP;
|
||||
location: { line: number; char: number };
|
||||
};
|
||||
|
||||
/** Sample program printing "Hello world" */
|
||||
export const sampleProgram = [
|
||||
"iisiiiisiiiiiiiio",
|
||||
"iiiiiiiiiiiiiiiiiiiiiiiiiiiiio",
|
||||
"iiiiiiioo",
|
||||
"iiio",
|
||||
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddo",
|
||||
"dddddddddddddddddddddsddo",
|
||||
"ddddddddo",
|
||||
"iiio",
|
||||
"ddddddo",
|
||||
"ddddddddo",
|
||||
].join("\n");
|
||||
|
||||
/** Tokens provider */
|
||||
export const editorTokensProvider: MonacoTokensProvider = {
|
||||
tokenizer: {
|
||||
root: [
|
||||
[/i/, "orange"],
|
||||
[/d/, "red"],
|
||||
[/s/, "blue"],
|
||||
[/o/, "green"],
|
||||
],
|
||||
},
|
||||
defaultToken: "comment",
|
||||
};
|
87
engines/deadfish/engine.ts
Normal file
87
engines/deadfish/engine.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { setupWorker } from "../setup-worker";
|
||||
import { DocumentRange, LanguageEngine, StepExecutionResult } from "../types";
|
||||
import { DFAstStep, DFRS, DF_OP } from "./constants";
|
||||
|
||||
// Default values for internal states
|
||||
// Factories are used to create new objects on reset
|
||||
const DEFAULT_AST = (): DFAstStep[] => [];
|
||||
const DEFAULT_PC = -1;
|
||||
const DEFAULT_VALUE = 0;
|
||||
|
||||
// Instruction characters
|
||||
const OP_CHARS = Object.values(DF_OP);
|
||||
|
||||
class DeadfishLanguageEngine implements LanguageEngine<DFRS> {
|
||||
private _ast: DFAstStep[] = DEFAULT_AST();
|
||||
private _value: number = DEFAULT_VALUE;
|
||||
private _pc: number = DEFAULT_PC;
|
||||
|
||||
resetState() {
|
||||
this._ast = DEFAULT_AST();
|
||||
this._value = DEFAULT_VALUE;
|
||||
this._pc = DEFAULT_PC;
|
||||
}
|
||||
|
||||
prepare(code: string, _input: string) {
|
||||
this._ast = this.parseCode(code);
|
||||
}
|
||||
|
||||
executeStep(): StepExecutionResult<DFRS> {
|
||||
// Execute and update program counter
|
||||
let output: string | undefined = undefined;
|
||||
if (this._pc !== -1) {
|
||||
const astStep = this._ast[this._pc];
|
||||
output = this.processOp(astStep.instr);
|
||||
}
|
||||
this._pc += 1;
|
||||
|
||||
// Prepare location of next step
|
||||
let nextStepLocation: DocumentRange | null = null;
|
||||
if (this._pc < this._ast.length) {
|
||||
const { line, char } = this._ast[this._pc].location;
|
||||
const charRange = { start: char, end: char + 1 };
|
||||
nextStepLocation = { line, charRange };
|
||||
}
|
||||
|
||||
// Prepare and return execution result
|
||||
const rendererState = { value: this._value };
|
||||
return { rendererState, nextStepLocation, output };
|
||||
}
|
||||
|
||||
private parseCode(code: string) {
|
||||
const ast: DFAstStep[] = [];
|
||||
|
||||
// For each line...
|
||||
code.split("\n").forEach((line, lIdx) => {
|
||||
// For each character of this line...
|
||||
line.split("").forEach((char, cIdx) => {
|
||||
if (OP_CHARS.includes(char as DF_OP)) {
|
||||
ast.push({
|
||||
instr: char as DF_OP,
|
||||
location: { line: lIdx + 1, char: cIdx + 1 },
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given instruction and return string to push to output if any.
|
||||
*
|
||||
* @param instr Instruction to apply
|
||||
* @returns String to append to output, if any
|
||||
*/
|
||||
private processOp(instr: DF_OP): string | undefined {
|
||||
if (instr === DF_OP.INCR) ++this._value;
|
||||
else if (instr === DF_OP.DECR) --this._value;
|
||||
else if (instr === DF_OP.SQ) this._value = this._value * this._value;
|
||||
else if (instr === DF_OP.OUT) return this._value.toString();
|
||||
else throw new Error("Invalid instruction");
|
||||
|
||||
if (this._value === -1 || this._value === 256) this._value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
setupWorker(new DeadfishLanguageEngine());
|
@ -1,10 +1,11 @@
|
||||
import { Renderer } from "./renderer";
|
||||
import { LanguageProvider } from "../types";
|
||||
import { RS, sampleProgram } from "./constants";
|
||||
import { DFRS, sampleProgram, editorTokensProvider } from "./constants";
|
||||
|
||||
const provider: LanguageProvider<RS> = {
|
||||
const provider: LanguageProvider<DFRS> = {
|
||||
Renderer,
|
||||
sampleProgram,
|
||||
editorTokensProvider,
|
||||
};
|
||||
|
||||
export default provider;
|
@ -1,5 +1,5 @@
|
||||
import { RendererProps } from "../types";
|
||||
import { RS } from "./constants";
|
||||
import { DFRS } from "./constants";
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
@ -14,7 +14,7 @@ const styles = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Renderer = ({ state }: RendererProps<RS>) => {
|
||||
export const Renderer = ({ state }: RendererProps<DFRS>) => {
|
||||
const value = state == null ? 0 : state.value;
|
||||
return (
|
||||
<div style={styles.container}>
|
@ -1,10 +0,0 @@
|
||||
/** Type for state passed to renderer */
|
||||
export type RS = { value: number };
|
||||
|
||||
/** Sample program */
|
||||
export const sampleProgram = [
|
||||
"ADD 10",
|
||||
"SUBTRACT 4",
|
||||
"MULTIPLY 3",
|
||||
"DIVIDE 2",
|
||||
].join("\n");
|
@ -1,124 +0,0 @@
|
||||
import { setupWorker } from "../setup-worker";
|
||||
import { LanguageEngine, StepExecutionResult } from "../types";
|
||||
import { RS } from "./constants";
|
||||
|
||||
// Default values for internal engine parameters
|
||||
const DEFAULT_AST: ASTStep[] = [];
|
||||
const DEFAULT_VALUE = 0;
|
||||
const DEFAULT_PC = -1;
|
||||
const DEFAULT_INPUT: number[] = [];
|
||||
const DEFAULT_INPUT_PC = 0;
|
||||
|
||||
/** Valid op keywords */
|
||||
enum OP_KEYWORD {
|
||||
ADD = "ADD",
|
||||
SUBTRACT = "SUBTRACT",
|
||||
MULTIPLY = "MULTIPLY",
|
||||
DIVIDE = "DIVIDE",
|
||||
}
|
||||
|
||||
/** Keyword used as value for using user input */
|
||||
const inputKeyword = "input";
|
||||
|
||||
type ASTStep = {
|
||||
/** Line number the step is located on */
|
||||
index: number;
|
||||
|
||||
/** Keyword and value of the step */
|
||||
step: { keyword: OP_KEYWORD; value: number | typeof inputKeyword };
|
||||
};
|
||||
|
||||
class SampleLanguageEngine implements LanguageEngine<RS> {
|
||||
private _ast: ASTStep[] = DEFAULT_AST;
|
||||
private _value: number = DEFAULT_VALUE;
|
||||
private _pc: number = DEFAULT_PC;
|
||||
private _input: number[] = DEFAULT_INPUT;
|
||||
private _inputPc: number = DEFAULT_INPUT_PC;
|
||||
|
||||
prepare(code: string, input: string): void {
|
||||
// Parse and load code
|
||||
const lines = code.split("\n").map((l) => l.trim());
|
||||
this._ast = lines.map((line, index) => {
|
||||
const astStep = this.parseLine(line);
|
||||
return { index: index + 1, step: astStep };
|
||||
});
|
||||
// Parse and load input
|
||||
const inputWords = input.split(/\s+/); // Split on whitespace
|
||||
this._input = inputWords.map((w) => parseInt(w, 10));
|
||||
}
|
||||
|
||||
executeStep(): StepExecutionResult<RS> {
|
||||
if (this._pc === -1) {
|
||||
// Initial dummy step
|
||||
this._pc += 1;
|
||||
return {
|
||||
rendererState: { value: this._value },
|
||||
nextStepLocation: { line: 1 },
|
||||
};
|
||||
}
|
||||
|
||||
// Execute step
|
||||
if (this._pc !== -1) {
|
||||
const step = this._ast[this._pc];
|
||||
this.processOp(step.step);
|
||||
}
|
||||
const rendererState = { value: this._value };
|
||||
|
||||
// Increment pc and return
|
||||
this._pc += 1;
|
||||
if (this._pc >= this._ast.length) {
|
||||
// Program execution is complete
|
||||
return {
|
||||
rendererState,
|
||||
nextStepLocation: null,
|
||||
output: this._value.toString(),
|
||||
};
|
||||
} else {
|
||||
// Add location of next line to be executed
|
||||
const lineNum = this._ast[this._pc].index;
|
||||
return { rendererState, nextStepLocation: { line: lineNum } };
|
||||
}
|
||||
}
|
||||
|
||||
resetState(): void {
|
||||
this._ast = DEFAULT_AST;
|
||||
this._pc = DEFAULT_PC;
|
||||
this._value = DEFAULT_VALUE;
|
||||
this._input = DEFAULT_INPUT;
|
||||
this._inputPc = DEFAULT_INPUT_PC;
|
||||
}
|
||||
|
||||
private processOp(step: ASTStep["step"]) {
|
||||
// Handle user input
|
||||
let value = 0;
|
||||
if (step.value === "input") value = this._input[this._inputPc++];
|
||||
else value = step.value;
|
||||
|
||||
// Modify runtime value according to instruction
|
||||
if (step.keyword === OP_KEYWORD.ADD) this._value += value;
|
||||
else if (step.keyword === OP_KEYWORD.SUBTRACT) this._value -= value;
|
||||
else if (step.keyword === OP_KEYWORD.MULTIPLY) this._value *= value;
|
||||
else if (step.keyword === OP_KEYWORD.DIVIDE) this._value /= value;
|
||||
}
|
||||
|
||||
private parseLine = (line: string): ASTStep["step"] => {
|
||||
// Check that line has two words
|
||||
const words = line.split(" ");
|
||||
if (words.length !== 2) throw new Error("Invalid line");
|
||||
|
||||
// Check that keyword is valid
|
||||
const [keyword, value] = words;
|
||||
if (!(keyword in OP_KEYWORD)) throw new Error("Invalid keyword");
|
||||
|
||||
// Check that value is valid
|
||||
const valueAsNum = parseInt(value, 10);
|
||||
const isInvalidValue = value !== inputKeyword && Number.isNaN(valueAsNum);
|
||||
if (isInvalidValue) throw new Error("Invalid value");
|
||||
|
||||
// Return as an AST step
|
||||
const validValue = value === inputKeyword ? inputKeyword : valueAsNum;
|
||||
return { keyword: keyword as OP_KEYWORD, value: validValue };
|
||||
};
|
||||
}
|
||||
|
||||
setupWorker(new SampleLanguageEngine());
|
@ -3,9 +3,9 @@ import { NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import { Mainframe } from "../../ui/Mainframe";
|
||||
import { Header } from "../../ui/header";
|
||||
import LangProvider from "../../engines/sample-lang";
|
||||
const LANG_ID = "sample-lang";
|
||||
const LANG_NAME = "Sample";
|
||||
import LangProvider from "../../engines/deadfish";
|
||||
const LANG_ID = "deadfish";
|
||||
const LANG_NAME = "Deadfish";
|
||||
|
||||
const IDE: NextPage = () => {
|
||||
return (
|
Loading…
x
Reference in New Issue
Block a user