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.

261 lines (226 loc) 7.75 kB
import * as minimatch from 'minimatch'; import { TclSimple, TclList } from '../types'; import { Scope } from '../scope'; import { Interpreter } from '../interpreter'; import { TclError } from '../tclerror'; import { Lexer } from '../lexer'; /** * Function to load the procs into the scope * * @export * @param {Scope} scope */ export function Load(scope: Scope) { /** * switch ?options? string { * pattern1 { * body1 * } * ?pattern2 { * body2 * }? * ... * ?patternN { * bodyN * }? * } * * @see https://wiki.tcl-lang.org/page/switch */ scope.defineProc( 'switch', async (interpreter, args, command, helpers) => { args = <string[]>args; // Intialize options let options: { type: null | string; nocase: boolean; matchVar: null | string; indexVar: null | string; } = { type: null, nocase: false, matchVar: null, indexVar: null, }; // Easy function to throw errors function setType(type: string) { if (options.type !== null) return helpers.sendHelp('wset'); options.type = type; } // Loop over all parsed options let arg; while ((arg = args.shift())) { // Stop reading options when the arguments stop starting with - if (!arg.startsWith('-')) { args.unshift(arg); break; } // Stop reading options when the argument is -- if (arg === '--') break; // Type options if (arg === '-exact') setType('exact'); else if (arg === '-glob') setType('glob'); else if (arg === '-regexp') setType('regexp'); // Case sensitive else if (arg === '-nocase') options.nocase = true; // Advanced options else if (arg === '-matchvar') { let matchvar = args.shift(); if (!matchvar) return helpers.sendHelp('wargs'); options.matchVar = matchvar; } else if (arg === '-indexvar') { let indexvar = args.shift(); if (!indexvar) return helpers.sendHelp('wargs'); options.indexVar = indexvar; } else { throw new TclError('unrecognized option: ' + arg); } } // Verify arguments if (!options.type) options.type = 'exact'; if (options.type !== 'regexp' && (options.matchVar || options.indexVar)) return helpers.sendHelp('needregexp'); // Read the string to match against let matchAgainst = args.shift(); if (!matchAgainst) return helpers.sendHelp('wargs'); if (options.nocase) matchAgainst = matchAgainst.toLowerCase(); // Check how many arguments are left // If none, error if (args.length === 0) return helpers.sendHelp('wargs'); // If 1, parse the argument else if (args.length === 1) { // Test if there is data let toLex = args.shift(); if (!toLex) return helpers.sendHelp('wargs'); // Create a new lexer let lexer = new Lexer(toLex); // Read all tokens back into the args variable let token; while ((token = lexer.nextToken())) { args.push(token.value); } } // Check if there are enough arguments for a switch statement if (args.length <= 1) return helpers.sendHelp('wargs'); // Create a new array for all switch operations let switchOps: Array<{ expression: string; code: string; }> = []; // Loop over the left args and put them in their corresponding place in the switchops let prop; while ((prop = args.shift())) { let expression = prop; let code = args.shift(); if (!code) return helpers.sendHelp('wargs'); switchOps.push({ expression, code, }); } // Function to get the code at a index while correctly parsing the - character function getCodeAtIndex(index: number): string { if (!switchOps[index]) return helpers.sendHelp('nocode'); let code = switchOps[index].code; if (code === '-') return getCodeAtIndex(index + 1); else return code; } // A variable for the code that will eventually be ran let runCode: null | string = null; // Check for exact match if (options.type === 'exact') { for (let i = 0; i < switchOps.length; i++) { let op = switchOps[i]; if (options.nocase) op.expression = op.expression.toLowerCase(); if (op.expression === 'default') continue; if (op.expression === matchAgainst) { runCode = getCodeAtIndex(i); break; } } } // Check for a global match else if (options.type === 'glob') { for (let i = 0; i < switchOps.length; i++) { let op = switchOps[i]; if (options.nocase) op.expression = op.expression.toLowerCase(); if (op.expression === 'default') continue; if (minimatch(matchAgainst, op.expression, { nocomment: true })) { runCode = getCodeAtIndex(i); break; } } } // Check for a regular expression match else if (options.type === 'regexp') { for (let i = 0; i < switchOps.length; i++) { let op = switchOps[i]; if (options.nocase) op.expression = op.expression.toLowerCase(); if (op.expression === 'default') continue; let rex = new RegExp(op.expression, 'u'); let rexResults = rex.exec(matchAgainst); if (rexResults !== null) { if (options.matchVar) { let listResult = TclList.createList(rexResults); interpreter.setVariable(options.matchVar, null, listResult); } if (options.indexVar) { let indexv = []; for (let rexResult of rexResults) { let foundIndex = matchAgainst.indexOf(rexResult); indexv.push([foundIndex, foundIndex + rexResult.length - 1]); } let listResult = TclList.createList(indexv); interpreter.setVariable(options.indexVar, null, listResult); } runCode = getCodeAtIndex(i); break; } } } // If non throw an error else { throw new TclSimple('invalid switchtype'); } // Check if there is code, if not try to find a default statement if (!runCode) { for (let i = 0; i < switchOps.length; i++) { let op = switchOps[i]; op.expression = op.expression.toLowerCase(); if (op.expression === 'default') { runCode = getCodeAtIndex(i); break; } } // If there is still nothing return an empty var if (!runCode) return new TclSimple(''); } // Interpret the procedures tcl code with the new scope let newInterpreter = new Interpreter( interpreter.getTcl(), runCode, new Scope(interpreter.getScope()), ); // Return the result return newInterpreter.run(); }, { arguments: { pattern: `switch ?options? string { pattern1 { body1 } ?pattern2 { body2 }? ... ?patternN { bodyN }? }`, textOnly: true, amount: { start: 2, end: -1, }, }, helpMessages: { wset: 'type of switch was already set', needregexp: 'You need the -regexp flag enabled to use -indexvar or -matchvar', nocode: 'there was no executable code to be found with the correct expression', }, }, ); }