from http://jlongster.com/Sweet.js-Tutorial--2--Recursive-Macros-and-Custom-Pattern-Classes
在第一篇教程 http://jlongster.com/Writing-Your-First-Sweet.js-Macro 翻译 http://segmentfault.com/blog/tcdona/1190000002490290 。我们讲到基本的一些 sweet.js 概念。现在我们来看一些技术,来创建更复杂的宏:递归和自定义模式类。
所有的这些教程在 repo sweet.js-tutorials https://github.com/jlongster/sweet.js-tutorials 包含了可以编译的 sweet.js 宏工作环境。
让我们创建一个 es6 非结构化变量 http://wiki.ecmascript.org/doku.php?id=harmony:destructuring 赋值特性宏。开始应该这样:
let var = macro {
rule { [$var (,) ...] = $obj:expr } => {
var i = 0;
var arr = $obj;
$(var $var = arr[i++]) (;) ...
}
rule { $id } => {
var $id
}
}
var [foo, bar, baz] = arr;
var i = 0;
var arr$2 = arr;
var foo = arr$2[i++];
var bar = arr$2[i++];
var baz = arr$2[i++];
这仅仅处理了基础的简单数组。我们把目标对象分配给 arr 确保表达式只执行了一次。 es6 非结构化变量赋值,处理了很多复杂的情况。
- 对象/哈希: var {foo, bar} = obj;
- 默认值: var [foo, bar=5] = arr;
- 重命名: var {foo: myFoo} = obj;
- 混合的解构: var {foo, bar: [x, y]} = obj;
你可以用学到的所有概念,把上面的宏写出来,但是当你尝试支持更缤纷的语法时,你可能被卡住。现在我们来看看一些技术来处理更复杂的情况。
递归宏
我在前一篇教程小提过递归宏,它们指的更深入的研究,以用来解决实际问题。
宏的输出总是被 sweet.js 再次展开,所以写递归宏和写递归函数是一样自然的。只要一个规则 rule 再次调用宏,然后其他的规则匹配了这个暂停的情况,并且停止本次展开。
一个普通的递归宏用例是处理一组不统一的语法。通常用重复格式 $item (,) ...匹配一组语法,用组 $($item = $arr[$i]) ... 甚至可以匹配复杂的。问题是每个组中的元素必须是同样的结构。你不能匹配语法类型不同的组。 sweet.js 没有类似正则重的或 | 操作符,或者可选择的 ? 符号。
例如,我们想匹配一组名字,可能含有初始化赋值语句: x, y, z=5, w=6。 我们想迭代 items 并形成不同的代码如果初始化赋值语句存在的话。来看用递归宏怎么做:
macro define {
rule { , $item = $init:expr $rest ... ; } => {
var $item = $init;
define $rest ... ;
}
rule { , $item $rest ... ; } => {
var $item;
define $rest ... ;
}
rule { ; } => { ; }
rule { $items ... ; } => {
define , $items ... ;
}
}
define x, y, z=5, w=6;
var x;
var y;
var z = 5;
var w = 6;
;
当使用了递归宏,你需要考虑到边缘的情况,比如后面的逗号。因为我们匹配的是逗号分割的组,我们需要剥开逗号,但是我们不能一直有逗号,因为最后的元素没有。我们解决这个问题依靠在组的开头加一个逗号,然后当迭代器穿过这个组的时候剥开逗号。因为初始的调用不会有逗号在前面,所以会匹配到最后一个规则,添加逗号并且递归调用。