Replace sample language with deadfish
This commit is contained in:
		
							
								
								
									
										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 ( | ||||
		Reference in New Issue
	
	Block a user
	 Nilay Majorwar
					Nilay Majorwar