from http://jlongster.com/Sweet.js-Tutorial--2--Recursive-Macros-and-Custom-Pattern-Classesphp
在第一篇教程 http://jlongster.com/Writing-Your-First-Sweet.js-Macro 翻译 http://segmentfault.com/blog/tcdona/1190000002490290 。咱们讲到基本的一些 sweet.js 概念。如今咱们来看一些技术,来建立更复杂的宏:递归和自定义模式类。git
全部的这些教程在 repo sweet.js-tutorials https://github.com/jlongster/sweet.js-tutorials 包含了能够编译的 sweet.js 宏工做环境。es6
让咱们建立一个 es6 非结构化变量 http://wiki.ecmascript.org/doku.php?id=harmony:destructuring 赋值特性宏。开始应该这样:github
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 非结构化变量赋值,处理了不少复杂的状况。segmentfault
你能够用学到的全部概念,把上面的宏写出来,可是当你尝试支持更缤纷的语法时,你可能被卡住。如今咱们来看看一些技术来处理更复杂的状况。数组
我在前一篇教程小提过递归宏,它们指的更深刻的研究,以用来解决实际问题。dom
宏的输出老是被 sweet.js 再次展开,因此写递归宏和写递归函数是同样天然的。只要一个规则 rule 再次调用宏,而后其余的规则匹配了这个暂停的状况,而且中止本次展开。ecmascript
一个普通的递归宏用例是处理一组不统一的语法。一般用重复格式 $item (,) ...匹配一组语法,用组 $($item = $arr[$i]) ... 甚至能够匹配复杂的。问题是每一个组中的元素必须是一样的结构。你不能匹配语法类型不一样的组。 sweet.js 没有相似正则重的或 | 操做符,或者可选择的 ? 符号。编辑器
例如,咱们想匹配一组名字,可能含有初始化赋值语句: x, y, z=5, w=6。 咱们想迭代 items 并造成不一样的代码若是初始化赋值语句存在的话。来看用递归宏怎么作:ide
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; ;
当使用了递归宏,你须要考虑到边缘的状况,好比后面的逗号。由于咱们匹配的是逗号分割的组,咱们须要剥开逗号,可是咱们不能一直有逗号,由于最后的元素没有。咱们解决这个问题依靠在组的开头加一个逗号,而后当迭代器穿过这个组的时候剥开逗号。由于初始的调用不会有逗号在前面,因此会匹配到最后一个规则,添加逗号而且递归调用。
当没到达最后的元素了,仅剩下 ; 了,所以它匹配了规则 3,只是输出 ; 而后中止迭代;
如今是提醒你,你能够在在线编辑器中使用 "step" 调试宏展开的好时候。当调试递归宏的时候 step 很是好用。你能看到他是如何一片一片展开的。
如今你看到了展开是如何在咱们控制下展开的。咱们来看看更复杂的栗子。咱们试着加一个特性给咱们的原生非结构化宏:指定默认的值的能力。你能够用一个逗号分开的变量名的数组表格,加上一个可选的 = 来定制元素的默认初始值,若是元素不存在的话。 var [foo, bar=5] = ... 和 var [foo, bar=5, baz] = ... 都是合法的。
先来回一下,咱们前面教程举的 let 宏例子, let var = macro { ... }。记得吗?他告诉 sweet.js 任何咱们本身展开造成的 var 不该该递归的被展开
咱们须要建立一个能被递归的辅助宏,由于咱们不能让 var 递归。看咱们如何来实现非结构化赋值,带可选的初始化表格:
macro destruct_array { rule { $obj $i [] } => {} rule { $obj $i [ $var:ident = $init:expr, $pattern ... ] } => { var $var = $obj[$i++] || $init; destruct_array $obj $i [ $pattern ... ] } rule { $obj $i [ $var:ident, $pattern ... ] } => { var $var = $obj[$i++]; destruct_array $obj $i [ $pattern ... ] } } let var = macro { rule { [ $pattern ...] = $obj:expr } => { var arr = $obj; var i = 0; destruct_array arr i [ $pattern ... , ] } rule { $id } => { var $id } } var [x, y] = arr; var [x, y, z=10] = arr;
var arr$2 = arr; var i = 0; var x = arr$2[i++]; var y = arr$2[i++]; ; var arr$3 = arr; var i$2 = 0; var x = arr$3[i$2++]; var y = arr$3[i$2++]; var z = arr$3[i$2++] || 10; ;
var 宏返回了一个语法,它包含非结构化的数组 destruct_array 宏,sweet.js 递归的展开它。这有点难懂,但不是太坏,咱们走读一下:
这里是 var [x, y=5] = expr 的展开:
var [x, y=5] = expr; var arr = expr; var i = 0; destruct_array arr i [ x , y = 5 , ]; var arr = expr; var i = 0; var x = arr [ i ++ ]; destruct_array arr i [ y = 5 , ]; var arr = expr; var i = 0; var x = arr [ i ++ ]; var y = arr [ i ++ ] || 5; destruct_array arr i [ ]; var arr = expr; var i = 0; var x = arr [ i ++ ]; var y = arr [ i ++ ] || 5;
值得注意的是 js 中有几个地方你不能调用宏。若是用递归宏你得注意这点。例如你不能在 var 绑定或者函数变量名字中调用宏。
var invoke_macro() { do_something_weird } 不工做, function foo (invoke_macro{}) {} 也不会工做。
意思是说你不能这样:
macro randomized { rule { RANDOM $var } => { $var = Math.random() } rule { $var (,) ...; } => { var $(randomized RANDOM $var) (,) ... } } randomized x, y, z; Error: Line 11: Unexpected identifier [... var randomized RANDOM x , ...]
去掉 var 才行的通。你想要在规则内部有本地展开语法的能力,可是 sweet.js 还不支持这点。
理论上咱们的宏应扩展成单独一个 var 语句,好比 var arr = expr, i=0, x = arr[i++] 代替多个 var 声明。咱们现有的宏由于在 for while 语句里面( for(var [x, y] = arr; x<10; x++){} )由于多行语句在这里是无效的。不幸的是,咱们须要递归的在 var 中调用一个宏,并绑定一个位置,但如上面的规则咱们不能这么作。宏会展开的相似 var destruct_array arr i [$pattern ..., END] 可是你不能这么作。
咱们继续解构宏,并加上混合解构支持。你应该能用 var [x, [y, z]] = arr 可是咱们的宏不能处理这个。用递归宏,咱们能够很是简单的添加这点。咱们须要的只是让 destruct_array 能接受任何类型的口令( $var:id 被改为了 $first )而且转换宏的顺序
let var = macro { rule { [ $pattern ...] = $obj:expr } => { var arr = $obj; var i = 0; destruct_array arr i [ $pattern ... , END ] } rule { $id } => { var $id } } macro destruct_array { rule { $obj $i [ END ] } => { } rule { $obj $i [ $var:ident = $init:expr, $pattern ... ] } => { var $var = $obj[$i++] || $init; destruct_array $obj $i [ $pattern ... ] } rule { $obj $i [ $first, $pattern ... ] } => { var $first = $obj[$i++]; destruct_array $obj $i [ $pattern ... ] } } var [x, y] = arr; var [x, [y=5, z]] = arr;
var arr$2 = arr; var i = 0; var x = arr$2[i++]; var y = arr$2[i++]; ; var arr$3 = arr; var i$2 = 0; var x = arr$3[i$2++]; var arr$4 = arr$3[i$2++]; var i$3 = 0; var y = arr$4[i$3++] || 5; var z = arr$4[i$3++]; ; ;
咱们改变了 var 和 destruct_array 的顺序,由于咱们用 var 在 destruct_array 中来建立新的标识符而且用右边的元素初始化他们。若是“元素”是另外一个模式,好比 [y, z] 咱们想要结构它。咱们难道不能用 var 宏来递归的解构他吗?是的咱们能够!如今, let 宏只是在他定义以后生效,所以若是咱们定义 destruct_array 在后面他会递归的展开进入它。
递归宏给了咱们更多控制扩展的能力。咱们留下一个解构给你们( var {x, y: foo} = obj )当递归性用熟了,咱们看看另一种匹配复杂的模式的姿式,它直觉上更容易用。
第一篇教程我提到模式类告诉扩展器匹配什么类型的口令。 indet, lit 和 expr 是 sweet.js 内建的。实际上你能够自定义模式类来抽象任何复杂的模式匹配。
常见的问题是须要思考如何抽象,这很必要,尤为是在匹配重复性的模式的时候。递归宏容许你创建辅助宏来创建抽象层。自定义模式类也容许你这样,可是他更(天然/表象/直觉) intuitive。
自定义模式类很是简单:只须要弄个宏!宏能够被做为模式类调用。(弄一个宏 foo, 用 rule { $x:foo } => {})。这里你也有2个表格 forms 能够用: $x:invoke(foo) 和 $x:invokeOnce(foo) 。invoke 递归的展开 foo 宏的结果, invokeOnce 只展开一次。 $x:foo 是 $:invoke(foo) 的简写。
来看咱们以前作的递归 define 宏,可是用了模式类代替:
macro item { rule { $item = $init:expr } => { var $item = $init } rule { $item } => { var $item } } macro define { rule { $items:item (,) ... ; } => { $items (;) ... } } define x, y, z=5, w=6;
var x; var y; var z = 5; var w = 6;
一个模式类在口令流上运行,而后替换成宏展开后的样子。 item 宏返回 var 定义,咱们只须要在 define 中输出 $items。模式类在许多状况下比递归模式简单。由于你不须要记帐是的处理尾部的逗号什么的。
若是 item 返回了一个宏做为第一个口令,他会不断的展开。任何宏中的其余代码都没机会被展开了。$items:invoke(item) 或者说 $items:item 的递归性只集中在 “头部” 。若是你不想这样,用 $items:invokeOnce(item) 来从最初的匹配中回来。
咱们的解构宏看起来会变成怎样,若是咱们用模式类代替递归宏?:
let var = macro { rule { [ $pattern:destruct_array (,) ...] = $obj:expr } => { $pattern (,) ... } rule { $id } => { var $id } }
问题是咱们须要给 destruct_array 传递参数。咱们转换组中的元素,来让每一个元素包含参数,而后用一个辅助宏来触发这个模式类
let var = macro { rule { [ $pattern:expr (,) ...] = $obj:expr } => { var arr = $obj; var i = 0; destruct [ $(arr i $pattern) (,) ... ] } rule { $id } => { var $id } } macro destruct { rule { [ $pattern:destruct_array (,) ... ] } => { $pattern (;) ... } }
咱们创建了须要传给解构状态的 arr 和 i 变量,而后创建了一组元素 destruct 能够用 destruct_array 来组合。如今咱们只须要定义 destruct_array。完整的来了:
let var = macro { rule { [ $pattern:expr (,) ...] = $obj:expr } => { var arr = $obj; var i = 0; destruct [ $(arr i $pattern) (,) ... ] } rule { $id = $init:expr } => { var $id = $init } rule { $id } => { var $id } } macro destruct_array { rule { $obj $i $var = $init:expr } => { var $var = $obj[$i++] || $init } rule { $obj $i $var } => { var $var = $obj[$i++] } } macro destruct { rule { [ $pattern:destruct_array (,) ... ] } => { $pattern (;) ... } } var [x, y] = arr; var [x, y, z=10] = arr; var [x, [y, z=10]] = arr;
var arr = arr$4; var i = 0; var x = arr[i++]; var y = arr[i++]; var arr$2 = arr$4; var i$2 = 0; var x = arr$2[i$2++]; var y = arr$2[i$2++]; var z = arr$2[i$2++] || 10; var arr$3 = arr$4; var i$3 = 0; var x = arr$3[i$3++]; var arr$4 = arr$3[i$3++]; var i$4 = 0; var y = arr$4[i$4++]; var z = arr$4[i$4++] || 10;
它支持初始化表格( var [x=5] = arr )而且混合解构。这里如何混合解构真的颇有趣:destruct_array 生成的 var 被咱们的宏引用,所以他能够递归展开。
递归性任然在模式类中有效,但你得当心翼翼的。var 宏返回的东西会注入到 destruct 的匹配中。注意咱们如何在 var 中添加一个规则来匹配 $id = $init:expr 表格。咱们须要这点,这样它才能在递归展开的时候返回整个的表达式给 destruct。
如今你不能单步的调试模式类的展开,可是它张这样:
var [x, y=5] = expr; var arr = expr; var i = 0; destruct [ arr i x , arr i y = 5 ] // pattern class running: `destruct_array arr i x` arr i x var x = arr[i++] // expanded with `var` macro var x = arr[i++] // pattern class running: `destruct_array arr i y = 5` arr i y = 5 var y = arr[i++] || 5 // expanded with `var` macro var y = arr[i++] || 5 // back inside `destruct` var x = arr[i++]; var y = arr[i++] || 5;
如今这个宏能够作咱们递归宏能够作的任何事情了,并且他更清晰、干净。他还让咱们更接近产生一个 var 语句的能力 好比 var arr = expr, i=0, x=arr[i++] 由于模式类让咱们能重复。表格 var $el (,) ... 是盒饭的由于它在返回给解析以前展开了; 你只是不能在 var 绑定之中递归展开。
不幸的,因为咱们须要创建2个新的绑定 arr 和 i ,咱们没法生成单独的 var 声明了。var 宏产生了这些绑定,而后调用 destruct 宏,所以宏调用不会再 var 绑定的内部发生。想要生成一个单独的简单干净的 var 声明的惟一方式是让咱们在宏规则中有生成本地展开语法的能力,可是咱们还没发支持这点。
你能用这2中技术建立不少有趣的宏。将来咱们会涉及像 infix 宏, case 宏,等,保持协调,而且关注个人博客 http://feeds.feedburner.com/jlongster ,为了之后的教程。