[add] branflakes execution

This commit is contained in:
2025-07-20 03:36:43 +01:00
parent 1ce0b2e4e2
commit 3656d71e6d
7 changed files with 257 additions and 169 deletions

View File

@@ -0,0 +1,20 @@
import { EventEmitter } from 'vscode';
import InputStrategy from './input/InputStrategy';
import {BranFlakesExecutorVisitor} from './exec/BranFlakesExecutorVisitor';
export class BranFlakesStreamingExecutor {
constructor(private fileData: string, private fileName: string = 'fileName:dummy', private emitter: EventEmitter<string>,
private inputStrategy: InputStrategy
) { }
async run() {
const finalOutput = await BranFlakesExecutorVisitor.run(
this.fileData,
this.fileName,
this.inputStrategy,
async (str) => { this.emitter.fire(str); }
);
}
}

View File

@@ -1,140 +0,0 @@
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
import { LoopStmtContext } from './generated/bfParser';
import { bfVisitor } from './generated/bfVisitor';
import { DiagnosticSeverity } from 'vscode-languageclient';
import { getTree } from './BranFlakesParseRunner';
import { RuleNode } from 'antlr4ts/tree/RuleNode';
import type InputStrategy from './input/InputStrategy';
export class BranFlakesExecutorVisitor
extends AbstractParseTreeVisitor<Promise<void>>
implements bfVisitor<Promise<void>>
{
/**
*
* @param input Input string
* @param inputPtr Input pointer to start from
*/
constructor(
private inputStrategy: InputStrategy,
private logger: (val: string) => Thenable<string>,
private inputPtr: number = 0
) {
super();
}
// /**
// * The memory cells (Can work with negative cells this way)
// */
// private cells: Map<number, number> = new Map();
private byteArraySize: number = 30000;
private byteArray: Int8Array = new Int8Array(this.byteArraySize);
/**
* Pointer
*/
private ptr: number = 0;
/** Output string */
private outputStr: string = '';
defaultResult() {
return Promise.resolve();
}
/**
* Run a file
* @param text
* @param fn
* @param inputStrategy
* @returns
*/
static async run(
text: string,
fn: string,
inputStrategy: InputStrategy,
logger: (str: string) => Thenable<string>
) {
//get tree and issues
const { tree, issues } = getTree(text, fn);
//get only errors
const x = issues.filter(e => e.type === DiagnosticSeverity.Error);
//if any error, drop
if (x.length > 0) {
throw Error('Errors exist');
}
// make visitor
const vis = new BranFlakesExecutorVisitor(inputStrategy, logger);
//visit the tree
await vis.visit(tree);
//get output
return vis.outputStr;
}
getCell(pointerIndex: number) {
return this.byteArray[pointerIndex];
}
setCell(pointerIndex: number, value: number): void {
this.byteArray[pointerIndex] = value;
}
async visitLoopStmt(ctx: LoopStmtContext) {
while ((this.getCell(this.ptr) ?? 0) !== 0) {
await this.visitChildren(ctx);
}
}
async visitPtrLeft() {
this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
}
async visitPtrRight() {
this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
}
async visitPtrIncr() {
const val = this.getCell(this.ptr);
this.setCell(this.ptr, (val + 1) % 256);
}
async visitPtrDecr() {
const val = this.getCell(this.ptr);
this.setCell(this.ptr, (val + 255) % 256);
}
async visitOutputStmt() {
const val = this.getCell(this.ptr) ?? 0;
const str = String.fromCharCode(val);
this.outputStr += str;
}
async visitInputStmt() {
//get char
const char = (await this.inputStrategy.getInput()) ?? 0;
//increment the input pointer after this
this.inputPtr++;
this.setCell(this.ptr, char);
}
// override for maintaining async
async visitChildren(node: RuleNode): Promise<void> {
let result = this.defaultResult();
await result;
let n = node.childCount;
for (let i = 0; i < n; i++) {
if (!this.shouldVisitNextChild(node, result)) {
break;
}
let c = node.getChild(i);
let childResult = c.accept(this);
result = this.aggregateResult(result, childResult);
await result;
}
return Promise.resolve();
}
// override for maintaining async
protected async aggregateResult(
aggregate: Promise<void>,
nextResult: Promise<void>
): Promise<void> {
await aggregate;
return nextResult;
}
}

View File

@@ -4,7 +4,7 @@ import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy';
export class CompileBranFlakesCommand implements BranFlakesCommand { export class CompileBranFlakesCommand implements BranFlakesCommand {
getCommandName() { getCommandName() {
return 'bf.execute'; return 'bf.execute.old';
} }
getCommandHandler() { getCommandHandler() {
return async () => { return async () => {
@@ -14,7 +14,7 @@ export class CompileBranFlakesCommand implements BranFlakesCommand {
window.showInputBox window.showInputBox
); );
const { BranFlakesExecutorVisitor } = await import( const { BranFlakesExecutorVisitor } = await import(
'../BranFlakesExecutorVisitor' '../exec/BranFlakesExecutorVisitor'
); );
const output = await BranFlakesExecutorVisitor.run( const output = await BranFlakesExecutorVisitor.run(

View File

@@ -0,0 +1,142 @@
import { AbstractParseTreeVisitor } from 'antlr4ts/tree/AbstractParseTreeVisitor';
import { LoopStmtContext } from '../generated/bfParser';
import { bfVisitor } from '../generated/bfVisitor';
import { DiagnosticSeverity } from 'vscode-languageclient';
import { getTree } from '../BranFlakesParseRunner';
import { RuleNode } from 'antlr4ts/tree/RuleNode';
import type InputStrategy from '../input/InputStrategy';
export class BranFlakesExecutorVisitor
extends AbstractParseTreeVisitor<Promise<void>>
implements bfVisitor<Promise<void>>
{
/**
*
* @param input Input string
* @param inputPtr Input pointer to start from
*/
constructor(
private inputStrategy: InputStrategy,
private logger: (val: string) => Thenable<void>,
private inputPtr: number = 0
) {
super();
}
// /**
// * The memory cells (Can work with negative cells this way)
// */
// private cells: Map<number, number> = new Map();
private byteArraySize: number = 30000;
private byteArray: Int8Array = new Int8Array(this.byteArraySize);
/**
* Pointer
*/
private ptr: number = 0;
/** Output string */
private outputStr: string = '';
defaultResult() {
return Promise.resolve();
}
/**
* Run a file
* @param text
* @param fn
* @param inputStrategy
* @returns
*/
static async run(
text: string,
fn: string,
inputStrategy: InputStrategy,
logger: (str: string) => Thenable<void>
) {
//get tree and issues
const { tree, issues } = getTree(text, fn);
//get only errors
const x = issues.filter(e => e.type === DiagnosticSeverity.Error);
//if any error, drop
if (x.length > 0) {
throw Error('Errors exist');
}
// make visitor
const vis = new BranFlakesExecutorVisitor(inputStrategy, logger);
//visit the tree
await vis.visit(tree);
//get output
return vis.outputStr;
}
getCell(pointerIndex: number) {
return this.byteArray[pointerIndex];
}
setCell(pointerIndex: number, value: number): void {
this.byteArray[pointerIndex] = value;
}
async visitLoopStmt(ctx: LoopStmtContext) {
while ((this.getCell(this.ptr) ?? 0) !== 0) {
await this.visitChildren(ctx);
}
}
async visitPtrLeft() {
this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
}
async visitPtrRight() {
this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
}
async visitPtrIncr() {
const val = this.getCell(this.ptr);
this.setCell(this.ptr, (val + 1) % 256);
}
async visitPtrDecr() {
const val = this.getCell(this.ptr);
this.setCell(this.ptr, (val + 255) % 256);
}
async visitOutputStmt() {
const val = this.getCell(this.ptr) ?? 0;
const str = String.fromCharCode(val);
this.outputStr += str;
await this.logger(str);
}
async visitInputStmt() {
//get char
const char = (await this.inputStrategy.getInput()) ?? 0;
if(char===3) {throw Error('Halt input wait');}
//increment the input pointer after this
this.inputPtr++;
this.setCell(this.ptr, char);
}
// override for maintaining async
async visitChildren(node: RuleNode): Promise<void> {
let result = this.defaultResult();
await result;
let n = node.childCount;
for (let i = 0; i < n; i++) {
if (!this.shouldVisitNextChild(node, result)) {
break;
}
let c = node.getChild(i);
let childResult = c.accept(this);
result = this.aggregateResult(result, childResult);
await result;
}
return Promise.resolve();
}
// override for maintaining async
protected async aggregateResult(
aggregate: Promise<void>,
nextResult: Promise<void>
): Promise<void> {
await aggregate;
return nextResult;
}
}

View File

@@ -1,5 +1,6 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path';
import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor';
interface BFRunTaskDefinition extends vscode.TaskDefinition { interface BFRunTaskDefinition extends vscode.TaskDefinition {
file?: string; file?: string;
@@ -9,7 +10,7 @@ export class CustomExecutionTaskProvider implements vscode.TaskProvider {
static type: string = 'bf-run'; static type: string = 'bf-run';
tasks: vscode.Task[] | undefined; tasks: vscode.Task[] | undefined;
constructor(private workspaceRoot: string|undefined){ constructor(private workspaceRoot: string | undefined) {
} }
@@ -21,25 +22,25 @@ export class CustomExecutionTaskProvider implements vscode.TaskProvider {
} }
getTaskFromDefinition(fileName: string|undefined): vscode.Task { getTaskFromDefinition(fileName: string | undefined): vscode.Task {
const definition:BFRunTaskDefinition = { const definition: BFRunTaskDefinition = {
type: CustomExecutionTaskProvider.type, type: CustomExecutionTaskProvider.type,
file: fileName file: fileName
}; };
return new vscode.Task(definition, vscode.TaskScope.Workspace,`bf: run: current file`,CustomExecutionTaskProvider.type, return new vscode.Task(definition, vscode.TaskScope.Workspace, `bf: run: current file`, CustomExecutionTaskProvider.type,
new vscode.CustomExecution(async ()=>{ new vscode.CustomExecution(async () => {
return new CustomBuildTaskTerminal(definition.file); return new CustomBuildTaskTerminal(definition.file);
}) })
); );
} }
resolveTask(_task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> { resolveTask(_task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> {
const definition:BFRunTaskDefinition = <any>_task.definition; const definition: BFRunTaskDefinition = <any>_task.definition;
const fileNameRecovered = definition.file; const fileNameRecovered = definition.file;
const taskName = `bf: run: `+ ( fileNameRecovered??'current file'); const taskName = `bf: run: ` + (fileNameRecovered ?? 'current file');
return new vscode.Task(definition,vscode.TaskScope.Workspace, taskName, CustomExecutionTaskProvider.type, return new vscode.Task(definition, vscode.TaskScope.Workspace, taskName, CustomExecutionTaskProvider.type,
new vscode.CustomExecution(async ()=>{ new vscode.CustomExecution(async () => {
return new CustomBuildTaskTerminal(definition.file); return new CustomBuildTaskTerminal(definition.file);
}) })
); );
@@ -47,31 +48,60 @@ export class CustomExecutionTaskProvider implements vscode.TaskProvider {
} }
function replaceLFWithCRLF(data: string) {
return data.replace(/(?<!\r)\n/gm, '\r\n');
}
class CustomBuildTaskTerminal implements vscode.Pseudoterminal { class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
private writeEmitter = new vscode.EventEmitter<string>(); private writeEmitter = new vscode.EventEmitter<string>();
private closeEmitter = new vscode.EventEmitter<number>(); private closeEmitter = new vscode.EventEmitter<number>();
private readEmitter = new vscode.EventEmitter<void>();
inputQueue: number[] = [];
private openDocument:vscode.TextDocument|undefined; private openDocument: vscode.TextDocument | undefined;
onDidWrite: vscode.Event<string> = this.writeEmitter.event; onDidWrite: vscode.Event<string> = this.writeEmitter.event;
onDidClose?: vscode.Event<number> = this.closeEmitter.event; onDidClose?: vscode.Event<number> = this.closeEmitter.event;
handleInput(data: string): void { handleInput(data: string): void {
this.writeEmitter.fire(`Echo`+data); // this.writeEmitter.fire(`Echo(${data.length})` + data);
const newData = [...data].map(e => e.charCodeAt(0));
console.log('new input', newData);
this.inputQueue.push(...newData);
this.readEmitter.fire();
} }
constructor(private fileName?:string) { constructor(private fileName?: string) {
} }
open(initialDimensions: vscode.TerminalDimensions | undefined): void { open(_initialDimensions: vscode.TerminalDimensions | undefined): void {
// At this point we can start using the terminal. // At this point we can start using the terminal.
this.openDocumentForTask().then(this.doExecution.bind(this));
}
const openDocument = vscode.window.activeTextEditor.document; getPath(fileLocationString: string | undefined, fileName: string) {
if (fileLocationString === undefined) { return vscode.Uri.file(fileName); }
return vscode.Uri.file(path.resolve(fileLocationString, fileName));
}
private async openDocumentForTask() {
let openDocument: vscode.TextDocument;
if (this.fileName !== undefined) {
try {
const fileLocationPathString = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
const finalPath = this.getPath(fileLocationPathString, this.fileName);
openDocument = await vscode.workspace.openTextDocument(finalPath);
} catch (e) {
vscode.window.showErrorMessage('Failed to open file ' + this.fileName);
this.closeEmitter.fire(2);
}
} else {
openDocument = vscode.window.activeTextEditor.document;
}
this.openDocument = openDocument; this.openDocument = openDocument;
console.log(openDocument.languageId);
this.doExecution();
} }
close(): void { close(): void {
@@ -79,9 +109,43 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
} }
private async doExecution(): Promise<void> { private async doExecution(): Promise<void> {
this.writeEmitter.fire('Build complete.\r\n\r\n'); this.writeEmitter.fire('[bf] Requested execution of ' + this.fileName + '\r\n');
this.writeEmitter.fire('Requested build of '+this.fileName); const cus = this;
this.writeEmitter.fire(this.openDocument.getText()); try {
this.closeEmitter.fire(0);
await BranFlakesExecutorVisitor.run(this.openDocument.getText(),
this.openDocument.uri.fsPath,
{
getInput() {
return new Promise((res, rej) => {
if (cus.inputQueue.length > 0) {
const char = cus.inputQueue.shift();
console.log('consumed input', char);
res(char);
} else {
const dispose: vscode.Disposable[] = [];
cus.readEmitter.event(e => {
const char = cus.inputQueue.shift();
console.log('consumed input async', char);
//clear the earliest disposable
dispose.shift()?.dispose();
res(char);
}, null, dispose);
}
});
},
},
async (data) => {
// console.log('output', data);
this.writeEmitter.fire(replaceLFWithCRLF(data));
}
);
this.closeEmitter.fire(0);
} catch (e) {
this.closeEmitter.fire(1);
}
} }
} }

View File

@@ -16,7 +16,8 @@ const config = {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: '[name].js', filename: '[name].js',
libraryTarget: 'commonjs2', libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]' sourceMapFilename: '[name].js.map',
devtoolModuleFilenameTemplate: '../[resource-path]',
}, },
// devtool: 'source-map', // devtool: 'source-map',
externals: { externals: {
@@ -27,6 +28,7 @@ const config = {
innerGraph:true, innerGraph:true,
usedExports:true usedExports:true
}, },
resolve: { resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
extensions: ['.ts', '.js'], extensions: ['.ts', '.js'],

View File

@@ -57,7 +57,7 @@
], ],
"commands": [ "commands": [
{ {
"command": "bf.execute", "command": "bf.execute.old",
"title": "BF: Execute", "title": "BF: Execute",
"when": "editorLangId == bf", "when": "editorLangId == bf",
"enablement": "editorLangId == bf" "enablement": "editorLangId == bf"