Adapt Befunge to modify code, fix char rendering
This commit is contained in:
parent
7b7475a4fb
commit
35bebf045f
@ -1,7 +1,13 @@
|
||||
import InputStream from "./input-stream";
|
||||
import { DocumentRange, LanguageEngine, StepExecutionResult } from "../types";
|
||||
import {
|
||||
DocumentEdit,
|
||||
DocumentRange,
|
||||
LanguageEngine,
|
||||
StepExecutionResult,
|
||||
} from "../types";
|
||||
import { ParseError, RuntimeError } from "../worker-errors";
|
||||
import { Bfg93RS, Bfg93Op, Bfg93Direction } from "./constants";
|
||||
import { toSafePrintableChar } from "../engine-utils";
|
||||
|
||||
const ROWSIZE = 80; // Maximum size of a single grid row
|
||||
const COLSIZE = 25; // Maximum size of a single grid column
|
||||
@ -50,6 +56,7 @@ export default class Befunge93LanguageEngine
|
||||
private _strmode: boolean = DEFAULT_STR_MODE;
|
||||
private _bounds: CodeBounds = DEFAULT_BOUNDS();
|
||||
private _input: InputStream = new InputStream("");
|
||||
private _edits: DocumentEdit[] = [];
|
||||
|
||||
resetState() {
|
||||
this._ast = DEFAULT_AST();
|
||||
@ -59,6 +66,7 @@ export default class Befunge93LanguageEngine
|
||||
this._strmode = DEFAULT_STR_MODE;
|
||||
this._bounds = DEFAULT_BOUNDS();
|
||||
this._input = new InputStream("");
|
||||
this._edits = [];
|
||||
}
|
||||
|
||||
validateCode(code: string) {
|
||||
@ -67,18 +75,22 @@ export default class Befunge93LanguageEngine
|
||||
|
||||
prepare(code: string, input: string) {
|
||||
this._ast = this.parseCode(code);
|
||||
this._edits = this.getGridPaddingEdits(code);
|
||||
this._input = new InputStream(input);
|
||||
}
|
||||
|
||||
executeStep(): StepExecutionResult<Bfg93RS> {
|
||||
// Execute and update program counter
|
||||
let output: string | undefined = undefined;
|
||||
let edits: DocumentEdit[] | undefined = undefined;
|
||||
let end: boolean = false;
|
||||
if (this._pc.x === -1 && this._pc.y === -1) {
|
||||
this._pc = { x: 0, y: 0 };
|
||||
edits = this._edits;
|
||||
} else {
|
||||
const result = this.processOp();
|
||||
output = result.output;
|
||||
edits = result.edit && [result.edit];
|
||||
end = !!result.end;
|
||||
}
|
||||
|
||||
@ -92,7 +104,7 @@ export default class Befunge93LanguageEngine
|
||||
direction: this._dirn,
|
||||
strMode: this._strmode,
|
||||
};
|
||||
return { rendererState, nextStepLocation, output };
|
||||
return { rendererState, nextStepLocation, output, codeEdits: edits };
|
||||
}
|
||||
|
||||
private parseCode(code: string) {
|
||||
@ -133,7 +145,7 @@ export default class Befunge93LanguageEngine
|
||||
* Also updates stack and pointer states.
|
||||
* @returns String to append to output, if any
|
||||
*/
|
||||
private processOp(): { output?: string; end?: boolean } {
|
||||
private processOp(): { output?: string; end?: boolean; edit?: DocumentEdit } {
|
||||
const char = this.getGridCell(this._pc.x, this._pc.y);
|
||||
if (this._strmode && char !== '"') {
|
||||
// Push character to string and return;
|
||||
@ -143,6 +155,7 @@ export default class Befunge93LanguageEngine
|
||||
}
|
||||
|
||||
let output: string | undefined = undefined;
|
||||
let edit: DocumentEdit | undefined = undefined;
|
||||
let end: boolean = false;
|
||||
|
||||
const op = this.charToOp(char);
|
||||
@ -275,7 +288,7 @@ export default class Befunge93LanguageEngine
|
||||
const y = this.popStack();
|
||||
const x = this.popStack();
|
||||
const charCode = this.popStack();
|
||||
this.setGridCell(x, y, charCode);
|
||||
edit = this.setGridCell(x, y, charCode);
|
||||
break;
|
||||
}
|
||||
case Bfg93Op.STDIN_INT: {
|
||||
@ -299,7 +312,7 @@ export default class Befunge93LanguageEngine
|
||||
|
||||
// Update grid pointer and return
|
||||
this.updatePointer();
|
||||
return { output, end };
|
||||
return { output, end, edit };
|
||||
}
|
||||
|
||||
/** Push a number onto the stack */
|
||||
@ -327,7 +340,7 @@ export default class Befunge93LanguageEngine
|
||||
* Set cell at (x, y) of program grid to character with given ASCII value.
|
||||
* Throws if (x, y) is out of bounds
|
||||
*/
|
||||
private setGridCell(x: number, y: number, asciiVal: number): void {
|
||||
private setGridCell(x: number, y: number, asciiVal: number): DocumentEdit {
|
||||
if (!this.isInGrid(x, y))
|
||||
throw new RuntimeError("Coordinates out of bound");
|
||||
|
||||
@ -340,6 +353,12 @@ export default class Befunge93LanguageEngine
|
||||
// Update grid bounds
|
||||
this._bounds.x[y] = Math.max(this._bounds.x[y], x);
|
||||
this._bounds.y[x] = Math.max(this._bounds.y[x], y);
|
||||
|
||||
// Return code edit object
|
||||
return {
|
||||
text: toSafePrintableChar(asciiVal),
|
||||
range: { line: y, charRange: { start: x, end: x + 1 } },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,6 +402,36 @@ export default class Befunge93LanguageEngine
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate `DocumentEdit`s to apply on code to pad it up to 80x25 size.
|
||||
* @param code Code content, lines separated by '\n'
|
||||
* @returns Array of `DocumentEdit`s to apply on code
|
||||
*/
|
||||
private getGridPaddingEdits(code: string): DocumentEdit[] {
|
||||
const lines = code.split("\n");
|
||||
const edits: DocumentEdit[] = [];
|
||||
for (let i = 0; i < COLSIZE; ++i) {
|
||||
if (i < lines.length) {
|
||||
if (lines[i].length === ROWSIZE) continue;
|
||||
// Add padding to line upto full-length
|
||||
edits.push({
|
||||
range: {
|
||||
line: i,
|
||||
charRange: { start: lines[i].length, end: lines[i].length },
|
||||
},
|
||||
text: " ".repeat(ROWSIZE - lines[i].length),
|
||||
});
|
||||
} else {
|
||||
// Add full-length empty line
|
||||
edits.push({
|
||||
range: { line: i, charRange: { start: 0, end: 0 } },
|
||||
text: "\n" + " ".repeat(80),
|
||||
});
|
||||
}
|
||||
}
|
||||
return edits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast the given character to corresponding Befunge-93 op.
|
||||
* If character is invalid op, returns null.
|
||||
|
20
engines/engine-utils.ts
Normal file
20
engines/engine-utils.ts
Normal file
@ -0,0 +1,20 @@
|
||||
/**
|
||||
* For given ASCII code, returns character that is safe to insert into code.
|
||||
*
|
||||
* This is useful for self-modifying programs that may insert non-printable characters into
|
||||
* the source code at runtime. Characters like `\n`, `\r` and `Tab` distort the grid visually
|
||||
* in the code editor. This function replaces such characters with safely printable alts. Other
|
||||
* control characters will be safely rendered by the code editor.
|
||||
*
|
||||
* @param asciiVal ASCII value to get safe character for
|
||||
* @returns Character safe to print without distorting code
|
||||
*/
|
||||
export const toSafePrintableChar = (asciiVal: number): string => {
|
||||
// "\n" -> "⤶"
|
||||
if (asciiVal === 10) return "\u21b5";
|
||||
// "\r" -> "␍"
|
||||
else if (asciiVal === 13) return "\u240d";
|
||||
// Tab -> "⇆"
|
||||
else if (asciiVal === 9) return "\u21c6";
|
||||
else return String.fromCharCode(asciiVal);
|
||||
};
|
@ -154,6 +154,9 @@ const CodeEditorComponent = (props: Props, ref: React.Ref<CodeEditorRef>) => {
|
||||
minimap: { enabled: false },
|
||||
glyphMargin: true,
|
||||
readOnly: readOnly,
|
||||
// Self-modifying programs may add control characters to the code.
|
||||
// This option ensures such characters are properly displayed.
|
||||
renderControlCharacters: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user