diff --git a/README.md b/README.md index 1d9098f..77cf601 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +- 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 \ No newline at end of file diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..609ea2c Binary files /dev/null and b/assets/screenshot.png differ diff --git a/client/src/BranFlakesExecutorVisitor.ts b/client/src/BranFlakesExecutorVisitor.ts index dccaa51..41bb667 100644 --- a/client/src/BranFlakesExecutorVisitor.ts +++ b/client/src/BranFlakesExecutorVisitor.ts @@ -16,32 +16,27 @@ export default class BranFlakesExecutorVisitor * @param inputPtr Input pointer to start from */ constructor( - protected inputStrategy: InputStrategy, - protected logger: (val: string) => Thenable, - protected inputPtr: number = 0 + private inputStrategy: InputStrategy, + private logger: (val: string) => Thenable, + private inputPtr: number = 0 ) { super(); } // /** // * The memory cells (Can work with negative cells this way) // */ - // protected cells: Map = new Map(); + // private cells: Map = 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 + logger: (str: string) => Thenable ) { //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 { - // await this.logger("checking "+node.constructor.name) let result = this.defaultResult(); await result; let n = node.childCount; diff --git a/client/src/command/Command.ts b/client/src/command/Command.ts new file mode 100644 index 0000000..da04d5d --- /dev/null +++ b/client/src/command/Command.ts @@ -0,0 +1,5 @@ + +export interface Command{ + getCommandName():string; + getCommandHandler():(...args:any)=>Promise; +} \ No newline at end of file diff --git a/client/src/command/CompileCommand.ts b/client/src/command/CompileCommand.ts new file mode 100644 index 0000000..d639080 --- /dev/null +++ b/client/src/command/CompileCommand.ts @@ -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}`); + }; + } +} diff --git a/client/src/extension.ts b/client/src/extension.ts index 2bd3f75..b1e4edb 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -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 | undefined { - if (!client) { - return undefined; - } - return client.stop(); + if (!client) { + return undefined; + } + return client.stop(); } diff --git a/client/src/input/VSCodePromptInputStrategy.ts b/client/src/input/VSCodePromptInputStrategy.ts index ca084b8..cec77c2 100644 --- a/client/src/input/VSCodePromptInputStrategy.ts +++ b/client/src/input/VSCodePromptInputStrategy.ts @@ -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) {} + private inputQueue: string = ''; + constructor( + private requestor: ( + promptOptions?: InputBoxOptions, + cancelToken?: CancellationToken + ) => Thenable + ) {} async getInput(): Promise { while (this.inputQueue.length == 0) { @@ -11,11 +16,13 @@ export class VSCodePromptInputStrategy implements InputStrategy { } const character = this.inputQueue.charCodeAt(0); this.inputQueue = this.inputQueue.substring(1); - + 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; } } diff --git a/package.json b/package.json index e9d75f3..449743f 100644 --- a/package.json +++ b/package.json @@ -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": [