2017-07-31
模版引擎
template
模版解释器的语法树
这里说的语法树,其实是指抽象语法树 AST,它描述了一种求值的机器,只要喂参数进去,就可以得到结果了,比如假设如下函数是一台计算机器:
00var add = (a, b) => a + b;
调用 add(1, 2) 可以得到 3 这里的 add 可以看成是一台计算机器。

# S-Expression

S-表达式描述了一种树结构,它是普通表达式的更加一般的形式:
001 + 2 + 3
而 S-表达式 :
00+ 1 2 3
可能S-表达式把运算符提前写在最前面会令人困惑,但事实上这才是最正确的表达式写法,观察:
005 + 2 * 3 + 4
因为 S-表达式的特殊性,不可能会有运算符优先级的概念,所有优先级由括号决定。
00(+ 5 (* 2 3) 4)
括号结构代表运算优先级使得S-表达式具有描述递归层级结构的能力,而且解析起来也比解析普通表达式容易的多。
而且,这种递归结构完全对应一棵树: (运算符是根结点)
00    +
01   /|\
02  5 * 4
03   / \
04  2   3
写成 JSON 就是: (运算符是 o 字段,之后跟随的参数用数字从左到右编码)
00var tree = {
01    o: '+', 
02    a: 5, 
03    b: {
04        o: '*', 
05        a: 2, 
06        b: 3
07    },
08    c: 4
09}
我一开始对这种递归结构没有太深刻的体验,直到自己写了 S-表达式 运算器之后才发现:
S-表达式里面还有S-表达式
任何一对匹配的括号都是 S-表达式

# S-表达式的递归求值

一棵 S-表达式 其实就是一台计算机器,递归的对其求值竟然是如此的简单:
00// 假设一个S表达式的参数个数不超过 6 个
01
02var calc = {
03    "*": (a, b) => a * b, 
04    "+": (a, b) => a + b
05}
06
07var eval = tree => {
08    // 如果 tree 不是 S-表达式, 返回 tree 
09    if (typeof tree !== 'object') return tree; 
10
11    // 运算符 
12    var todo = calc[tree.o]; 
13
14    // 没有 o 字段的参数们的键 
15    var keys = Object.keys(tree).filter(key => key !== 'o'); 
16
17    // 键对应的值 即S表达式的参数 
18    var vals = keys.map(e => tree[e]); 
19
20    // 归约递归
21    return vals.reduce((sum, val) => {
22        return todo(sum, eval(val));
23    });
24}
很自然的可以求出结果:
00// tree 是 (+ 5 (* 2 3) 4) 代表的树 
01var res = eval(tree); 
02
03console.log(res); 
04// => 
05// 15

# 运算符拓展

S-表达式的运算符拓展极为容易:
00var calc = {
01    "*": (a, b) => a * b, 
02    "+": (a, b) => a + b, 
03    "/": (a, b) => a / b
04}
00// (/ 100 2 2)
01var tree = {
02    o: '/', 
03    a: 100, 
04    b: 2, 
05    c: 2
06}
07
08var res = eval(tree); 
09
10console.log(res); 
11// => 
12// 25
S-表达式极其简洁优雅,完美自然,任何的赞美在它面前都显得多余。

# 那模版解释器呢?

大可以把模版解释器当成计算机器,不过这次它不是对数字处理了,而是对字符串。
原理也是一样,都需要构造语法树,这样求值计算的速度才足够快。
而构造语法树请看下篇详细实现了 tplser




回到顶部