Image

Imageruakh wrote in Imagejavascript

JavaScript support for s///e.

I wrote the below (cut) function for those who mourn JavaScript's lack of an equivalent to Perl's s///e.

function re_e(str, num_e)
// Usage:
// - Perl's $foo =~ s/bar/baz/e; is
//   JavaScript's foo = foo.replace(/bar/, eval(re_e('baz')));
// - The num_e argument specifies the number of 'e' flags (default 1). Note:
//   foo = foo.replace(/bar/, eval(re_e('baz'), 0)); is equivalent to
//   foo = foo.replace(/bar/, 'baz');, except that it's less efficient.
// Rationale:
// - In Perl, $1 and whatnot are interpolated into the right-hand-side
//   *before* evaluation; in JavaScript, they're interpolated *after*.
//   JavaScript offers an alternative system, where the right-hand-side is a
//   a function instead of a string, but it is not always perfectly intuitive.
//   This function maps Perl's /e approach to JavaScript's function approach.
// Note that while the eval takes place in the caller's scope (thus allowing
// local variables to be used, as in:
//   var foo = 6; bar = bar.replace(/foo/g, eval(re_e('foo')));
// which takes bar replaces all occurrences of "foo" with "6"), there is
// internally another function going on, which means that function-local
// variables (arguments and this and whatnot) are not accessible inside the
// string: the internal other function's function-local variables are used
// instead.
{
 if(typeof(num_e) == 'undefined') num_e = 1;
 var evalstr = ''; for(var i = 0; i < num_e; ++i) evalstr += 'eval(';
 var rpstr = ''; for(var i = 0; i < num_e; ++i) rpstr += ')';
 return 'function () { return ' + evalstr + str.replace
 (
  /\$(?:\$|`|&|'|(\d)(\d?))|\$|[^$]+/g,
  function(str, p1, p2, offset, s)
  {
   if(str == '$' || str == '$$') return '"$" + ';
   if(str == '$`')
   {
    return 'arguments[arguments.length-1].substring(' +
           '0,arguments[arguments.length-2]) + ';
   }
   if(str == '$&') return 'arguments[0] + ';
   if(str == "$'")
   {
    return 'arguments[arguments.length-1].substring(' +
           'arguments[arguments.length-2]+arguments[0].length) + ';
   }
   if(str.match(/\$/)) // the numeric one
   {
    if(str == '$0' || str == '$00') return '"' + str + '" + ';
    if(p1 == '0' || p2 == '')
    {
     return '((arguments.length >= ' + p1 + p2 + ' + 3)?(arguments[' +
            p1 + p2 + ']):("' + str + '")) + ';
    }
    return '((arguments.length >= ' + p1 + p2 + ' + 3)?(arguments[' +
           p1 + p2 + ']):((arguments.length >= ' + p1 + ' + 3)?(arguments[' +
           p1 + '] + "' + p2 + '"):("' + str + '"))) + ';
   }
   return '"' + str.replace(/"/g, '\\"') + '" + ';
  }
 ) + '""' + rpstr + '; }';
};

So, I have a few questions:

  • Was this a good idea? Am I inappropriately trying to mold JavaScript in Perl's image? (I'm rather new to the Tao of JavaScript, as it were.)
  • Is this the right way to do it? I originally created a replaceEval function that operated like String.replace except for applying eval() a specified number of times (default 1), but I ended up scrapping that because the eval()s would get invoked in the scope of replaceEval's definition rather than in the scope of its invocation, preventing replacement-strings from using local variables (including local functions). I'm not sure if this is really the Right Way, though.
  • How inefficient is this? Obviously it involves applying a bunch of eval()s and String.replace()s, but I don't know exactly how intensive those are. Is this something to be concerned about? Is there a way to improve it?
  • Any other comments?

(BTW, if the above function looks useful to you, feel free to use it.)