[fix] aborting

This commit is contained in:
2025-07-20 04:12:13 +01:00
parent 3656d71e6d
commit 3f8f515262
6 changed files with 145 additions and 122 deletions

View File

@@ -13,14 +13,21 @@ A simple language server based VSCode Extension for the (Branflakes?) (BrainFuck
### Execution ### Execution
Use the command to execute the code. Use the BF execute task to execute the code.
Issue is, because BF is a **turing complete** language, there is no way to know if the program will terminate or not. Hence for now, the command may lead to infinite execution. Because JS is single threaded+.
If the program requires input, it will be requested as a prompt. If the program requires input, it will be requested as a prompt.
TODO: Implement a timeout. TODO: Implement a timeout.
### Changelog ### Changelog
#### 0.3.0
- Added a task for execution
- Press Control C to halt it while its waiting for input
- Close task to abort execution
#### 0.2.1 #### 0.2.1
- Change category - Change category

View File

@@ -0,0 +1,11 @@
export class AbortClassRequestor{
private abortRequest=false;
requestAbort(){
this.abortRequest=true;
}
isAborted(){
return this.abortRequest;
}
}

View File

@@ -5,20 +5,21 @@ import { DiagnosticSeverity } from 'vscode-languageclient';
import { getTree } from '../BranFlakesParseRunner'; import { getTree } from '../BranFlakesParseRunner';
import { RuleNode } from 'antlr4ts/tree/RuleNode'; import { RuleNode } from 'antlr4ts/tree/RuleNode';
import type InputStrategy from '../input/InputStrategy'; import type InputStrategy from '../input/InputStrategy';
import { AbortClassRequestor } from './AbortRequestor';
export class BranFlakesExecutorVisitor export class BranFlakesExecutorVisitor
extends AbstractParseTreeVisitor<Promise<void>> extends AbstractParseTreeVisitor<Promise<void>>
implements bfVisitor<Promise<void>> implements bfVisitor<Promise<void>> {
{
/** /**
* *
* @param input Input string * @param input Input string
* @param inputPtr Input pointer to start from * @param inputPtr Input pointer to start from
*/ */
constructor( constructor(
private inputStrategy: InputStrategy, private inputStrategy: InputStrategy,
private logger: (val: string) => Thenable<void>, private logger: (val: string) => Thenable<void>,
private inputPtr: number = 0 private abortRequestor?: AbortClassRequestor,
private inputPtr: number = 0
) { ) {
super(); super();
} }
@@ -27,116 +28,120 @@ export class BranFlakesExecutorVisitor
// */ // */
// private cells: Map<number, number> = new Map(); // private cells: Map<number, number> = new Map();
private byteArraySize: number = 30000; private byteArraySize: number = 30000;
private byteArray: Int8Array = new Int8Array(this.byteArraySize); private byteArray: Int8Array = new Int8Array(this.byteArraySize);
/** /**
* Pointer * Pointer
*/ */
private ptr: number = 0; private ptr: number = 0;
/** Output string */ /** Output string */
private outputStr: 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>,
aborter?: AbortClassRequestor
) {
//get tree and issues
const { tree, issues } = getTree(text, fn);
defaultResult() { //get only errors
return Promise.resolve(); const x = issues.filter(e => e.type === DiagnosticSeverity.Error);
} //if any error, drop
/** if (x.length > 0) {
* Run a file throw Error('Errors exist');
* @param text }
* @param fn // make visitor
* @param inputStrategy const vis = new BranFlakesExecutorVisitor(inputStrategy, logger, aborter);
* @returns //visit the tree
*/ await vis.visit(tree);
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 //get output
const x = issues.filter(e => e.type === DiagnosticSeverity.Error); return vis.outputStr;
//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 getCell(pointerIndex: number) {
return vis.outputStr; return this.byteArray[pointerIndex];
} }
setCell(pointerIndex: number, value: number): void {
this.byteArray[pointerIndex] = value;
}
getCell(pointerIndex: number) { async visitLoopStmt(ctx: LoopStmtContext) {
return this.byteArray[pointerIndex]; while ((this.getCell(this.ptr) ?? 0) !== 0) {
} await this.visitChildren(ctx);
setCell(pointerIndex: number, value: number): void { }
this.byteArray[pointerIndex] = value; }
} 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);
async visitLoopStmt(ctx: LoopStmtContext) { this.outputStr += str;
while ((this.getCell(this.ptr) ?? 0) !== 0) { await this.logger(str);
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() {
await this.logger(str); //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);
}
async visitInputStmt() { // override for maintaining async
//get char async visitChildren(node: RuleNode): Promise<void> {
const char = (await this.inputStrategy.getInput()) ?? 0; let result = this.defaultResult();
if(char===3) {throw Error('Halt input wait');} await result;
//increment the input pointer after this let n = node.childCount;
this.inputPtr++; for (let i = 0; i < n; i++) {
this.setCell(this.ptr, char); if (!this.shouldVisitNextChild(node, result)) {
} break;
}
let c = node.getChild(i);
let childResult = c.accept(this);
result = this.aggregateResult(result, childResult);
await result;
}
// break for any close requests
return new Promise((res, rej) => {
// override for maintaining async setTimeout(() => { if (this.abortRequestor?.isAborted()??false) {rej('aborted');} else {res(undefined);} }, 0);
async visitChildren(node: RuleNode): Promise<void> { });
let result = this.defaultResult(); }
await result; // override for maintaining async
let n = node.childCount; protected async aggregateResult(
for (let i = 0; i < n; i++) { aggregate: Promise<void>,
if (!this.shouldVisitNextChild(node, result)) { nextResult: Promise<void>
break; ): Promise<void> {
} await aggregate;
let c = node.getChild(i); return nextResult;
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,6 +1,7 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as path from 'path'; import * as path from 'path';
import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor'; import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor';
import { AbortClassRequestor } from '../exec/AbortRequestor';
interface BFRunTaskDefinition extends vscode.TaskDefinition { interface BFRunTaskDefinition extends vscode.TaskDefinition {
file?: string; file?: string;
@@ -63,7 +64,7 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
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;
abortRequestor = new AbortClassRequestor();
handleInput(data: string): void { handleInput(data: string): void {
// this.writeEmitter.fire(`Echo(${data.length})` + data); // this.writeEmitter.fire(`Echo(${data.length})` + data);
@@ -106,6 +107,8 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
close(): void { close(): void {
// The terminal has been closed. Shutdown the build. // The terminal has been closed. Shutdown the build.
console.log('Forced close');
this.abortRequestor.requestAbort();
} }
private async doExecution(): Promise<void> { private async doExecution(): Promise<void> {
@@ -120,13 +123,11 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
return new Promise((res, rej) => { return new Promise((res, rej) => {
if (cus.inputQueue.length > 0) { if (cus.inputQueue.length > 0) {
const char = cus.inputQueue.shift(); const char = cus.inputQueue.shift();
console.log('consumed input', char);
res(char); res(char);
} else { } else {
const dispose: vscode.Disposable[] = []; const dispose: vscode.Disposable[] = [];
cus.readEmitter.event(e => { cus.readEmitter.event(e => {
const char = cus.inputQueue.shift(); const char = cus.inputQueue.shift();
console.log('consumed input async', char);
//clear the earliest disposable //clear the earliest disposable
dispose.shift()?.dispose(); dispose.shift()?.dispose();
res(char); res(char);
@@ -137,9 +138,8 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
}, },
}, },
async (data) => { async (data) => {
// console.log('output', data);
this.writeEmitter.fire(replaceLFWithCRLF(data)); this.writeEmitter.fire(replaceLFWithCRLF(data));
} },this.abortRequestor
); );
this.closeEmitter.fire(0); this.closeEmitter.fire(0);
} catch (e) { } catch (e) {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "bfc-server", "name": "bfc-server",
"version": "0.2.1", "version": "0.3.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bfc-server", "name": "bfc-server",
"version": "0.2.1", "version": "0.3.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@@ -5,7 +5,7 @@
"author": "Atreya Bain", "author": "Atreya Bain",
"license": "MIT", "license": "MIT",
"publisher": "atreyabain", "publisher": "atreyabain",
"version": "0.2.1", "version": "0.3.0",
"icon": "assets/128.png", "icon": "assets/128.png",
"categories": ["Programming Languages","Linters"], "categories": ["Programming Languages","Linters"],
"keywords": [ "keywords": [