From 3656d71e6d941804fab925e6bb8b143af5db8099 Mon Sep 17 00:00:00 2001 From: Atreya Bain Date: Sun, 20 Jul 2025 03:36:43 +0100 Subject: [PATCH] [add] branflakes execution --- client/src/BranFlakesExecutorGen.ts | 20 +++ client/src/BranFlakesExecutorVisitor.ts | 140 ----------------- .../src/command/CompileBranFlakesCommand.ts | 4 +- client/src/exec/BranFlakesExecutorVisitor.ts | 142 ++++++++++++++++++ client/src/task/CustomExecutionTerminal.ts | 114 +++++++++++--- client/webpack.config.js | 4 +- package.json | 2 +- 7 files changed, 257 insertions(+), 169 deletions(-) create mode 100644 client/src/BranFlakesExecutorGen.ts delete mode 100644 client/src/BranFlakesExecutorVisitor.ts create mode 100644 client/src/exec/BranFlakesExecutorVisitor.ts diff --git a/client/src/BranFlakesExecutorGen.ts b/client/src/BranFlakesExecutorGen.ts new file mode 100644 index 0000000..0429b67 --- /dev/null +++ b/client/src/BranFlakesExecutorGen.ts @@ -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, + private inputStrategy: InputStrategy + ) { } + + + async run() { + const finalOutput = await BranFlakesExecutorVisitor.run( + this.fileData, + this.fileName, + this.inputStrategy, + async (str) => { this.emitter.fire(str); } + ); + } +} \ No newline at end of file diff --git a/client/src/BranFlakesExecutorVisitor.ts b/client/src/BranFlakesExecutorVisitor.ts deleted file mode 100644 index 5c6bd9d..0000000 --- a/client/src/BranFlakesExecutorVisitor.ts +++ /dev/null @@ -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> - implements bfVisitor> -{ - /** - * - * @param input Input string - * @param inputPtr Input pointer to start from - */ - constructor( - private inputStrategy: InputStrategy, - private logger: (val: string) => Thenable, - private inputPtr: number = 0 - ) { - super(); - } - // /** - // * The memory cells (Can work with negative cells this way) - // */ - // private cells: Map = 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 - ) { - //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 { - 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, - nextResult: Promise - ): Promise { - await aggregate; - return nextResult; - } -} diff --git a/client/src/command/CompileBranFlakesCommand.ts b/client/src/command/CompileBranFlakesCommand.ts index facbb49..4b34256 100644 --- a/client/src/command/CompileBranFlakesCommand.ts +++ b/client/src/command/CompileBranFlakesCommand.ts @@ -4,7 +4,7 @@ import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy'; export class CompileBranFlakesCommand implements BranFlakesCommand { getCommandName() { - return 'bf.execute'; + return 'bf.execute.old'; } getCommandHandler() { return async () => { @@ -14,7 +14,7 @@ export class CompileBranFlakesCommand implements BranFlakesCommand { window.showInputBox ); const { BranFlakesExecutorVisitor } = await import( - '../BranFlakesExecutorVisitor' + '../exec/BranFlakesExecutorVisitor' ); const output = await BranFlakesExecutorVisitor.run( diff --git a/client/src/exec/BranFlakesExecutorVisitor.ts b/client/src/exec/BranFlakesExecutorVisitor.ts new file mode 100644 index 0000000..6bbc43e --- /dev/null +++ b/client/src/exec/BranFlakesExecutorVisitor.ts @@ -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> + implements bfVisitor> +{ + /** + * + * @param input Input string + * @param inputPtr Input pointer to start from + */ + constructor( + private inputStrategy: InputStrategy, + private logger: (val: string) => Thenable, + private inputPtr: number = 0 + ) { + super(); + } + // /** + // * The memory cells (Can work with negative cells this way) + // */ + // private cells: Map = 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 + ) { + //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 { + 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, + nextResult: Promise + ): Promise { + await aggregate; + return nextResult; + } +} diff --git a/client/src/task/CustomExecutionTerminal.ts b/client/src/task/CustomExecutionTerminal.ts index 5389268..7258c8f 100644 --- a/client/src/task/CustomExecutionTerminal.ts +++ b/client/src/task/CustomExecutionTerminal.ts @@ -1,5 +1,6 @@ import * as vscode from 'vscode'; - +import * as path from 'path'; +import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor'; interface BFRunTaskDefinition extends vscode.TaskDefinition { file?: string; @@ -9,37 +10,37 @@ export class CustomExecutionTaskProvider implements vscode.TaskProvider { static type: string = 'bf-run'; tasks: vscode.Task[] | undefined; - constructor(private workspaceRoot: string|undefined){ + constructor(private workspaceRoot: string | undefined) { } provideTasks(token?: vscode.CancellationToken): vscode.ProviderResult { if (this.tasks !== undefined) { return this.tasks; } - + this.tasks = [this.getTaskFromDefinition(undefined)]; return this.tasks; } - getTaskFromDefinition(fileName: string|undefined): vscode.Task { - const definition:BFRunTaskDefinition = { + getTaskFromDefinition(fileName: string | undefined): vscode.Task { + const definition: BFRunTaskDefinition = { type: CustomExecutionTaskProvider.type, file: fileName }; - return new vscode.Task(definition, vscode.TaskScope.Workspace,`bf: run: current file`,CustomExecutionTaskProvider.type, - new vscode.CustomExecution(async ()=>{ + return new vscode.Task(definition, vscode.TaskScope.Workspace, `bf: run: current file`, CustomExecutionTaskProvider.type, + new vscode.CustomExecution(async () => { return new CustomBuildTaskTerminal(definition.file); }) ); } resolveTask(_task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult { - const definition:BFRunTaskDefinition = _task.definition; - + const definition: BFRunTaskDefinition = _task.definition; + const fileNameRecovered = definition.file; - const taskName = `bf: run: `+ ( fileNameRecovered??'current file'); - return new vscode.Task(definition,vscode.TaskScope.Workspace, taskName, CustomExecutionTaskProvider.type, - new vscode.CustomExecution(async ()=>{ + const taskName = `bf: run: ` + (fileNameRecovered ?? 'current file'); + return new vscode.Task(definition, vscode.TaskScope.Workspace, taskName, CustomExecutionTaskProvider.type, + new vscode.CustomExecution(async () => { return new CustomBuildTaskTerminal(definition.file); }) ); @@ -47,31 +48,60 @@ export class CustomExecutionTaskProvider implements vscode.TaskProvider { } +function replaceLFWithCRLF(data: string) { + return data.replace(/(?(); private closeEmitter = new vscode.EventEmitter(); - - private openDocument:vscode.TextDocument|undefined; + private readEmitter = new vscode.EventEmitter(); + inputQueue: number[] = []; + + private openDocument: vscode.TextDocument | undefined; onDidWrite: vscode.Event = this.writeEmitter.event; onDidClose?: vscode.Event = this.closeEmitter.event; + 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. - - const openDocument = vscode.window.activeTextEditor.document; + this.openDocumentForTask().then(this.doExecution.bind(this)); + } + + 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; - console.log(openDocument.languageId); - this.doExecution(); } close(): void { @@ -79,9 +109,43 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal { } private async doExecution(): Promise { - this.writeEmitter.fire('Build complete.\r\n\r\n'); - this.writeEmitter.fire('Requested build of '+this.fileName); - this.writeEmitter.fire(this.openDocument.getText()); - this.closeEmitter.fire(0); + this.writeEmitter.fire('[bf] Requested execution of ' + this.fileName + '\r\n'); + const cus = this; + try { + + 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); + } + + } } \ No newline at end of file diff --git a/client/webpack.config.js b/client/webpack.config.js index f0e597a..654b7aa 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -16,7 +16,8 @@ const config = { path: path.resolve(__dirname, 'dist'), filename: '[name].js', libraryTarget: 'commonjs2', - devtoolModuleFilenameTemplate: '../[resource-path]' + sourceMapFilename: '[name].js.map', + devtoolModuleFilenameTemplate: '../[resource-path]', }, // devtool: 'source-map', externals: { @@ -27,6 +28,7 @@ const config = { innerGraph:true, usedExports:true }, + resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'], diff --git a/package.json b/package.json index 39aecec..800ed99 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ ], "commands": [ { - "command": "bf.execute", + "command": "bf.execute.old", "title": "BF: Execute", "when": "editorLangId == bf", "enablement": "editorLangId == bf"