[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 # 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 ## Functionality
- [X] Syntax - Syntax Highlighting
- [X] Bracket matching - Execution
- [X] Autocomplete suggestions - Autocomplete suggestions
- [X] Extension icon
- [X] Execution
- [ ] Timeout
### Execution ### Execution
Use the command to execute the code. 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. If the program requires input, it will be requested as a prompt.
TODO: Implement a timeout. TODO: Implement a timeout.
### Changelog ### Changelog
#### 0.2.0
- Cycle input pointer on overflow/underflow
- Refactoring code
#### 0.1.0 #### 0.1.0
- Request input as required during execution - Request input as required during execution
- Using array-based indexing. This implies that only positive indices upto 30k are supported. - 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 * @param inputPtr Input pointer to start from
*/ */
constructor( constructor(
protected inputStrategy: InputStrategy, private inputStrategy: InputStrategy,
protected logger: (val: string) => Thenable<string>, private logger: (val: string) => Thenable<string>,
protected inputPtr: number = 0 private inputPtr: number = 0
) { ) {
super(); super();
} }
// /** // /**
// * The memory cells (Can work with negative cells this way) // * 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; private byteArraySize: number = 30000;
protected byteArray: Int8Array = new Int8Array(this.byteArraySize); private byteArray: Int8Array = new Int8Array(this.byteArraySize);
/** /**
* Pointer * Pointer
*/ */
protected ptr: number = 0; private ptr: number = 0;
/** Output string */ /** Output string */
protected outputStrArray: string[] = []; private outputStr: string = '';
/**
* Output string (Available only after visiting)
*/
public get outputStr() {
return this.outputStrArray.join('');
}
defaultResult() { defaultResult() {
return Promise.resolve(); return Promise.resolve();
@@ -57,7 +52,7 @@ export default class BranFlakesExecutorVisitor
text: string, text: string,
fn: string, fn: string,
inputStrategy: InputStrategy, inputStrategy: InputStrategy,
logger: (str:string) => Thenable<string> logger: (str: string) => Thenable<string>
) { ) {
//get tree and issues //get tree and issues
const { tree, issues } = getTree(text, fn); const { tree, issues } = getTree(text, fn);
@@ -90,10 +85,10 @@ export default class BranFlakesExecutorVisitor
} }
} }
async visitPtrLeft() { async visitPtrLeft() {
--this.ptr; this.ptr = (this.ptr + this.byteArraySize - 1) % this.byteArraySize;
} }
async visitPtrRight() { async visitPtrRight() {
++this.ptr; this.ptr = (this.ptr + this.byteArraySize + 1) % this.byteArraySize;
} }
async visitPtrIncr() { async visitPtrIncr() {
const val = this.getCell(this.ptr); const val = this.getCell(this.ptr);
@@ -107,12 +102,12 @@ export default class BranFlakesExecutorVisitor
const val = this.getCell(this.ptr) ?? 0; const val = this.getCell(this.ptr) ?? 0;
const str = String.fromCharCode(val); const str = String.fromCharCode(val);
this.outputStrArray.push(str); this.outputStr += str;
} }
async visitInputStmt() { async visitInputStmt() {
//get char //get char
const char = await this.inputStrategy.getInput() ?? 0; const char = (await this.inputStrategy.getInput()) ?? 0;
//increment the input pointer after this //increment the input pointer after this
this.inputPtr++; this.inputPtr++;
this.setCell(this.ptr, char); this.setCell(this.ptr, char);
@@ -120,7 +115,6 @@ export default class BranFlakesExecutorVisitor
// override for maintaining async // override for maintaining async
async visitChildren(node: RuleNode): Promise<void> { async visitChildren(node: RuleNode): Promise<void> {
// await this.logger("checking "+node.constructor.name)
let result = this.defaultResult(); let result = this.defaultResult();
await result; await result;
let n = node.childCount; 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 * as path from 'path';
import { ExtensionContext,commands, window } from 'vscode'; import { ExtensionContext, commands, window } from 'vscode';
import { import {
LanguageClient, LanguageClient,
LanguageClientOptions, LanguageClientOptions,
ServerOptions, ServerOptions,
TransportKind TransportKind,
} from 'vscode-languageclient'; } from 'vscode-languageclient';
import BranFlakesExecutorVisitor from './BranFlakesExecutorVisitor'; import { CompileBranFlakesCommand } from './command/CompileCommand';
import { VSCodePromptInputStrategy } from './input/VSCodePromptInputStrategy';
let client: LanguageClient; let client: LanguageClient;
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(
path.join('server', 'dist', 'server.js') path.join('server', 'dist', 'server.js')
); );
// The debug options for the server // 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 // --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'] }; let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
// If the extension is launched in debug mode then the debug server options are used // If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used // Otherwise the run options are used
let serverOptions: ServerOptions = { let serverOptions: ServerOptions = {
run: { module: serverModule, transport: TransportKind.ipc }, run: { module: serverModule, transport: TransportKind.ipc },
debug: { debug: {
module: serverModule, module: serverModule,
transport: TransportKind.ipc, transport: TransportKind.ipc,
options: debugOptions options: debugOptions,
} },
}; };
// Options to control the language client // Options to control the language client
let clientOptions: LanguageClientOptions = { let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents // Register the server for plain text documents
documentSelector: [{ scheme: 'file', language: 'bf' }] documentSelector: [{ scheme: 'file', language: 'bf' }],
}; };
const command = 'bf.execute'; const branFlakesCommands = [new CompileBranFlakesCommand()];
const commandHandler = async()=>{ for (let branFlakesCommand of branFlakesCommands) {
const text= window.activeTextEditor.document.getText(); context.subscriptions.push(
const fn = window.activeTextEditor.document.fileName; commands.registerCommand(
const inputStrategy = new VSCodePromptInputStrategy(window.showInputBox); branFlakesCommand.getCommandName(),
const output = await BranFlakesExecutorVisitor.run(text,fn,inputStrategy,window.showInformationMessage); branFlakesCommand.getCommandHandler()
await window.showInformationMessage(`Output: ${output}`); )
}; );
}
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. // Start the client. This will also launch the server
client = new LanguageClient( client.start();
'brainfucklanguageserver',
'Brainfuck Language Server',
serverOptions,
clientOptions
);
// Start the client. This will also launch the server
client.start();
} }
export function deactivate(): Thenable<void> | undefined { export function deactivate(): Thenable<void> | undefined {
if (!client) { if (!client) {
return undefined; return undefined;
} }
return client.stop(); return client.stop();
} }

View File

@@ -2,8 +2,13 @@ import { CancellationToken, InputBoxOptions } from 'vscode';
import InputStrategy from './InputStrategy'; import InputStrategy from './InputStrategy';
export class VSCodePromptInputStrategy implements InputStrategy { export class VSCodePromptInputStrategy implements InputStrategy {
private inputQueue:string; private inputQueue: string = '';
constructor(private requestor:(promptOptions?:InputBoxOptions,cancelToken?:CancellationToken)=>Thenable<string>) {} constructor(
private requestor: (
promptOptions?: InputBoxOptions,
cancelToken?: CancellationToken
) => Thenable<string>
) {}
async getInput(): Promise<number> { async getInput(): Promise<number> {
while (this.inputQueue.length == 0) { while (this.inputQueue.length == 0) {
@@ -11,11 +16,13 @@ export class VSCodePromptInputStrategy implements InputStrategy {
} }
const character = this.inputQueue.charCodeAt(0); const character = this.inputQueue.charCodeAt(0);
this.inputQueue = this.inputQueue.substring(1); this.inputQueue = this.inputQueue.substring(1);
return character; return character;
} }
private async requestInputFromPrompt() { 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; this.inputQueue += inputPrompt;
} }
} }

View File

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