概述
1. Egg 语言示例
do(define(x, 10),
if(>(x, 5),
print("large"),
print("small")))
相当于js代码:
x = 10;
if(x>5)
print("large");
else
print("small");
1. Parser
读取一段代码,把它转化成一种数据结构,这个数据结构可以精准的反映代码的程序结构。
Egg中所有的东西都是Expression,可以分成四类:
1. variable
任意字符序列,不包括空格,不能是保留字
2. number
一串数字
3. string
双引号包含的一段text,不支持转义字符
4. application
function,if,while,>, +, -, *, / 等等
如上面第一段代码,有多少左括号,就有多少个application,括号中是参数。do, define, if, >, print 都是application。
操作符是application,书写顺序和js不同。 >(x,5) 相当于 x>5。后面的两个print表达式是if的参数,也是两个分支。
Parser 会把每一个expression转换成一个对象,所有的expression会组成一个对象树。 例如:
> (x,5)
会解析成:
{
type: "apply",
operator: {type: "word", name: ">"},
args: [
{type: "word", name: "x"},
{type: "value", value: 5}
]
}
expression对象有三种type:
1. "value"
字符串或数值, 有一个value属性,包含字符串和数字值。如上面的常量 5 。
2. "word"
变量,有一个name属性,保存标识符的名称。如上面的变量 x 和 > 。
3. "apply"
application,有一个operator属性,保存操作的表达式,有一个args属性,保存参数数组。如上面的 > (x,5) 。注意, > 本身是 "word" 类型,加上后面的括号和参数会组合成 "apply" 类型。
1. 表达式树
这是第一段代码的表达式树。树中的每个圆点都是一个Expression,带有子节点的都是 "apply" 类型。
1. Parser 的实现代码
function skipSpace(string) {
var first = string.search(/S/);
if (first == -1)
return "";
return string.slice(first);
}
function parseExpression(program) {
program = skipSpace(program);
var match, expr;
if (match = /^"([^"]*)"/.exec(program))
expr = {type: "value", value: match[1]};
else if (match = /^d+b/.exec(program))
expr = {type: "value", value: Number(match[0])};
else if (match = /^[^s(),"]+/.exec(program))
expr = {type: "word", name: match[0]};
else
throw new SyntaxError("Unexpected syntax: " + program);
return parseApply(expr, program.slice(match[0].length));
}
function parseApply(expr, program) {
program = skipSpace(program);
if (program[0] != "(")
return {expr: expr, rest: program};
program = skipSpace(program.slice(1));
expr = {type: "apply", operator:expr, args:[]};
while (program[0] != ")") {
var arg = parseExpression(program);
expr.args.push(arg.expr);
program = skipSpace(arg.rest);
if (program[0] == ",")
program = skipSpace(program.slice(1));
else if (program[0] != ")")
throw new SyntaxError("Expected ',' or ')'");
}
return parseApply(expr, program.slice(1));
}
function parse(program) {
var result = parseExpression(program);
if (skipSpace(result.rest).length > 0)
throw new SyntaxError("Unexpected text after program");
return result.expr;
}
console.log(parse("+(a, 10)"));
这是完整的Parser代码,它的核心是parseExpression()。 这是一个递归函数,输入字符串,返回一个表达式对象和剩余的字符串。子表达式也用这个函数解析。上图中的每个黑色圆点都需要调用一次parseExpression() 来生成。
代码本身还是有一点难度的,我是看了两遍,又动手敲了一遍才彻底理解。不过还好,代码比较短,有二十分钟就够了。
1. evaluator
把代码parse成语法树之后,就可以执行了。
function evaluate(expr, env) {
switch (expr.type) {
case "value":
return expr.value;
case "word":
if (expr.name in env)
return env[expr.name];
else
throw new ReferenceError("Undefined variable: " + expr.name);
case "apply":
if (expr.operator.type == "word" &&
expr.operator.name in specialForms) {
return specialForms[expr.operator.name](expr.args, env);
}
var op = evaluate(expr.operator, env);
if (typeof op != "function") {
throw new TypeError("Applying a non-function.");
}
return op.apply(null, expr.args.map(function (arg) {
return evaluate(arg, env);
}));
}
}
evaluate() 的第一个参数是expression,就是刚刚解析好的语法树,当然,也可以是一部分,只要是合法的expression就可以。第二个参数是env,env中包含了所有定义的变量(类似于js的全局变量),也包含了Egg语言预定义的运算符(例如:+, -, *, /等)和关键字(true, false等)。
evaluate()函数体中对三种expression类型分别进行了处理,"value" 和 "word" 类型处理很简单。"apply" 类型的处理相当令人费解,需要先看看specialForm 和env:
var specialForms = Object.create(null);
specialForms["if"] = function(args, env) {
if (args.length != 3)
throw new SyntaxError("Bad number of args to if");
if (evaluate(args[0], env) !== false)
return evaluate(args[1], env);
else
return evaluate(args[2], env);
};
specialForms["while"] = function(args, env) {
if (args.length != 2)
throw new SyntaxError("Bad number of args to while");
while (evaluate(args[0], env) !== false)
evaluate(args[1], env);
// Since undefined does not exist in Egg, we return false,
// for lack of a meaningful result.
return false;
};
specialForms["do"] = function(args, env) {
var value = false;
args.forEach(function(arg) {
value = evaluate(arg, env);
});
return value;
};
specialForms["define"] = function(args, env) {
if (args.length != 2 || args[0].type != "word")
throw new SyntaxError("Bad use of define");
var value = evaluate(args[1], env);
env[args[0].name] = value;
return value;
};
var topEnv = Object.create(null);
topEnv["true"] = true;
topEnv["false"] = false;
["+", "-", "*", "/", "==", "<", ">"].forEach(function(op) {
topEnv[op] = new Function("a, b", "return a " + op + " b;");
});
topEnv["print"] = function(value) {
console.log(value);
return value;
};
可见,在specialForms和topEnv中,apply类型相关的operator都是function。所以,evaluate()的第13、16行,都会返回function。
至此,parser和evaluator的代码就完整了,可以执行一段Egg代码了。
再提供一个run()函数,它可以让我们的代码显得更整齐:
function run() {
var env = Object.create(topEnv);
var program = Array.prototype.slice.call(arguments, 0).join("n");
return evaluate(parse(program), env);
}
run("do(define(total, 0),",
" define(count, 1),",
" while(<(count, 11),",
" do(define(total, +(total, count)),",
" define(count, +(count, 1)))),",
" print(total))");
// → 55
1. Function
我们的Egg语言还不支持function,现在加上它。添加一个关键字:fun 。
specialForms["fun"] = function (args, env) {
if (!args.length)
throw new SyntaxError("Functions need a body");
function name(expr) {
if (expr.type != "word")
throw new SyntaxError("Arg names must be words");
return expr.name;
}
var argNames = args.slice(0, args.length - 1).map(name);
var body = args[args.length - 1];
return function () {
if (arguments.length != argNames.length)
throw new TypeError("Wrong number of arguments");
var localEnv = Object.create(env);
for (var i=0; i< arguments.length; i++) {
localEnv[argNames[i]] = arguments[i];
}
return evaluate(body, localEnv);
}
};
只剩下瞻仰的份儿了,打死我也想不出来这种实现方法。背下来,背下来。
1. Compilation (编译)
我们刚刚实现的parser和evaluator,实际上是个解释器。可以在paser和“执行” 中间加一层“编译”,就是把解析完成的语法树转换成机器码(机器语言)。这样的话,执行效率会大大提升。 当然,也可以把Egg编译成js代码,也会大大提升执行效率。 TypeScript的编译器(transpiler)就是干这个用的。
1. Exercise: Arrays
Egg的Array: array(1,2,5)
实现array和两个方法length(array) ,element(array, i)
分析:
array后面有括号,所以,它是apply类型。parser不需要修改。
对于apply类型,eveluator 都是按function处理。要么在specialForms中添加定义,要么在env中添加定义,其实都可以。我们倾向于把特殊的语法词汇(如:if,do,while等)放在specialForms中,那么,array就放在topEnv中。而且,topEnv中已经有了print函数,把length 和 element 放在topEnv中也就顺理成章了。
js本身有Array,只需要把function的arguments (第四章讲过这个arguments) 转换成js的Array就可以了。
注意,arguments本身不是Array,但在某种程度上,它和Array同型,可以用Array.prototype.slice.call(arguments,0) 把它转换成真正的Array。 这是个trick,js中有很多这样的小技巧,后面的章节还会遇到类似的。
topEnv["array"] = function () {
return Array.prototype.slice.call(arguments, 0);
};
topEnv["length"] = function (array) {
return array.length;
};
topEnv["element"] = function (array, i) {
return array[i];
};
加上array之后,执行一下下面的Egg代码,看看输出什么:
run("do(define(sum, fun(array,",
" do(define(i, 0),",
" define(sum, 0),",
" while(<(i, length(array)),",
" do(define(sum, +(sum, element(array, i))),",
" define(i, +(i, 1)))),",
" sum))),",
" print(sum(array(1, 2, 3))))");
1. Exercise: Closure
run("do(define(f, fun(a, fun(b, +(a, b)))),",
" print(f(4)(5)))");
根据前面对fun的定义,这段代码转换成js代码是:
var f = function(a) {
return function(b) {
return a + b;
}
};
console.log(f(4)(5));
所谓Closure,就是内部函数能得到外层函数的变量,如这里的 a。这是如何做到的呢?看fun的实现代码,这部分:
var localEnv = Object.create(env);
for (var i=0; i< arguments.length; i++) {
localEnv[argNames[i]] = arguments[i];
}
return evaluate(body, localEnv);
注意localEnv,它包含了外层传入的env,还有本身的arguments,无论有多少层函数内嵌,这些env和arguments都会传下去。
localEnv = Object.create(env) 把env作为localEnv的prototype,而变量是作为env对象的属性存储的,所以局部变量不会覆盖全局变量。有多层函数嵌套,会生成多层prototype,互相之间不会影响。
js的Closure也是这个原理吗?
1. Exercise: Comments
修改skipSpace() 方法,使Egg支持单行注释,注释以 # 开头。
原来的:
function skipSpace(string) {
var first = string.search(/S/);
if (first == -1) return "";
return string.slice(first);
}
修改后的:
function skipSpace(string) {
var skippable = string.match(/^(s|#.*)*/);
return string.slice(skippable[0].length);
}
注意,正则表达式中的 . 是不匹配 n 的。
试一下:
console.log(parse("# hellonx"));
// → {type: "word", name: "x"}
console.log(parse("a # onen # twon()"));
// → {type: "apply",
// operator: {type: "word", name: "x"},
// args: []}
给变量赋值 set:
specialForms["set"] = function(args, env) {
if (args.length != 2 || args[0].type != "word")
throw new SyntaxError("Bad use of set");
var varName = args[0].name;
var value = evaluate(args[1], env);
for (var scope = env; scope; scope = Object.getPrototypeOf(scope)) {
if (Object.prototype.hasOwnProperty.call(scope, varName)) {
scope[varName] = value;
return value;
}
}
throw new ReferenceError("Setting undefined variable " + varName);
};
使用方法:
run("do(define(x, 4),",
" define(setx, fun(val, set(x, val))),",
" setx(50),",
" print(x))");
// → 50
run("set(quux, true)");
// → Some kind of ReferenceError
这里再一次重申了变量scope的实现方法。
最后
以上就是娇气海燕为你收集整理的Eloquent JavaScript 笔记 十一:A Programming Language的全部内容,希望文章能够帮你解决Eloquent JavaScript 笔记 十一:A Programming Language所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复