UNPKG

tcl-js

Version:

tcl-js is a tcl intepreter written completely in Typescript. It is meant to replicate the tcl-sh interpreter as closely as possible.

276 lines (248 loc) 7.73 kB
import { TclVariableHolder, TclVariable, TclProc, TclProcHolder, TclProcFunction, TclProcOptionsEmpty, } from './types'; import { LoadFunctions } from './commands'; import { TclError } from './tclerror'; type settingObj = | boolean | { [index: string]: boolean; }; /** * A class to keep track of all variables in an interpreter, and manage access to them * * @export * @class Scope */ export class Scope { private parent: Scope | null = null; // Initialize two holders for variables and for procedures private members: TclVariableHolder = {}; private procedures: TclProcHolder = {}; private settings: { [index: string]: settingObj; } = {}; /** * Creates an instance of Scope. * * @param {Scope} [parent] - Parent scope, it will extract variables from here if it doesnt have then itself * @param {Array<string>} [disableProcs=[]] - A list of procedures you want disabled * @memberof Scope */ public constructor(parent?: Scope, disableProcs: Array<string> = []) { // Set the scope if present if (parent) this.parent = parent; else { // If there is no scope parsed, load the base functions because you are the most upper parent for (let loadFunc of LoadFunctions) { loadFunc(this); } // Disable all requested procs for (let disFunc of disableProcs) { this.disableProc(disFunc); } } } // Not needed /** * Return the parent of the scope * * @returns Scope */ /* public pop(): Scope | null { return this.parent; }*/ /** * Define a variable in the current scope * * @param {string} name - The name of the variable * @param {TclVariable} value - The variable to put there * @returns {Scope} - The current scope * @memberof Scope */ public define(name: string, value: TclVariable): Scope { // Try to define the variable in the top most scope if (this.parent !== null) { this.parent.define(name, value); } else { this.members[name] = value; } return this; } /** * Delete a variable from the scope * * @param {string} name - Name of the variable to be deleted * @returns {TclVariable | undefined} - The value of the deleted variable, undefined if it didnt exist * @memberof Scope */ public undefine(name: string): TclVariable | undefined { // Check if variable exists if (!Object.prototype.hasOwnProperty.call(this.members, name)) { // If there is a parent, run their undefine function if (this.parent) return this.parent.undefine(name); else return undefined; } // Delete the variable and return its value let returnValue = this.members[name]; delete this.members[name]; return returnValue; } /** * Resolves a variable and returns it * * @param {string} name - The name of the variable * @returns {(TclVariable | null)} - The variable requested, null if not found * @memberof Scope */ public resolve(name: string): TclVariable | null { // Check this scope if (Object.prototype.hasOwnProperty.call(this.members, name)) { return this.members[name]; } // Check the parent scopes recursively else if (this.parent !== null) { return this.parent.resolve(name); } // Return null if variable is not found return null; } // Not needed /** * Function to fetch all stored variables * * @returns TclVariable */ /* public resolveAll(): TclVariable[] { let out: TclVariable[] = []; // Grab values from parent if exists if(this.parent) out = this.parent.resolveAll(); // Grab own values let values = Object.values(this.members); // Stitch together and return return [...out, ...values]; }*/ /** * This is used to add a new function to the current scope * * @param {string} name - The name of the procedure * @param {TclProcFunction} callback - The js function that will be called to process the procedure * @param {TclProcOptionsEmpty} [options] - The options for the procedure * @memberof Scope */ public defineProc( name: string, callback: TclProcFunction, options?: TclProcOptionsEmpty, ) { // Try to define the procedure in the top most scope if (this.parent !== null) { this.parent.defineProc(name, callback, options); } else { this.procedures[name] = new TclProc(name, callback, options); } } /** * This is used to remove a function from the current scope * * @param {string} name - The name of the procedure * @memberof Scope */ public disableProc(name: string) { // Remove the procedure if it exists, if not throw an error if (Object.prototype.hasOwnProperty.call(this.procedures, name)) { delete this.procedures[name]; } else { throw new TclError(`can't disable "${name}": no such function`); } } /** * This is used to request the scope for a procedure * * @param {string} name - The name of the procedure you want to get * @returns {(TclProc | null)} - An object containing the callback with its name, null if not found * @memberof Scope */ public resolveProc(name: string): TclProc | null { // Check if this scope has the function and return if so if (Object.prototype.hasOwnProperty.call(this.procedures, name)) { return this.procedures[name]; } // If not ask the parent scope and return if it has it else if (this.parent !== null) { return this.parent.resolveProc(name); } // Return null if the function is not found return null; } /** * Set a scoped setting * * @param {string} name - The name of the setting to set * @param {(boolean | null)} value - The value to put there, use null to remove setting * @memberof Scope */ public setSetting(name: string, value: boolean | null) { if (value !== null) this.settings[name] = value; else delete this.settings[name]; } /** * Set a sub setting, this will replace the main setting with an object * * @param {string} setting - The main setting to put the subsetting in * @param {string} subsetting - The name of the subsetting * @param {(boolean | null)} value - The value to put there, use null to remove setting * @returns {boolean} - If it succeeded * @memberof Scope */ public setSubSetting( setting: string, subsetting: string, value: boolean | null, ): boolean { // Check if the setting is in this scope if (this.settings[setting] !== undefined) { // If the setting is still a boolean replace it with an object if (typeof this.settings[setting] === 'boolean') this.settings[setting] = {}; // If the value to set is null, remove the subsetting if (value === null) { delete (<{ [index: string]: boolean }>this.settings[setting])[ subsetting ]; } // Otherwise set the subsetting to the correct variable else { (<{ [index: string]: boolean }>this.settings[setting])[ subsetting ] = value; } return true; } // If not test in the parent else if (this.parent !== null) { return this.parent.setSubSetting(setting, subsetting, value); } // If there is no parent return false else return false; } /** * Get a scoped setting * * @param {string} name * @returns {(settingObj | null)} * @memberof Scope */ public getSetting(name: string): settingObj | null { if (this.settings[name] !== undefined) return this.settings[name]; else if (this.parent !== null) return this.parent.getSetting(name); else return null; } }