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: []}
给变量赋值 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的实现方法。
- Eloquent JavaScript 笔记 十一:A Programming Language
- Eloquent JavaScript 笔记 十五:A Platform Game
- 《Eloquent JavaScript》笔记--函数;
- Eloquent JavaScript 笔记 三: Functions
- Eloquent JavaScript 笔记 十: Modules
- Eloquent JavaScript 笔记 十三:DOM
- Eloquent JavaScript 笔记 十七:HTTP
- A C Programming Language Puzzle
- The Swift Programming Language学习笔记(十一)——属性
- The Swift Programming Language学习笔记(二十一)——嵌套类型
- 《Eloquent JavaScript》笔记--对象与数组
- 《Eloquent JavaScript》笔记--程序的结构;
- Eloquent JavaScript 笔记 二:Program Structure
- Eloquent JavaScript 笔记 五: High-Order Functions
- Eloquent JavaScript 笔记 四:Objects and Arrays
- Eloquent JavaScript 笔记 七: Electronic Life
- Eloquent JavaScript 笔记 十四:Handling Event
- Eloquent JavaScript 笔记 十六:Drawing on Canvas
- 2017年微软暑期实习PM产品经理面经
- 深入了解装箱和拆箱
- android开发之布局(大概)
- xmapp后localhost不能访问
- 拟牛顿法 分析与推导
- Eloquent JavaScript 笔记 十一:A Programming Language
- just blog
- SQL 获取排名排序后的名次
- c++远征之多态篇——异常处理
- getRequestDispatcher()与sendRedirect()的区别
- getopt 模块
- IDEA快捷键设置,选择Eclipse风格的快捷键,自动补全快捷键,关闭当前窗口快捷键Ctrl+W,自动导入设置,引入外部的jar的方式,设置项目字体,设置项目字符集,Maven的方式获取jar
- 19. 将s所指字符串中的所有数字字符移到所有非数字字符之后,并保持数字字符和非数字字符串原有的先后次序。
- Java Socket 编程