diff --git a/lib/tput.js b/lib/tput.js index 6080ee7..b97fbac 100644 --- a/lib/tput.js +++ b/lib/tput.js @@ -253,445 +253,455 @@ Tput.prototype.parseExtended = function(data) { return info; }; -Tput.prototype.invoke = function(key, prefix, params, suffix) { - var self = this; - if (!this.info.all) { - this.info.all = {}; - Object.keys(info.bools).forEach(function(key) { - self.info.all[key] = info.bools; - }); - Object.keys(info.numbers).forEach(function(key) { - self.info.all[key] = info.numbers; - }); - Object.keys(info.strings).forEach(function(key) { - self.info.all[key] = info.strings; - }); - } +Tput.prototype.compile = function(key) { + var self = this + , info = this.info; - var val = this.info.all[key]; - if (val == null) return; + this.methods = {}; + this.info.all = {}; + + Object.keys(info.bools).forEach(function(key) { + info.all[key] = info.bools; + }); + + Object.keys(info.numbers).forEach(function(key) { + info.all[key] = info.numbers; + }); + + Object.keys(info.strings).forEach(function(key) { + info.all[key] = info.strings; + }); + + Object.keys(info.all).forEach(function(key) { + self.methods[key] = self._compile(info.all[key]); + }); +}; + +Tput.prototype._compile = function(val) { + var self = this; switch (typeof val) { case 'boolean': - val = val ? 'true' : 'false'; - break; + return function() { + return val ? 'true' : 'false'; + }; case 'number': - //val = val === -1 ? '' : val + ''; - val = val + ''; - break; + return function() { + return val === -1 ? null : val; + }; case 'string': - // e.g. - // set_attributes: '%?%p9%t\u001b(0%e\u001b(B%;\u001b[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m', - // cursor_address: '\u001b[%i%p1%d;%p2%dH', - // column_address: '\u001b[%i%p1%dG', - // change_scroll_region: '\u001b[%i%p1%d;%p2%dr', - // CSI Ps ; Ps r - // CSI ? Pm r - - var code = 'var dyn = {}, stat = {}, stack = [], out = []; out.push("'; - - // man terminfo, around line 940 - - while (val) { - // '\e' -> ^[ - if (cap = /^\\e/gi.exec(val)) { - val = val.substring(cap[0].length); - code += '\x1b'; - continue; - } - - // '^A' -> ^A - if (cap = /^\^(.)/gi.exec(val)) { // case-insensitive? - val = val.substring(cap[0].length); - ch = cap[1]; - switch (ch) { - case '@': - code += '\x00'; - break; - case 'A': - code += '\x01'; - break; - case 'B': - code += '\x02'; - break; - case 'C': - code += '\x03'; - break; - case 'D': - code += '\x04'; - break; - case 'E': - code += '\x05'; - break; - case 'F': - code += '\x06'; - break; - case 'G': - code += '\x07'; - break; - case 'H': - code += '\x08'; - break; - case 'I': - code += '\x09'; // \t - break; - case 'J': - code += '\x0a'; // \n - break; - case 'K': - code += '\x0b'; - break; - case 'L': - code += '\x0c'; - break; - case 'M': - code += '\x0d'; - break; - case 'N': - code += '\x0e'; - break; - case 'O': - code += '\x0f'; - break; - case 'P': - code += '\x10'; - break; - case 'Q': - code += '\x11'; - break; - case 'R': - code += '\x12'; - break; - case 'S': - code += '\x13'; - break; - case 'T': - code += '\x14'; - break; - case 'U': - code += '\x15'; - break; - case 'V': - code += '\x16'; - break; - case 'W': - code += '\x17'; - break; - case 'X': - code += '\x18'; - break; - case 'Y': - code += '\x19'; - break; - case 'Z': - code += '\x1a'; - break; - case '\\': - code += '\x1c'; - break; - case '^': - code += '\x1e'; - break; - case '_': - code += '\x1f'; - break; - case '[': - code += '\x1b'; - break; - case ']': - code += '\x1d'; - break; - case '?': - code += '\x7f'; - break; - } - continue; - } - - // '\n' -> \n - // '\r' -> \r - // '\0' -> \200 (special case) - if (cap = /^\\([nlrtbfs\^\\,:0])/g.exec(val)) { - val = val.substring(cap[0].length); - ch = cap[1]; - switch (ch) { - case 'n': - return '\n'; - case 'l': - return '\l'; - case 'r': - return '\r'; - case 't': - return '\t'; - case 'b': - return '\b'; - case 'f': - return '\f'; - case 's': - return '\s'; - case '\\': - return '\\'; - case ',': - return ','; - case ';': - return ';'; - case '0': - //return '\0'; - return '\200'; - } - continue; - } - - // 3 octal digits -> character - if (cap = /^\\(\d\d\d)/g.exec(val)) { - val = val.substring(cap[0].length); - ch = cap[1]; - code += String.fromCharCode(parseInt(ch, 8)); - continue; - } - - // $<5> -> padding - if (cap = /^\$<(\d+)>(\*|\/)/g.exec(val)) { - val = val.substring(cap[0].length); - ch = cap[1]; - code += Array(+ch + 1).join(' '); // "padding" characters? - continue; - } - - // man terminfo, around page 1034 - // %% outputs `%' - if (cap = /^%%/g.exec(val)) { - val = val.substring(cap[0].length); - code += '%'; - continue; - } - - // %[[:]flags][width[.precision]][doxXs] - // as in printf, flags are [-+#] and space. Use a `:' to allow the - // next character to be a `-' flag, avoiding interpreting "%-" as an - // operator. - if (cap = /^%(?:(:)?([\-+# ]+)?)(?:(\d+)(\.\d+)?)?([doxXs])?/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'TODO'; - continue; - } - - // %c print pop() like %c in printf - if (cap = /^%c/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'stack.pop()'; // TODO: FORMAT - continue; - } - - // %d print pop() like %d in printf - // NOT SURE ABOUT %d being print! - if (cap = /^%d/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'stack.pop()'; // TODO: FORMAT - continue; - } - - // %s print pop() like %s in printf - if (cap = /^%s/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'stack.pop()'; // TODO: FORMAT - continue; - } - - // %p[1-9] - // push i'th parameter - if (cap = /^%p([1-9])/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'params[i]'; - continue; - } - - // %P[a-z] - // set dynamic variable [a-z] to pop() - if (cap = /^%P([a-z])/g.exec(val)) { - val = val.substring(cap[0].length); - v = cap[1]; - code += 'dyn.' + v + ' = stack.pop()'; - continue; - } - - // %g[a-z] - // get dynamic variable [a-z] and push it - if (cap = /^%g([a-z])/g.exec(val)) { - val = val.substring(cap[0].length); - v = cap[1]; - code += '(stack.push(dyn.' + v + '), dyn.' + v + ')'; - continue; - } - - // %P[A-Z] - // set static variable [a-z] to pop() - if (cap = /^%P([A-Z])/g.exec(val)) { - val = val.substring(cap[0].length); - v = cap[1]; - code += 'stat.' + v + ' = stack.pop()'; - continue; - } - - // %g[A-Z] - // get static variable [a-z] and push it - - // The terms "static" and "dynamic" are misleading. Historically, - // these are simply two different sets of variables, whose values are - // not reset between calls to tparm. However, that fact is not - // documented in other implementations. Relying on it will adversely - // impact portability to other implementations. - - if (cap = /^%g([A-Z])/g.exec(val)) { - val = val.substring(cap[0].length); - v = cap[1]; - code += 'stack.push(stat.' + v + ')'; - continue; - } - - // %'c' char constant c - if (cap = /^%'(\w)'/g.exec(val)) { - val = val.substring(cap[0].length); - ch = cap[1]; - code += '"' + ch + '"'; - continue; - } - - // %{nn} - // integer constant nn - if (cap = /^%\{(\d+)\}/g.exec(val)) { - val = val.substring(cap[0].length); - ch = cap[1]; - code += '(' + ch + ')'; - continue; - } - - // %l push strlen(pop) - if (cap = /^%l/g.exec(val)) { - val = val.substring(cap[0].length); - code += 'stack.push(stack.pop().length)'; - continue; - } - - // %+ %- %* %/ %m - // arithmetic (%m is mod): push(pop() op pop()) - // %& %| %^ - // bit operations (AND, OR and exclusive-OR): push(pop() op pop()) - // %= %> %< - // logical operations: push(pop() op pop()) - if (cap = /^%([+\-*\/m&|\^=><])/g.exec(val)) { - val = val.substring(cap[0].length); - op = cap[1]; - if (op === '=') op = '==='; - else if (op === 'm') op = '%'; - code += 'stack.push(stack.pop() ' + op + ' stack.pop())'; - continue; - } - - // %A, %O - // logical AND and OR operations (for conditionals) - if (cap = /^%([AO])/g.exec(val)) { - val = val.substring(cap[0].length); - op = cap[1]; - code += op === ' A ' ? ' && ' : ' || '; - continue; - } - - // %! %~ - // unary operations (logical and bit complement): push(op pop()) - if (cap = /^%([!~])/g.exec(val)) { - val = val.substring(cap[0].length); - op = cap[1]; - code += 'stack.push(' + op + 'stack.pop())'; - continue; - } - - // %i add 1 to first two parameters (for ANSI terminals) - if (cap = /^%i/g.exec(val)) { - val = val.substring(cap[0].length); - code += '(params[0]++, params[1]++)'; - continue; - } - - // %? expr %t thenpart %e elsepart %; - // This forms an if-then-else. The %e elsepart is optional. Usually - // the %? expr part pushes a value onto the stack, and %t pops it from - // the stack, testing if it is nonzero (true). If it is zero (false), - // control passes to the %e (else) part. - - // It is possible to form else-if's a la Algol 68: - // %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e %; - - // where ci are conditions, bi are bodies. - - // Use the -f option of tic or infocmp to see the structure of - // if-then-else's. Some strings, e.g., sgr can be very complicated when - // written on one line. The -f option splits the string into lines with - // the parts indented. - if (cap = /^%\?/g.exec(val)) { - val = val.substring(cap[0].length); - code += '"); if ('; - continue; - } - - if (cap = /^%t/g.exec(val)) { - val = val.substring(cap[0].length); - code += ') { out.push("'; - continue; - } - - if (cap = /^%e/g.exec(val)) { - val = val.substring(cap[0].length); - code += '"); } else { out.push("'; - continue; - } - - if (cap = /^%;/g.exec(val)) { - val = val.substring(cap[0].length); - code += '"); } out.push("'; - continue; - } - - // Binary operations are in postfix form with the operands in the usual - // order. That is, to get x-5 one would use "%gx%{5}%-". %P and %g vari‐ - // ables are persistent across escape-string evaluations. - - // Consider the HP2645, which, to get to row 3 and column 12, needs to be - // sent \E&a12c03Y padded for 6 milliseconds. Note that the order of the - // rows and columns is inverted here, and that the row and column are - // printed as two digits. Thus its cup capability is - // “cup=6\E&%p2%2dc%p1%2dY”. - - // The Microterm ACT-IV needs the current row and column sent - // preceded by a ^T, with the row and column simply encoded in - // binary, “cup=^T%p1%c%p2%c”. Terminals which use “%c” need to be able - // to backspace the cursor (cub1), and to move the cursor up one line - // on the screen (cuu1). This is necessary because it is not always safe - // to transmit \n ^D and \r, as the system may change or discard them. - // (The library routines dealing with terminfo set tty modes so that tabs - // are never expanded, so \t is safe to send. This turns out to be - // essential for the Ann Arbor 4080.) - - // A final example is the LSI ADM-3a, which uses row and column offset - // by a blank character, thus “cup=\E=%p1%' '%+%c%p2%' '%+%c”. After - // sending `\E=', this pushes the first parameter, pushes the ASCII value - // for a space (32), adds them (pushing the sum on the stack in place of - // the two previous values) and outputs that value as a character. - // Then the same is done for the second parameter. More complex - // arithmetic is possible using the stack. - - code += val[0]; - val = val.substring(1); - } - - code += '"); return out.join("");'; - break; + default: + return function() {}; } - console.log(val); + // e.g. + // set_attributes: '%?%p9%t\u001b(0%e\u001b(B%;\u001b[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m', + // cursor_address: '\u001b[%i%p1%d;%p2%dH', + // column_address: '\u001b[%i%p1%dG', + // change_scroll_region: '\u001b[%i%p1%d;%p2%dr', + // CSI Ps ; Ps r + // CSI ? Pm r - return val; + var code = 'var dyn = {}, stat = {}, stack = [], out = []; out.push("'; + + // man terminfo, around line 940 + + while (val) { + // '\e' -> ^[ + if (cap = /^\\e/gi.exec(val)) { + val = val.substring(cap[0].length); + code += '\x1b'; + continue; + } + + // '^A' -> ^A + if (cap = /^\^(.)/gi.exec(val)) { // case-insensitive? + val = val.substring(cap[0].length); + ch = cap[1]; + switch (ch) { + case '@': + code += '\x00'; + break; + case 'A': + code += '\x01'; + break; + case 'B': + code += '\x02'; + break; + case 'C': + code += '\x03'; + break; + case 'D': + code += '\x04'; + break; + case 'E': + code += '\x05'; + break; + case 'F': + code += '\x06'; + break; + case 'G': + code += '\x07'; + break; + case 'H': + code += '\x08'; + break; + case 'I': + code += '\x09'; // \t + break; + case 'J': + code += '\x0a'; // \n + break; + case 'K': + code += '\x0b'; + break; + case 'L': + code += '\x0c'; + break; + case 'M': + code += '\x0d'; + break; + case 'N': + code += '\x0e'; + break; + case 'O': + code += '\x0f'; + break; + case 'P': + code += '\x10'; + break; + case 'Q': + code += '\x11'; + break; + case 'R': + code += '\x12'; + break; + case 'S': + code += '\x13'; + break; + case 'T': + code += '\x14'; + break; + case 'U': + code += '\x15'; + break; + case 'V': + code += '\x16'; + break; + case 'W': + code += '\x17'; + break; + case 'X': + code += '\x18'; + break; + case 'Y': + code += '\x19'; + break; + case 'Z': + code += '\x1a'; + break; + case '\\': + code += '\x1c'; + break; + case '^': + code += '\x1e'; + break; + case '_': + code += '\x1f'; + break; + case '[': + code += '\x1b'; + break; + case ']': + code += '\x1d'; + break; + case '?': + code += '\x7f'; + break; + } + continue; + } + + // '\n' -> \n + // '\r' -> \r + // '\0' -> \200 (special case) + if (cap = /^\\([nlrtbfs\^\\,:0])/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + switch (ch) { + case 'n': + return '\n'; + case 'l': + return '\l'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'b': + return '\b'; + case 'f': + return '\f'; + case 's': + return '\s'; + case '\\': + return '\\'; + case ',': + return ','; + case ';': + return ';'; + case '0': + //return '\0'; + return '\200'; + } + continue; + } + + // 3 octal digits -> character + if (cap = /^\\(\d\d\d)/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += String.fromCharCode(parseInt(ch, 8)); + continue; + } + + // $<5> -> padding + if (cap = /^\$<(\d+)>(\*|\/)/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += Array(+ch + 1).join(' '); // "padding" characters? + continue; + } + + // man terminfo, around page 1034 + // %% outputs `%' + if (cap = /^%%/g.exec(val)) { + val = val.substring(cap[0].length); + code += '%'; + continue; + } + + // %[[:]flags][width[.precision]][doxXs] + // as in printf, flags are [-+#] and space. Use a `:' to allow the + // next character to be a `-' flag, avoiding interpreting "%-" as an + // operator. + if (cap = /^%(?:(:)?([\-+# ]+)?)(?:(\d+)(\.\d+)?)?([doxXs])?/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'TODO'; + continue; + } + + // %c print pop() like %c in printf + if (cap = /^%c/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'stack.pop()'; // TODO: FORMAT + continue; + } + + // %d print pop() like %d in printf + // NOT SURE ABOUT %d being print! + if (cap = /^%d/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'stack.pop()'; // TODO: FORMAT + continue; + } + + // %s print pop() like %s in printf + if (cap = /^%s/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'stack.pop()'; // TODO: FORMAT + continue; + } + + // %p[1-9] + // push i'th parameter + if (cap = /^%p([1-9])/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'params[i]'; + continue; + } + + // %P[a-z] + // set dynamic variable [a-z] to pop() + if (cap = /^%P([a-z])/g.exec(val)) { + val = val.substring(cap[0].length); + v = cap[1]; + code += 'dyn.' + v + ' = stack.pop()'; + continue; + } + + // %g[a-z] + // get dynamic variable [a-z] and push it + if (cap = /^%g([a-z])/g.exec(val)) { + val = val.substring(cap[0].length); + v = cap[1]; + code += '(stack.push(dyn.' + v + '), dyn.' + v + ')'; + continue; + } + + // %P[A-Z] + // set static variable [a-z] to pop() + if (cap = /^%P([A-Z])/g.exec(val)) { + val = val.substring(cap[0].length); + v = cap[1]; + code += 'stat.' + v + ' = stack.pop()'; + continue; + } + + // %g[A-Z] + // get static variable [a-z] and push it + + // The terms "static" and "dynamic" are misleading. Historically, + // these are simply two different sets of variables, whose values are + // not reset between calls to tparm. However, that fact is not + // documented in other implementations. Relying on it will adversely + // impact portability to other implementations. + + if (cap = /^%g([A-Z])/g.exec(val)) { + val = val.substring(cap[0].length); + v = cap[1]; + code += 'stack.push(stat.' + v + ')'; + continue; + } + + // %'c' char constant c + if (cap = /^%'(\w)'/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += '"' + ch + '"'; + continue; + } + + // %{nn} + // integer constant nn + if (cap = /^%\{(\d+)\}/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += '(' + ch + ')'; + continue; + } + + // %l push strlen(pop) + if (cap = /^%l/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'stack.push(stack.pop().length)'; + continue; + } + + // %+ %- %* %/ %m + // arithmetic (%m is mod): push(pop() op pop()) + // %& %| %^ + // bit operations (AND, OR and exclusive-OR): push(pop() op pop()) + // %= %> %< + // logical operations: push(pop() op pop()) + if (cap = /^%([+\-*\/m&|\^=><])/g.exec(val)) { + val = val.substring(cap[0].length); + op = cap[1]; + if (op === '=') op = '==='; + else if (op === 'm') op = '%'; + code += 'stack.push(stack.pop() ' + op + ' stack.pop())'; + continue; + } + + // %A, %O + // logical AND and OR operations (for conditionals) + if (cap = /^%([AO])/g.exec(val)) { + val = val.substring(cap[0].length); + op = cap[1]; + code += op === ' A ' ? ' && ' : ' || '; + continue; + } + + // %! %~ + // unary operations (logical and bit complement): push(op pop()) + if (cap = /^%([!~])/g.exec(val)) { + val = val.substring(cap[0].length); + op = cap[1]; + code += 'stack.push(' + op + 'stack.pop())'; + continue; + } + + // %i add 1 to first two parameters (for ANSI terminals) + if (cap = /^%i/g.exec(val)) { + val = val.substring(cap[0].length); + code += '(params[0]++, params[1]++)'; + continue; + } + + // %? expr %t thenpart %e elsepart %; + // This forms an if-then-else. The %e elsepart is optional. Usually + // the %? expr part pushes a value onto the stack, and %t pops it from + // the stack, testing if it is nonzero (true). If it is zero (false), + // control passes to the %e (else) part. + + // It is possible to form else-if's a la Algol 68: + // %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e %; + + // where ci are conditions, bi are bodies. + + // Use the -f option of tic or infocmp to see the structure of + // if-then-else's. Some strings, e.g., sgr can be very complicated when + // written on one line. The -f option splits the string into lines with + // the parts indented. + if (cap = /^%\?/g.exec(val)) { + val = val.substring(cap[0].length); + code += '"); if ('; + continue; + } + + if (cap = /^%t/g.exec(val)) { + val = val.substring(cap[0].length); + code += ') { out.push("'; + continue; + } + + if (cap = /^%e/g.exec(val)) { + val = val.substring(cap[0].length); + code += '"); } else { out.push("'; + continue; + } + + if (cap = /^%;/g.exec(val)) { + val = val.substring(cap[0].length); + code += '"); } out.push("'; + continue; + } + + // Binary operations are in postfix form with the operands in the usual + // order. That is, to get x-5 one would use "%gx%{5}%-". %P and %g vari‐ + // ables are persistent across escape-string evaluations. + + // Consider the HP2645, which, to get to row 3 and column 12, needs to be + // sent \E&a12c03Y padded for 6 milliseconds. Note that the order of the + // rows and columns is inverted here, and that the row and column are + // printed as two digits. Thus its cup capability is + // “cup=6\E&%p2%2dc%p1%2dY”. + + // The Microterm ACT-IV needs the current row and column sent + // preceded by a ^T, with the row and column simply encoded in + // binary, “cup=^T%p1%c%p2%c”. Terminals which use “%c” need to be able + // to backspace the cursor (cub1), and to move the cursor up one line + // on the screen (cuu1). This is necessary because it is not always safe + // to transmit \n ^D and \r, as the system may change or discard them. + // (The library routines dealing with terminfo set tty modes so that tabs + // are never expanded, so \t is safe to send. This turns out to be + // essential for the Ann Arbor 4080.) + + // A final example is the LSI ADM-3a, which uses row and column offset + // by a blank character, thus “cup=\E=%p1%' '%+%c%p2%' '%+%c”. After + // sending `\E=', this pushes the first parameter, pushes the ASCII value + // for a space (32), adds them (pushing the sum on the stack in place of + // the two previous values) and outputs that value as a character. + // Then the same is done for the second parameter. More complex + // arithmetic is possible using the stack. + + code += val[0]; + val = val.substring(1); + } + + code += '"); return out.join("");'; + + return new Function('params', code); }; // Return alias if one exists.