前言 上篇文章 我为大家介绍了语法解析
的一些基本概念,以及如何通过自定义的DSL语言实现Simple语言解释器的语法树解析。在本篇也是这个系列最后一篇文章中我将为大家介绍Simple解释器是如何执行生成的语法树的。
evaluate函数和作用域 前面在介绍语法解析相关知识的时候有出现过evaluate
函数,其实基本每一个AST节点都会有一个对应的evaluate函数
,这个函数的作用就是告诉Simple解释器如何执行当前AST节点。因此Simple解释器执行代码的过程就是:从根节点开始执行当前节点的evaluate函数然后递归地执行子节点evalute函数的过程
。
我们知道JavaScript代码执行的时候有一个概念叫做作用域
,当我们访问一个变量的时候,会先看看当前作用域有没有定义这个变量,如果没有就会沿着作用域链向上一直寻找到全局作用域,如果作用域链上都没有该变量的定义的话就会抛出一个Uncaught ReferenceError: xx is not defined
的错误。在实现Simple语言解释器的时候,我参照了JavaScript作用域的概念实现了一个叫做Environment
的类,我们来看看Evironment类的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class Environment { private parent: Environment = null protected values: Object = {} create(key: string , value: any ) { if (this .values.hasOwnProperty(key)) { throw new Error (`${key} has been initialized` ) } this .values[key] = value } update(key: string , value: any ) { const matchedEnvironment = this .getEnvironmentWithKey(key) if (!matchedEnvironment) { throw new Error (`Uncaught ReferenceError: ${key} hasn't been defined` ) } matchedEnvironment.values = { ...matchedEnvironment.values, [key]: value } } get (key: string ) { const matchedEnvironment = this .getEnvironmentWithKey(key) if (!matchedEnvironment) { throw new Error (`Uncaught ReferenceError: ${key} is not defined` ) } return matchedEnvironment.values[key] } private getEnvironmentWithKey(key: string ): Environment { if (this .values.hasOwnProperty(key)) { return this } let currentEnvironment = this .parent while (currentEnvironment) { if (currentEnvironment.values.hasOwnProperty(key)) { return currentEnvironment } currentEnvironment = currentEnvironment.parent } return null } }
从上面的代码以及注释可以看出,所谓的作用域链其实就是由Environment实例组成的单向链表
。解析某个变量值的时候会沿着这个作用域链进行寻找,如果没有找到该变量的定义就会报错。接着我们以for循环执行的过程来看一下具体过程是怎么样的:
被执行的代码:
1 2 3 for (let i = 0 ; i < 10 ; i++) { console .log(i); };
ForStatement代码的执行过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ForStatement extends Node { ... evaluate(env: Environment): any { const bridgeEnvironment = new Environment(env) this .init.evaluate(bridgeEnvironment) while (!runtime.isBreak && !runtime.isReturn && this .test.evaluate(bridgeEnvironment)) { const executionEnvironment = new Environment(bridgeEnvironment) this .body.evaluate(executionEnvironment) this .update.evaluate(bridgeEnvironment) } } }
闭包和this绑定 在理解了evalute函数的一般执行过程后,我们再来看看闭包
是如何实现的。我们都知道JavaScript是词法作用域
,也就是说一个函数的作用域链在这个函数被定义的时候就决定了
。我们通过函数声明节点FunctionDeclaration
的evaluate函数的代码来看一下Simple语言的闭包是如何实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class FunctionDeclaration extends Node { ... evaluate(env: Environment): any { const func = new FunctionDeclaration() func.loc = this .loc func.id = this .id func.params = [...this.params] func.body = this .body func.parentEnv = env env.create(this .id.name, func) } ... }
从上面的代码可以看出,要实现Simple语言的闭包,其实只需要在函数声明的时候记录一下当前作用域(parentEnv)就可以了
。
接着我们再来看一下函数执行的时候是如何判断this
绑定的是哪个对象的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class FunctionDeclaration extends Node { ... call(args: Array <any >, callerInstance?: any ): any { if (this .params.length !== args.length) { throw new Error ('function declared parameters are not matched with arguments' ) } const callEnvironment = new Environment(this .parentEnv) for (let i = 0 ; i < args.length; i++) { const argument = args[i] const param = this .params[i] callEnvironment.create(param.name, argument) } callEnvironment.create('arguments' , args) if (callerInstance) { callEnvironment.create('this' , callerInstance) } else { callEnvironment.create('this' , this .parentEnv.getRootEnv().get('process' )) } this .body.evaluate(callEnvironment) } }
上面的代码大概给大家介绍了Simple语言的this是如何绑定的,实际上JavaScript的实现可能和这个有比较大的出入,这里只是给大家一个参考而已。
总结 在本篇文章中我给大家介绍了Simple解释器是如何执行代码的,其中包括闭包和this绑定的内容,由于篇幅限制这里忽略了很多内容,例如for和while循环的break语句是如何退出的,函数的return语句是如何将值传递给父级函数的,大家如果感兴趣可以看一下我的源码:https://github.com/XiaocongDong/simple。
最后希望大家经过这三篇系列文章的学习可以对编译原理和JavaScript一些比较难懂的语言特性有一定的了解,也希望后面我可以继续给大家带来优质的内容来让我们共同进步。
个人技术动态 欢迎关注公众号进击的大葱 一起学习成长