Eloquent JavaScript 笔记 十一:A Programming Language

来源:互联网 发布:亚丝娜的剑数据 编辑:程序博客网 时间:2024/05/23 16:46


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("# hello\nx"));// → {type: "word", name: "x"}console.log(parse("a # one\n   # two\n()"));// → {type: "apply",//    operator: {type: "word", name: "x"},//    args: []}


1. Exercise: Fixing Scope

给变量赋值 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))");// → 50run("set(quux, true)");// → Some kind of ReferenceError

这里再一次重申了变量scope的实现方法。


阅读全文
0 0
原创粉丝点击