19
README.md
19
README.md
@@ -13,14 +13,25 @@ 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.
|
Either run the "current file" task, or create a customized task with the required file.
|
||||||
If the program requires input, it will be requested as a prompt.
|
I/O is done on the created terminal window.
|
||||||
|
|
||||||
TODO: Implement a timeout.
|
|
||||||
|
There is also an older command to run the code, where output is shown as a status message. Here, if the program requires input, it will be requested as a prompt.
|
||||||
|
|
||||||
### 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
|
||||||
|
- Detail: The program will halt between loop iterations.
|
||||||
|
- Migrated the run command to `bf.execute.old`
|
||||||
|
|
||||||
#### 0.2.1
|
#### 0.2.1
|
||||||
|
|
||||||
- Change category
|
- Change category
|
||||||
|
BIN
assets/command.gif
Normal file
BIN
assets/command.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 540 KiB |
20
client/src/BranFlakesExecutorGen.ts
Normal file
20
client/src/BranFlakesExecutorGen.ts
Normal 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); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -3,27 +3,27 @@ import type { BranFlakesCommand } from './BranFlakesCommand';
|
|||||||
import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy';
|
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 () => {
|
||||||
const text = window.activeTextEditor.document.getText();
|
const text = window.activeTextEditor.document.getText();
|
||||||
const fn = window.activeTextEditor.document.fileName;
|
const fn = window.activeTextEditor.document.fileName;
|
||||||
const inputStrategy = new VSCodePromptInputStrategy(
|
const inputStrategy = new VSCodePromptInputStrategy(
|
||||||
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(
|
||||||
text,
|
text,
|
||||||
fn,
|
fn,
|
||||||
inputStrategy,
|
inputStrategy,
|
||||||
window.showInformationMessage
|
window.showInformationMessage
|
||||||
);
|
);
|
||||||
await window.showInformationMessage(`Output: ${output}`);
|
await window.showInformationMessage(`Output: ${output}`);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
147
client/src/exec/BranFlakesExecutorVisitor.ts
Normal file
147
client/src/exec/BranFlakesExecutorVisitor.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
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';
|
||||||
|
import { AbortClassRequestor } from './AbortRequestor';
|
||||||
|
|
||||||
|
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 abortRequestor?: AbortClassRequestor,
|
||||||
|
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>,
|
||||||
|
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, aborter);
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,8 +4,8 @@
|
|||||||
* ------------------------------------------------------------------------------------------ */
|
* ------------------------------------------------------------------------------------------ */
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { commands } from 'vscode';
|
import { commands, tasks, workspace,window } from 'vscode';
|
||||||
import type { ExtensionContext } from 'vscode';
|
import type { Disposable, ExtensionContext } from 'vscode';
|
||||||
import {
|
import {
|
||||||
LanguageClient,
|
LanguageClient,
|
||||||
LanguageClientOptions,
|
LanguageClientOptions,
|
||||||
@@ -13,9 +13,12 @@ import {
|
|||||||
TransportKind,
|
TransportKind,
|
||||||
} from 'vscode-languageclient';
|
} from 'vscode-languageclient';
|
||||||
import { CompileBranFlakesCommand } from './command/CompileBranFlakesCommand';
|
import { CompileBranFlakesCommand } from './command/CompileBranFlakesCommand';
|
||||||
|
import { CustomExecutionTaskProvider } from './task/CustomExecutionTerminal';
|
||||||
|
|
||||||
let client: LanguageClient;
|
let client: LanguageClient;
|
||||||
|
|
||||||
|
let bfRunTaskProvider: Disposable;
|
||||||
|
|
||||||
export function activate(context: ExtensionContext) {
|
export function activate(context: ExtensionContext) {
|
||||||
// The server is implemented in node
|
// The server is implemented in node
|
||||||
let serverModule = context.asAbsolutePath(
|
let serverModule = context.asAbsolutePath(
|
||||||
@@ -48,10 +51,13 @@ export function activate(context: ExtensionContext) {
|
|||||||
commands.registerCommand(
|
commands.registerCommand(
|
||||||
branFlakesCommand.getCommandName(),
|
branFlakesCommand.getCommandName(),
|
||||||
branFlakesCommand.getCommandHandler()
|
branFlakesCommand.getCommandHandler()
|
||||||
)
|
),
|
||||||
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Create the language client and start the client.
|
// Create the language client and start the client.
|
||||||
client = new LanguageClient(
|
client = new LanguageClient(
|
||||||
'brainfucklanguageserver',
|
'brainfucklanguageserver',
|
||||||
@@ -62,11 +68,17 @@ export function activate(context: ExtensionContext) {
|
|||||||
|
|
||||||
// Start the client. This will also launch the server
|
// Start the client. This will also launch the server
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
|
const workspaceRoot = (workspace.workspaceFolders && (workspace.workspaceFolders.length > 0))
|
||||||
|
? workspace.workspaceFolders[0].uri.fsPath : undefined;
|
||||||
|
|
||||||
|
bfRunTaskProvider = tasks.registerTaskProvider(CustomExecutionTaskProvider.type, new CustomExecutionTaskProvider(workspaceRoot));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deactivate(): Thenable<void> | undefined {
|
export function deactivate(): Thenable<void> | undefined {
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return client.stop();
|
bfRunTaskProvider?.dispose();
|
||||||
|
client?.stop();
|
||||||
}
|
}
|
||||||
|
@@ -1,74 +1,152 @@
|
|||||||
import path = require('node:path');
|
|
||||||
import * as vscode from 'vscode';
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomExecutionTaskProvider implements vscode.TaskProvider {
|
||||||
|
static type: string = 'bf-run';
|
||||||
|
tasks: vscode.Task[] | undefined;
|
||||||
|
|
||||||
|
constructor(private workspaceRoot: string | undefined) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class CustomExecutionTaskProvider implements vscode.TaskProvider{
|
|
||||||
|
|
||||||
provideTasks(token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task[]> {
|
provideTasks(token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task[]> {
|
||||||
throw new Error('Method not implemented.');
|
if (this.tasks !== undefined) { return this.tasks; }
|
||||||
}
|
|
||||||
resolveTask(task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> {
|
this.tasks = [this.getTaskFromDefinition(undefined)];
|
||||||
const taskDefinition = {};
|
return this.tasks;
|
||||||
throw new Error('5');
|
|
||||||
// return new vscode.Task()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 CustomBuildTaskTerminal(definition.file);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveTask(_task: vscode.Task, token?: vscode.CancellationToken): vscode.ProviderResult<vscode.Task> {
|
||||||
|
const definition: BFRunTaskDefinition = <any>_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 () => {
|
||||||
|
return new CustomBuildTaskTerminal(definition.file);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
|
||||||
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;
|
||||||
|
onDidWrite: vscode.Event<string> = this.writeEmitter.event;
|
||||||
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
onDidClose?: vscode.Event<number> = this.closeEmitter.event;
|
||||||
|
abortRequestor = new AbortClassRequestor();
|
||||||
|
|
||||||
private fileWatcher: vscode.FileSystemWatcher | undefined;
|
handleInput(data: string): void {
|
||||||
|
// this.writeEmitter.fire(`Echo(${data.length})` + data);
|
||||||
constructor(private workspaceRoot: string, private flavor: string, private flags: string[], private getSharedState: () => string | undefined, private setSharedState: (state: string) => void) {
|
const newData = [...data].map(e => e.charCodeAt(0));
|
||||||
|
console.log('new input', newData);
|
||||||
|
this.inputQueue.push(...newData);
|
||||||
|
this.writeEmitter.fire(replaceLFWithCRLF(data));
|
||||||
|
this.readEmitter.fire();
|
||||||
}
|
}
|
||||||
|
|
||||||
open(initialDimensions: vscode.TerminalDimensions | undefined): void {
|
|
||||||
|
constructor(private fileName?: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
open(_initialDimensions: vscode.TerminalDimensions | undefined): void {
|
||||||
// At this point we can start using the terminal.
|
// At this point we can start using the terminal.
|
||||||
if (this.flags.indexOf('watch') > -1) {
|
this.openDocumentForTask().then(this.doExecution.bind(this));
|
||||||
const pattern = path.join(this.workspaceRoot, 'customBuildFile');
|
}
|
||||||
this.fileWatcher = vscode.workspace.createFileSystemWatcher(pattern);
|
|
||||||
this.fileWatcher.onDidChange(() => this.doBuild());
|
getPath(fileLocationString: string | undefined, fileName: string) {
|
||||||
this.fileWatcher.onDidCreate(() => this.doBuild());
|
if (fileLocationString === undefined) { return vscode.Uri.file(fileName); }
|
||||||
this.fileWatcher.onDidDelete(() => this.doBuild());
|
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.doBuild();
|
this.openDocument = openDocument;
|
||||||
}
|
}
|
||||||
|
|
||||||
close(): void {
|
close(): void {
|
||||||
// The terminal has been closed. Shutdown the build.
|
// The terminal has been closed. Shutdown the build.
|
||||||
if (this.fileWatcher) {
|
console.log('Forced close');
|
||||||
this.fileWatcher.dispose();
|
this.abortRequestor.requestAbort();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doBuild(): Promise<void> {
|
private async doExecution(): Promise<void> {
|
||||||
return new Promise<void>((resolve) => {
|
this.writeEmitter.fire('[bf] Requested execution of ' + (this.fileName ?? 'active file') + '\r\n');
|
||||||
this.writeEmitter.fire('Starting build...\r\n');
|
const cus = this;
|
||||||
let isIncremental = this.flags.indexOf('incremental') > -1;
|
try {
|
||||||
if (isIncremental) {
|
|
||||||
if (this.getSharedState()) {
|
await BranFlakesExecutorVisitor.run(this.openDocument.getText(),
|
||||||
this.writeEmitter.fire('Using last build results: ' + this.getSharedState() + '\r\n');
|
this.openDocument.uri.fsPath,
|
||||||
} else {
|
{
|
||||||
isIncremental = false;
|
getInput() {
|
||||||
this.writeEmitter.fire('No result from last build. Doing full build.\r\n');
|
return new Promise((res, rej) => {
|
||||||
}
|
if (cus.inputQueue.length > 0) {
|
||||||
}
|
const char = cus.inputQueue.shift();
|
||||||
|
res(char);
|
||||||
|
} else {
|
||||||
|
const dispose: vscode.Disposable[] = [];
|
||||||
|
cus.readEmitter.event(e => {
|
||||||
|
const char = cus.inputQueue.shift();
|
||||||
|
//clear the earliest disposable
|
||||||
|
dispose.shift()?.dispose();
|
||||||
|
res(char);
|
||||||
|
}, null, dispose);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (data) => {
|
||||||
|
this.writeEmitter.fire(replaceLFWithCRLF(data));
|
||||||
|
},this.abortRequestor
|
||||||
|
);
|
||||||
|
this.closeEmitter.fire(0);
|
||||||
|
} catch (e) {
|
||||||
|
this.closeEmitter.fire(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Since we don't actually build anything in this example set a timeout instead.
|
|
||||||
setTimeout(() => {
|
|
||||||
const date = new Date();
|
|
||||||
this.setSharedState(date.toTimeString() + ' ' + date.toDateString());
|
|
||||||
this.writeEmitter.fire('Build complete.\r\n\r\n');
|
|
||||||
if (this.flags.indexOf('watch') === -1) {
|
|
||||||
this.closeEmitter.fire(0);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}, isIncremental ? 1000 : 4000);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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'],
|
||||||
|
7596
package-lock.json
generated
7596
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@@ -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.1",
|
||||||
"icon": "assets/128.png",
|
"icon": "assets/128.png",
|
||||||
"categories": ["Programming Languages","Linters"],
|
"categories": ["Programming Languages","Linters"],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/chrisvrose/bf-server"
|
"url": "https://github.com/chrisvrose/bf-server"
|
||||||
},
|
},
|
||||||
"main": "./client/dist/extension",
|
"main": "./client/dist/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
@@ -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"
|
||||||
@@ -85,7 +85,18 @@
|
|||||||
"description": "Traces the communication between VS Code and the language server."
|
"description": "Traces the communication between VS Code and the language server."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"taskDefinitions": [
|
||||||
|
{
|
||||||
|
"type": "bf-run",
|
||||||
|
"properties": {
|
||||||
|
"file":{
|
||||||
|
"type":"string",
|
||||||
|
"description": "The BF file to be executed. Can be omitted to run current file"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"vscode:prepublish": "npm run compile",
|
"vscode:prepublish": "npm run compile",
|
||||||
|
@@ -5,7 +5,8 @@
|
|||||||
"lib": ["ES2019"],
|
"lib": ["ES2019"],
|
||||||
"outDir": "out",
|
"outDir": "out",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"sourceMap": true
|
"sourceMap": true,
|
||||||
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
Reference in New Issue
Block a user