[refactor] commands

This commit is contained in:
2024-01-02 17:15:19 +05:30
parent 6e143cd4d0
commit 0a2a6c4db6
8 changed files with 127 additions and 90 deletions

View File

@@ -1,31 +1,39 @@
# BF extension
A simple language server based VSCode Extension for the ~~Branflakes~~ BF language. You can also execute your code and see its output.
A simple language server based VSCode Extension for the (Branflakes?) (BrainFuck?) BF language. You can also execute your code and see its output.
![BF](./assets/screenshot.png)
![BF](https://kekvrose.me/static/projects/screenshots/bf-server.png)
## Functionality
- [X] Syntax
- [X] Bracket matching
- [X] Autocomplete suggestions
- [X] Extension icon
- [X] Execution
- [ ] Timeout
- Syntax Highlighting
- Execution
- Autocomplete suggestions
### 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.
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.
If the program requires input, it will be requested as a prompt.
TODO: Implement a timeout.
### Changelog
#### 0.2.0
- Cycle input pointer on overflow/underflow
- Refactoring code
#### 0.1.0
- Request input as required during execution
- Using array-based indexing. This implies that only positive indices upto 30k are supported.
### Building it
1. `npm i` - Install all dependencies
2. `npm i -g @vscode/vsce` - Install VSCode Command line CLI
3. `vsce package` - Package to VSIX

BIN
assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -16,32 +16,27 @@ export default class BranFlakesExecutorVisitor
* @param inputPtr Input pointer to start from
*/
constructor(
protected inputStrategy: InputStrategy,
protected logger: (val: string) => Thenable<string>,
protected inputPtr: number = 0
private inputStrategy: InputStrategy,
private logger: (val: string) => Thenable<string>,
private inputPtr: number = 0
) {
super();
}
// /**
// * The memory cells (Can work with negative cells this way)
// */
// protected cells: Map<number, number> = new Map();
// private cells: Map<number, number> = new Map();
protected byteArraySize: number = 30000;
protected byteArray: Int8Array = new Int8Array(this.byteArraySize);
private byteArraySize: number = 30000;
private byteArray: Int8Array = new Int8Array(this.byteArraySize);
/**
* Pointer
*/
protected ptr: number = 0;
private ptr: number = 0;
/** Output string */
protected outputStrArray: string[] = [];
private outputStr: string = '';
/**
* Output string (Available only after visiting)
*/
public get outputStr() {
return this.outputStrArray.join('');
}
defaultResult() {
return Promise.resolve();
@@ -57,7 +52,7 @@ export default class BranFlakesExecutorVisitor
text: string,
fn: string,
inputStrategy: InputStrategy,
logger: (str:string) => Thenable<string>
logger: (str: string) => Thenable<string>
) {
//get tree and issues
const { tree, issues } = getTree(text, fn);
@@ -90,10 +85,10 @@ export default class BranFlakesExecutorVisitor
}
}
async visitPtrLeft() {
--this.ptr;
this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
}
async visitPtrRight() {
++this.ptr;
this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
}
async visitPtrIncr() {
const val = this.getCell(this.ptr);
@@ -107,12 +102,12 @@ export default class BranFlakesExecutorVisitor
const val = this.getCell(this.ptr) ?? 0;
const str = String.fromCharCode(val);
this.outputStrArray.push(str);
this.outputStr += str;
}
async visitInputStmt() {
//get char
const char = await this.inputStrategy.getInput() ?? 0;
const char = (await this.inputStrategy.getInput()) ?? 0;
//increment the input pointer after this
this.inputPtr++;
this.setCell(this.ptr, char);
@@ -120,7 +115,6 @@ export default class BranFlakesExecutorVisitor
// override for maintaining async
async visitChildren(node: RuleNode): Promise<void> {
// await this.logger("checking "+node.constructor.name)
let result = this.defaultResult();
await result;
let n = node.childCount;

View File

@@ -0,0 +1,5 @@
export interface Command{
getCommandName():string;
getCommandHandler():(...args:any)=>Promise<any>;
}

View File

@@ -0,0 +1,26 @@
import { window } from 'vscode';
import { Command as BranFlakesCommand } from './Command';
import { VSCodePromptInputStrategy } from '../input/VSCodePromptInputStrategy';
import BranFlakesExecutorVisitor from '../BranFlakesExecutorVisitor';
export class CompileBranFlakesCommand implements BranFlakesCommand {
getCommandName() {
return 'bf.execute';
}
getCommandHandler() {
return async () => {
const text = window.activeTextEditor.document.getText();
const fn = window.activeTextEditor.document.fileName;
const inputStrategy = new VSCodePromptInputStrategy(
window.showInputBox
);
const output = await BranFlakesExecutorVisitor.run(
text,
fn,
inputStrategy,
window.showInformationMessage
);
await window.showInformationMessage(`Output: ${output}`);
};
}
}

View File

@@ -4,71 +4,68 @@
* ------------------------------------------------------------------------------------------ */
import * as path from 'path';
import { ExtensionContext,commands, window } from 'vscode';
import { ExtensionContext, commands, window } from 'vscode';
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind,
} from 'vscode-languageclient';
import BranFlakesExecutorVisitor from './BranFlakesExecutorVisitor';
import { VSCodePromptInputStrategy } from './input/VSCodePromptInputStrategy';
import { CompileBranFlakesCommand } from './command/CompileCommand';
let client: LanguageClient;
export function activate(context: ExtensionContext) {
// The server is implemented in node
let serverModule = context.asAbsolutePath(
path.join('server', 'dist', 'server.js')
);
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
// The server is implemented in node
let serverModule = context.asAbsolutePath(
path.join('server', 'dist', 'server.js')
);
// The debug options for the server
// --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging
let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: debugOptions
}
};
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc },
debug: {
module: serverModule,
transport: TransportKind.ipc,
options: debugOptions,
},
};
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [{ scheme: 'file', language: 'bf' }]
};
// Options to control the language client
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [{ scheme: 'file', language: 'bf' }],
};
const command = 'bf.execute';
const commandHandler = async()=>{
const text= window.activeTextEditor.document.getText();
const fn = window.activeTextEditor.document.fileName;
const inputStrategy = new VSCodePromptInputStrategy(window.showInputBox);
const output = await BranFlakesExecutorVisitor.run(text,fn,inputStrategy,window.showInformationMessage);
await window.showInformationMessage(`Output: ${output}`);
};
const branFlakesCommands = [new CompileBranFlakesCommand()];
for (let branFlakesCommand of branFlakesCommands) {
context.subscriptions.push(
commands.registerCommand(
branFlakesCommand.getCommandName(),
branFlakesCommand.getCommandHandler()
)
);
}
context.subscriptions.push(commands.registerCommand(command,commandHandler));
// Create the language client and start the client.
client = new LanguageClient(
'brainfucklanguageserver',
'Brainfuck Language Server',
serverOptions,
clientOptions
);
// Create the language client and start the client.
client = new LanguageClient(
'brainfucklanguageserver',
'Brainfuck Language Server',
serverOptions,
clientOptions
);
// Start the client. This will also launch the server
client.start();
// Start the client. This will also launch the server
client.start();
}
export function deactivate(): Thenable<void> | undefined {
if (!client) {
return undefined;
}
return client.stop();
if (!client) {
return undefined;
}
return client.stop();
}

View File

@@ -2,8 +2,13 @@ import { CancellationToken, InputBoxOptions } from 'vscode';
import InputStrategy from './InputStrategy';
export class VSCodePromptInputStrategy implements InputStrategy {
private inputQueue:string;
constructor(private requestor:(promptOptions?:InputBoxOptions,cancelToken?:CancellationToken)=>Thenable<string>) {}
private inputQueue: string = '';
constructor(
private requestor: (
promptOptions?: InputBoxOptions,
cancelToken?: CancellationToken
) => Thenable<string>
) {}
async getInput(): Promise<number> {
while (this.inputQueue.length == 0) {
@@ -15,7 +20,9 @@ export class VSCodePromptInputStrategy implements InputStrategy {
return character;
}
private async requestInputFromPrompt() {
const inputPrompt = await this.requestor({prompt:"More input is required. Please provide input:"});
const inputPrompt = await this.requestor({
prompt: 'More input is required. Please provide input:',
});
this.inputQueue += inputPrompt;
}
}

View File

@@ -5,7 +5,7 @@
"author": "Atreya Bain",
"license": "MIT",
"publisher": "atreyabain",
"version": "0.1.0",
"version": "0.2.0",
"icon": "assets/128.png",
"categories": [],
"keywords": [