diff --git a/lib/tput.js b/lib/tput.js index bcee4cf..43b7a77 100644 --- a/lib/tput.js +++ b/lib/tput.js @@ -288,348 +288,393 @@ Tput.prototype.invoke = function(key, prefix, params, suffix) { // CSI Ps ; Ps r // CSI ? Pm r - var code = 'var dyn = {}, stat = {}, stack = []; out.push("'; + var code = 'var dyn = {}, stat = {}, stack = [], out = []; out.push("'; // man terminfo, around line 940 - // '\e' -> ^[ - val = val.replace(/\\e/gi, '\x1b'); - - // '^A' -> ^A - val = val.replace(/\^(.)/gi, function(_, ch) { // case-insensitive? - switch (ch) { - case '@': - return '\x00'; - case 'A': - return '\x01'; - case 'B': - return '\x02'; - case 'C': - return '\x03'; - case 'D': - return '\x04'; - case 'E': - return '\x05'; - case 'F': - return '\x06'; - case 'G': - return '\x07'; - case 'H': - return '\x08'; - case 'I': - return '\x09'; // \t - case 'J': - return '\x0a'; // \n - case 'K': - return '\x0b'; - case 'L': - return '\x0c'; - case 'M': - return '\x0d'; - case 'N': - return '\x0e'; - case 'O': - return '\x0f'; - case 'P': - return '\x10'; - case 'Q': - return '\x11'; - case 'R': - return '\x12'; - case 'S': - return '\x13'; - case 'T': - return '\x14'; - case 'U': - return '\x15'; - case 'V': - return '\x16'; - case 'W': - return '\x17'; - case 'X': - return '\x18'; - case 'Y': - return '\x19'; - case 'Z': - return '\x1a'; - case '\\': - return '\x1c'; - case '^': - return '\x1e'; - case '_': - return '\x1f'; - case '[': - return '\x1b'; - case ']': - return '\x1d'; - case '?': - return '\x7f'; + while (val) { + // '\e' -> ^[ + if (cap = /\\e/gi.exec(val)) { + val = val.substring(cap[0].length); + code += '\x1b'; + continue; } - }); - // '\n' -> \n - // '\r' -> \r - // '\0' -> \200 (special case) - val = val.replace(/\\([nlrtbfs\^\\,:0])/g, function(_, ch) { - 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'; + // '^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; } - }); - // 3 octal digits -> character - val = val.replace(/\\(\d\d\d)/g, function(_, ch) { - return String.fromCharCode(parseInt(ch, 8)); - }); + // '\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; + } - // $<5> -> padding - val = val.replace(/\$<(\d+)>(\*|\/)/g, function(_, ch, opt) { - // code += ''; - // TODO - return ''; - return Array(+ch + 1).join(' '); // "padding" characters? - }); + // 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; + } - // man terminfo, around page 1034 - // %% outputs `%' - val = val.replace(/%%/g, '%'); + // $<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; + } - // %[[:]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. - val = val.replace(/%(?:(:)?([\-+# ]+)?)(?:(\d+)(\.\d+)?)?([doxXs])?/g, function() { - // TODO - return ''; - }); + // man terminfo, around page 1034 + // %% outputs `%' + if (cap = /%%/g.exec(val)) { + val = val.substring(cap[0].length); + code += '%'; + continue; + } - // %c print pop() like %c in printf - val = val.replace(/%c/g, function() { - // code += 'out += stack.pop()'; // TODO: FORMAT - // TODO - return ''; - }); + // %[[:]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; + } - // %s print pop() like %s in printf - val = val.replace(/%s/g, function() { - // code += 'out += stack.pop()'; // TODO: FORMAT - // TODO - return ''; - }); + // %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; + } - // %p[1-9] - // push i'th parameter - val = val.replace(/%p([1-9])/g, function(_, i) { - // code += 'params[i]'; - return params[i] || ''; - }); + // %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[a-z] - // set dynamic variable [a-z] to pop() - val = val.replace(/%P([a-z])/g, function(_, v) { - // code += 'dyn[' + v + '] = stack.pop()'; - // TODO - return ''; - }); + // %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; + } - // %g[a-z] - // get dynamic variable [a-z] and push it - val = val.replace(/%g([a-z])/g, function(_, v) { - // code += '(stack.push(dyn[' + v + ']), data[' + v + '])'; - // TODO - return ''; - }); + // %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; + } - // %P[A-Z] - // set static variable [a-z] to pop() - val = val.replace(/%P([A-Z])/g, function(_, v) { - // code += 'stat[' + v + '] = stack.pop()'; - // TODO - return ''; - }); + // %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; + } - // %g[A-Z] - // get static variable [a-z] and push it + // %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; + } - // 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. + // %g[A-Z] + // get static variable [a-z] and push it - val = val.replace(/%g([A-Z])/g, function(_, v) { - // TODO - return ''; - }); + // 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. - // %'c' char constant c - val = val.replace(/%'(\w)'/g, function(_, ch) { - // code += '"' + ch + '"'; - // TODO - return ''; - }); + if (cap = /%g([A-Z])/g.exec(val)) { + val = val.substring(cap[0].length); + v = cap[1]; + code += 'stack.push(stat[' + v + '])'; + continue; + } - // %{nn} - // integer constant nn - val = val.replace(/%\{(\d+)\}/g, function(_, nn) { - // code += '(' + ch + ')'; - // TODO - return ''; - }); + // %'c' char constant c + if (cap = /%'(\w)'/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += '"' + ch + '"'; + continue; + } - // %l push strlen(pop) - val = val.replace(/%l/g, function() { - // code += 'stack.push(stack.pop().length)'; - // TODO - return ''; - }); + // %{nn} + // integer constant nn + if (cap = /%\{(\d+)\}/g.exec(val)) { + val = val.substring(cap[0].length); + ch = cap[1]; + code += '(' + ch + ')'; + continue; + } - // %+ %- %* %/ %m - // arithmetic (%m is mod): push(pop() op pop()) - val = val.replace(/%([+\-*\/m])/g, function(_, op) { - // code += 'stack.push(stack.pop() ' + op + ' stack.pop())'; - // TODO - return ''; - }); + // %l push strlen(pop) + if (cap = /%l/g.exec(val)) { + val = val.substring(cap[0].length); + code += 'stack.push(stack.pop().length)'; + continue; + } - // %& %| %^ - // bit operations (AND, OR and exclusive-OR): push(pop() op pop()) - val = val.replace(/%([&|\^])/g, function(_, op) { - // code += 'stack.push(stack.pop() ' + op + ' stack.pop())'; - // TODO - return ''; - }); + // %+ %- %* %/ %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; + } - // %= %> %< - // logical operations: push(pop() op pop()) - val = val.replace(/%([=><])/g, function(_, op) { - // code += 'stack.push(stack.pop() ' + op + ' stack.pop())'; - // TODO - return ''; - }); + // %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; + } - // %A, %O - // logical AND and OR operations (for conditionals) - val = val.replace(/%([AO])/g, function(_, v) { - // code += v === ' A ' ? ' && ' : ' || '; - // TODO - return ''; - }); + // %! %~ + // 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; + } - // %! %~ - // unary operations (logical and bit complement): push(op pop()) - val = val.replace(/%([!~])/g, function(_, op) { - // code += 'stack.push(' + op + 'stack.pop())'; - // TODO - return ''; - }); + // %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; + } - // %i add 1 to first two parameters (for ANSI terminals) - val = val.replace(/%i/g, function(_, v) { - // code += '(params[0]++, params[1]++)'; - // TODO - return ''; - }); + // %? 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. - // %? 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 %; - // 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. - // 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; + } - // 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. - //val = val.replace(/%\?(.+?)%t(.+?)%e(.+?)%;/g, function(_, expr, thenpart, elsepart) { - // // TODO: Generate code: - // // code += ';if (' + parse(expr) + ') {' + out(thenpart) + '} else {' + out(elsepart) + '}'; - // // TODO - // return ''; - //}); + if (cap = /%t/g.exec(val)) { + val = val.substring(cap[0].length); + code += ') {'; + continue; + } - val = val.replace(/%\?/g, function(_, expr, thenpart, elsepart) { - // code += ';if ('; - // TODO - return ''; - }); + if (cap = /%e/g.exec(val)) { + val = val.substring(cap[0].length); + code += '} else {'; + continue; + } - val = val.replace(/%t/g, function(_, expr, thenpart, elsepart) { - // code += ') {'; - // TODO - return ''; - }); + if (cap = /%;/g.exec(val)) { + val = val.substring(cap[0].length); + code += '}'; + continue; + } - val = val.replace(/%e/g, function(_, expr, thenpart, elsepart) { - // code += '} else {'; - // TODO - return ''; - }); + // 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. - val = val.replace(/%;/g, function(_, expr, thenpart, elsepart) { - // code += '}'; - // TODO - return ''; - }); + // 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”. - // 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. + // 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.) - // 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. - - //val = val.replace(/%p(\d+)?/g, function(_, n) { - // return params[i++] || ''; - //}); + // 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); + } break; }