如何在sweet.js中创建参数化的中缀宏

时间:2014-10-24 20:17:07

标签: javascript macros infix-notation sweet.js

bluebird wiki article about JavaScript optimization killers中,作者提到将arguments关键字传递给任何函数(apply除外)都会导致父函数无法优化。我想创建一个sweet.js宏,它允许我编写标准的惯用JavaScript,但会处理优化杀手。

理想情况下,我想要一个可以采用以下功能的宏:

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

并输出如下内容:

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

然而,我无法正确获取sweet.js宏语法。 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;
}

其中输出以下内容:

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

我尝试使我的宏在arguments关键字周围加上括号,并且还包含var声明,但没有任何成功。我试过这样的事情:

无效的宏

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])
        }
    }
}

这会产生以下错误:

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

2 个答案:

答案 0 :(得分:2)

是的,所以有几种方法可以做到这一点。提出论点 在parens内部不起作用,因为中缀宏无法在外部匹配 封闭分隔符,以便在arguments宏被调用时 在它之前或之后看到零令牌(错误应该更清楚)。

你的另一个解决方案正在遇到卫生问题 arguments宏需要访问args标识符,但需要中缀 当它在a中时,不允许宏在等号之前匹配 var语句因此无法与args标识符实际匹配。

所以有几个解决方案。最简单的就是做一些类似的事情 蓝鸟维基建议:

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;
}

你也可以走不卫生的路线(不是真的推荐但是 arguments已经有点不卫生所以......):

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;
}

修改

我刚想到了另一种解决方案,可以让您通过覆盖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);
}

答案 1 :(得分:2)

这不是完全相同的结果,因为它有一个函数包装器(尽管它是用apply调用的),但它不需要你覆盖var并且可以在任何表达立场。

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;
}

扩展为

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;
}

不知道是否会破坏优化......