[fix] aborting
This commit is contained in:
11
README.md
11
README.md
@@ -13,14 +13,21 @@ A simple language server based VSCode Extension for the (Branflakes?) (BrainFuck
|
||||
|
||||
### Execution
|
||||
|
||||
Use the command 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.
|
||||
Use the BF execute task to execute the code.
|
||||
Because JS is single threaded+.
|
||||
If the program requires input, it will be requested as a prompt.
|
||||
|
||||
TODO: Implement a timeout.
|
||||
|
||||
### 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
|
||||
|
||||
- Change category
|
||||
|
11
client/src/exec/AbortRequestor.ts
Normal file
11
client/src/exec/AbortRequestor.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export class AbortClassRequestor{
|
||||
private abortRequest=false;
|
||||
|
||||
requestAbort(){
|
||||
this.abortRequest=true;
|
||||
}
|
||||
isAborted(){
|
||||
return this.abortRequest;
|
||||
}
|
||||
}
|
@@ -5,20 +5,21 @@ import { DiagnosticSeverity } from 'vscode-languageclient';
|
||||
import { getTree } from '../BranFlakesParseRunner';
|
||||
import { RuleNode } from 'antlr4ts/tree/RuleNode';
|
||||
import type InputStrategy from '../input/InputStrategy';
|
||||
import { AbortClassRequestor } from './AbortRequestor';
|
||||
|
||||
export class BranFlakesExecutorVisitor
|
||||
extends AbstractParseTreeVisitor<Promise<void>>
|
||||
implements bfVisitor<Promise<void>>
|
||||
{
|
||||
implements bfVisitor<Promise<void>> {
|
||||
/**
|
||||
*
|
||||
* @param input Input string
|
||||
* @param inputPtr Input pointer to start from
|
||||
*/
|
||||
*
|
||||
* @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
|
||||
private inputStrategy: InputStrategy,
|
||||
private logger: (val: string) => Thenable<void>,
|
||||
private abortRequestor?: AbortClassRequestor,
|
||||
private inputPtr: number = 0
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -27,116 +28,120 @@ export class BranFlakesExecutorVisitor
|
||||
// */
|
||||
// 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 = '';
|
||||
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);
|
||||
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);
|
||||
|
||||
//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 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, aborter);
|
||||
//visit the tree
|
||||
await vis.visit(tree);
|
||||
|
||||
//get output
|
||||
return vis.outputStr;
|
||||
}
|
||||
//get output
|
||||
return vis.outputStr;
|
||||
}
|
||||
|
||||
getCell(pointerIndex: number) {
|
||||
return this.byteArray[pointerIndex];
|
||||
}
|
||||
setCell(pointerIndex: number, value: number): void {
|
||||
this.byteArray[pointerIndex] = value;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// break for any close requests
|
||||
return new Promise((res, rej) => {
|
||||
|
||||
setTimeout(() => { if (this.abortRequestor?.isAborted()??false) {rej('aborted');} else {res(undefined);} }, 0);
|
||||
});
|
||||
}
|
||||
// override for maintaining async
|
||||
protected async aggregateResult(
|
||||
aggregate: Promise<void>,
|
||||
nextResult: Promise<void>
|
||||
): Promise<void> {
|
||||
await aggregate;
|
||||
return nextResult;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { BranFlakesExecutorVisitor } from '../exec/BranFlakesExecutorVisitor';
|
||||
import { AbortClassRequestor } from '../exec/AbortRequestor';
|
||||
|
||||
interface BFRunTaskDefinition extends vscode.TaskDefinition {
|
||||
file?: string;
|
||||
@@ -63,7 +64,7 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
private openDocument: vscode.TextDocument | undefined;
|
||||
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
||||
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
||||
|
||||
abortRequestor = new AbortClassRequestor();
|
||||
|
||||
handleInput(data: string): void {
|
||||
// this.writeEmitter.fire(`Echo(${data.length})` + data);
|
||||
@@ -106,6 +107,8 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
|
||||
close(): void {
|
||||
// The terminal has been closed. Shutdown the build.
|
||||
console.log('Forced close');
|
||||
this.abortRequestor.requestAbort();
|
||||
}
|
||||
|
||||
private async doExecution(): Promise<void> {
|
||||
@@ -120,13 +123,11 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
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);
|
||||
@@ -137,9 +138,8 @@ class CustomBuildTaskTerminal implements vscode.Pseudoterminal {
|
||||
},
|
||||
},
|
||||
async (data) => {
|
||||
// console.log('output', data);
|
||||
this.writeEmitter.fire(replaceLFWithCRLF(data));
|
||||
}
|
||||
},this.abortRequestor
|
||||
);
|
||||
this.closeEmitter.fire(0);
|
||||
} catch (e) {
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bfc-server",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bfc-server",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@@ -5,7 +5,7 @@
|
||||
"author": "Atreya Bain",
|
||||
"license": "MIT",
|
||||
"publisher": "atreyabain",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"icon": "assets/128.png",
|
||||
"categories": ["Programming Languages","Linters"],
|
||||
"keywords": [
|
||||
|
Reference in New Issue
Block a user