kzh
kzh

Reputation: 20598

How to create a parameterized infix macro in sweet.js

In the bluebird wiki article about JavaScript optimization killers, the author mentions that passing the arguments keyword to any function (except apply) will cause the parent function to not be optimizable. I would like to create a sweet.js macro that allows me to write standard idiomatic JavaScript but will take care of the optimization killer.

Ideally, I would like a macro that would take the following function:

function foo() {
    var args = [].slice.call(arguments);
    return args;
}

And output something like this:

function foo() {
    var args = [];
    for(var i, len = arguments.length; i < len; i++) {
        args.push(arguments[i]);
    }
    return args;
}

I am having trouble with getting the sweet.js macro syntax correct, however. This is what I have so far:

example.sjs

let arguments = macro {
    rule infix {
         [].slice.call | 
    } => {
        [];
        for(var i = 0, len = arguments.length; i < len; i++) {
            args.push(arguments[i])
        }
    }
}

function toArray() {
    var args = [].slice.call  arguments
    return args;
}

Which outputs the following:

function toArray() {
    var args$2 = [];
    for (var i = 0, len = arguments.length; i < len; i++) {
        args.push(arguments[i]);
    }
    return args$2;
}

I tried making my macro have parenthesis around the arguments keyword and also include the var declaration, but without any success. I tried something like this:

invalid macro

let arguments = macro {
    rule infix {
        var $var = [].slice.call ( | ) 
    } => {
        var $var = [];
        for(var i = 0, len = arguments.length; i < len; i++) {
            args.push(arguments[i])
        }
    }
}

This produces the following error:

SyntaxError: [syntaxCase] Infix macros require a `|` separator
414: 
                                ^

Upvotes: 2

Views: 386

Answers (2)

Nathan Faubion
Nathan Faubion

Reputation: 161

This isn't quite the same result, since it has a function wrapper (though it's invoked with apply), but it doesn't require you to override var and can be used in any expression position.

macro copy_args {
  rule {} => {
    function() {
      var len = arguments.length;
      var args = Array(len);
      for (var i = 0; i < len; i++) {
        args[i] = arguments[i];
      }
      return args;
    }.apply(this, arguments)
  }
}

let slice = macro {
  rule infix { []. | .call(arguments) } => { copy_args } 
  rule infix { []. | .apply(arguments) } => { copy_args } 
  rule infix { Array.prototype. | .call(arguments) } => { copy_args }
  rule infix { Array.prototype. | .apply(arguments) } => { copy_args }
  rule { } => { slice }
}

function go() {
  var args = Array.prototype.slice.call(arguments);
  return args;
}

expands to

function go() {
    var args = function () {
            var len = arguments.length;
            var args$2 = Array(len);
            for (var i = 0; i < len; i++) {
                args$2[i] = arguments[i];
            }
            return args$2;
        }.apply(this, arguments);
    return args;
}

Don't know if that would kill optimization though...

Upvotes: 2

timdisney
timdisney

Reputation: 5337

Right, so there are a couple of ways to do this. Putting arguments inside of parens doesn't work because infix macros can't match outside of enclosing delimiters so when the arguments macro gets invoked it sees zero tokens before or after it (the error should be clearer).

Your other solution is running into hygiene problems since the arguments macro needs access to the args identifier but infix macros are not allowed to match before the equals sign when it's in a var statement so it can't actually match the args identifier.

So couple of solutions. The easiest is to just do something like what the bluebird wiki suggested:

macro argify {
    rule { ( $arg ) } => {
    var $arg;
    for (var i, len = arguments.length; i < len; i++) {
        args.push(arguments[i]);
    }
    }
}

function foo() {
    argify(args)
    return args;
}

You could also go the unhygienic route (not really recommended but arguments is already kinda unhygienic so…):

let function = macro {
    case {$mname $name ( $parens ...) { $body ... } } => {
    letstx $args = [makeIdent("args", #{$mname})];
    return #{
        function $name ( $parens ...) {
        var $args = [];
        for (var i, len = arguments.length; i < len; i++) {
            $args.push(arguments[i]);
        }
        $body ...
        }
    }
    }
}

function foo() {
    return args;
}

Edit:

I just thought of another solution that would allow you to keep your current syntax by overriding var:

let var = macro {
    rule { $args = [].slice.call(arguments) } => {
        var $args = [];
        for(var i, len = arguments.length; i < len; i++) {
            $args.push(arguments[i]);
        }
    }
    rule { $rest ... } => { var $rest ... }
}

function foo() {
    var args = [].slice.call(arguments);
}

Upvotes: 2

Related Questions