【前端与编译原理】用JS写一个JS解释器
- 使用JS写一个JS解释器
- 引子
- 抽象语法树(AST)
- 入口功能和环境
- ES5的功能的实现
- Program 程序入口
- Literal 原始值
- Identifier 字面量
- ExpressionStatement 表达式
- VariableDeclaration 变量声明
- AssignmentExpression 变量赋值
- UpdateExpression 自加1或自减1
- 基本运算
- ObjectExpression 对象表达式
- ArrayExpression 数组表达式
- MemberExpression 成员变量表达式
- CallExpression 调用函数
- FunctionExpression 普通函数表达式
- FunctionDeclaration 函数声明
- 实现new和this的指向
- 循环表达式
- Try Catch Finally和 throw
- 异步操作
- ES6
使用JS写一个JS解释器
引子
一个很实用的问题,用javascript写一个javascript的解释器
问:为什么要用JS写JS的解释器
答:接触过小程序开发的同学应该知道,小程序运行的环境禁止new Function,eval等方法的使用,导致我们无法直接执行字符串形式的动态代码。此外,许多平台也对这些JS自带的可执行动态代码的方法进行了限制,那么我们是没有任何办法了吗?既然如此,我们便可以用JS写一个解析器,让JS自己去运行自己。在开始之前,我们先简单回顾一下编译原理的一些概念。
文章引用:
js写一个js解释器
微信小程序也要强行热更代码,鹅厂不服你来肛我呀
抽象语法树(AST)
使用相关的工具(如acorn),把字符串代码生成JSON对象的语法树
acorn.js:github/acornjs
预览生成ast结构: AST explorer
入口功能和环境
npm
或者yarn
安装acorn.js
用acorn.js
将字符串转化为ast结构
evaluate
函数处前先声明好srcCode
变量便于evaluate
let srcCode
定义customerEval
函数,调用acore.parse
生成ast节点
若仅欲取得表达式的结果,可调用acore.parseExpression
将ast节点代入
/**
* @param {string} code 待解析的代码字符串
* @param {Scope} env 定义的除globalThis以外的最外层作用域
*/
function customerEval(code, env = new Scope('block')) {
srcCode = code
const node = acorn.parse(code, {
ecmaVersion: 5 // 可填入2020或2022以兼容后续版本
})
return evaluate(node, env)
}
此处定义好一个evaluate
函数,供ast节点的遍历递归用,对接收到的Node节点判定type并进行对应的处理
/**
*
* @param {Node} node AST节点
* @param {Scope} scope 当前作用域 Scope
* @param {JSON} config 配置项(label,isSetObj)
* @author: kumlowhup
* @date : 2022-2-2
* @returns node为js表达式时返回运算结果
*/
function evaluate(node, scope, config) {
case 'Literal':
return node.value;
case 'Identifier': {
return scope.get(node.name);
}
// 。。。省略剩余
}
作用域Scope的定义
Scope
类用于存储作用域内的变量
class Scope {
constructor(type, parent) {
this.variables = {};
this.type = type; // 'funcition' | 'block'
this.parent = parent;
}
declare(kind = 'var', name, initValue = undefined) {
if (kind === 'var') {
// 把变量声明提升至函数体顶部
let scope = this
while (scope.parent && scope.type !== 'function') { scope = scope.parent }
scope.variables[name] = new Value(initValue, 'var')
return this.variables[name]
} else if (kind === 'let') {
if (name in this.variables) { throw new SyntaxError(`Identifier ${name} has already been declared`) }
this.variables[name] = new Value(initValue, 'let')
return this.variables[name]
} else if (kind === 'const') {
if (name in this.variables) { throw new SyntaxError(`Identifier ${name} has already been declared`) }
this.variables[name] = new Value(initValue, 'const')
return this.variables[name]
} else {
throw new Error(`canjs: Invalid Variable Declaration Kind of "${kind}"`)
}
}
get(name) {
if (name in this.variables) {
return this.variables[name].value
}
else if (this.parent) { return this.parent.get(name) }
else if (globalThis[name]) { return globalThis[name] }
throw new ReferenceError(`${name} is not defined`)
}
set(name, value) {
if (this.variables[name]) { this.variables[name].set(value) }
else if (this.parent) { this.parent.set(name, value) }
}
}
变量储存Value类的定义
class Value {
constructor(value, kind = '') {
this.value = value
this.kind = kind
}
set(value) {
// 禁止重新对const类型变量赋值
if (this.kind === 'const') {
throw new TypeError('Assignment to constant variable')
} else {
this.value = value
}
}
get() { return this.value }
}
关于全局对象
使用globalThis
调用,nodejs
中为global
对象,浏览器、小程序等中为window
对象
ES5的功能的实现
关于ES5,或ECMA2009
acornjs生成的es5语法树的node定义:
原文档:estree/es5
Program 程序入口
程序入口:遍历其body
case 'Program': {
let arr = node.body.map(n => evaluate.call(this, n, scope))
return arr.length ? arr[arr.length - 1] : undefined
}
Literal 原始值
字面量直接为值 直接读取value
case 'Literal':
return node.value;
Identifier 字面量
Identifier为读取字面量,可能为变量名,属性名等,若为变量名则调用当前作用域函数,在当前作用域、其父级作用域或globalThis中读取变量
case 'Identifier':
return scope.get(node.name);
ExpressionStatement 表达式
表达式,返回表达式的运算结果
case 'ExpressionStatement':
return evaluate.call(this, node.expression, scope)
VariableDeclaration 变量声明
// 变量声明
case 'VariableDeclaration': {
return node.declarations.forEach(v => {
return scope.declare(node.kind, v.id.name, evaluate.call(this, v.init, scope))
})
}
AssignmentExpression 变量赋值
变量赋值
case 'ExpressionStatement':
return evaluate.call(this, node.expression, scope)
运算符的定义
enum AssignmentOperator {
"=" | "+=" | "-=" | "*=" | "/=" | "%="
| "<<=" | ">>=" | ">>>="
| "|=" | "^=" | "&="
}
node.left
可能有两种情况,Identifier
和MenberExpression
若为Identifier
则调用Scope.set
为变量赋值,否则向对象的属性赋值,向函数第三个参数config
传入{ setObjPropVal: true }
,便以MemberExpression
处理时候分辨返回对象值还是[对象,属性名]
let rightValue = evaluate.call(this, node.right, scope)
if (node.left.type === 'Identifier') {
// 取出左原值
let leftValue = evaluate.call(this, node.left, scope)
// 直接给变量赋值
switch (node.operator) {
case '=': scope.set(node.left.name, rightValue); break;
case '+=': scope.set(node.left.name, leftValue + rightValue); break;
case '-=': scope.set(node.left.name, leftValue - rightValue); break;
case '/=': scope.set(node.left.name, leftValue / rightValue); break;
case '*=': scope.set(node.left.name, leftValue * rightValue); break;
case '%=': scope.set(node.left.name, leftValue % rightValue); break;
case '<<=': scope.set(node.left.name, leftValue << rightValue); break;
case '>>=': scope.set(node.left.name, leftValue >> rightValue); break;
case '>>>=': scope.set(node.left.name, leftValue >>> rightValue); break;
case '|=': scope.set(node.left.name, leftValue | rightValue); break;
case '^=': scope.set(node.left.name, leftValue ^ rightValue); break;
case '&=': scope.set(node.left.name, leftValue & rightValue); break;
}
return scope.get(node.left.name)
} else if (node.left.type === 'MemberExpression') {
// 给对象的内部属性赋值
let [leftObj, leftPropName] = evaluate.call(this, node.left, scope, { setObjPropVal: true })
let leftValue = leftObj[leftPropName]
let retVal;
switch (node.operator) {
case '=': retVal = rightValue; break;
case '+=': retVal = leftValue + rightValue; break;
case '-=': retVal = leftValue - rightValue; break;
case '/=': retVal = leftValue / rightValue; break;
case '*=': retVal = leftValue * rightValue; break;
case '%=': retVal = leftValue % rightValue; break;
case '<<=': retVal = leftValue << rightValue; break;
case '>>=': retVal = leftValue >> rightValue; break;
case '>>>=': retVal = leftValue >>> rightValue; break;
case '|=': retVal = leftValue | rightValue; break;
case '^=': retVal = leftValue ^ rightValue; break;
case '&=': retVal = leftValue & rightValue; break;
}
leftObj[leftPropName] = retVal;
return retVal;
}
UpdateExpression 自加1或自减1
变量自加1或自减1,与变量赋值类似,返回值需要分辨返回运算前or后的欠货
// ++ 和 --
case 'UpdateExpression': {
let preValue = evaluate.call(this, node.argument, scope)
if (node.argument.type === 'MemberExpression') {
let [obj, objPropName] = evaluate.call(this, node.argument, scope, { setObjPropVal: true })
if (node.operator === '++') {
return node.prefix ? ++obj[objPropName] : obj[objPropName]++
} else {
return node.prefix ? --obj[objPropName] : obj[objPropName]--
}
} else {
// node.argument.type === 'Indentifier'
if (node.operator === '++') {
scope.set(node.argument.name, preValue + 1)
return node.prefix ? (preValue + 1) : preValue
} else {
scope.set(node.argument.name, preValue - 1)
return node.prefix ? (preValue - 1) : preValue
}
}
}
基本运算
ConditionalExpression 三目运算符
按 ? : 格式直接套入
// 三目运算符
case 'ConditionalExpression':
return evaluate.call(this, node.test, scope) ? evaluate.call(this, node.consequent, scope) : evaluate.call(this, node.alternate, scope)
if和switch
// If
case 'IfStatement': {
if (evaluate.call(this, node.test, scope)){
return evaluate.call(this, node.consequent, scope)
} else if (node.alternate){
return evaluate.call(this, node.alternate, scope)
} else return
}
// Switch
case 'SwitchStatement': {
let ret
node.cases.forEach(c => {
if (c.test !== null && !(evaluate.call(this, c.test, scope) === evaluate.call(this, node.discriminant, scope))) return ret
c.consequent.forEach(e => {
if (e.type === 'BlockStatement') {
ret = evaluate.call(this, e, new Scope('block', scope))
} else {
ret = evaluate.call(this, e, scope)
}
})
})
return ret
}
逻辑运算符
// 逻辑运算符
case 'LogicalExpression': {
switch (node.operator) {
case '&&': return evaluate.call(this, node.left, scope) && evaluate.call(this, node.right, scope)
case '||': return evaluate.call(this, node.left, scope) || evaluate.call(this, node.right, scope)
}
}
基本运算符
// 基本运算符
/**
*
enum BinaryOperator {
"==" | "!=" | "===" | "!=="
| "<" | "<=" | ">" | ">="
| "<<" | ">>" | ">>>"
| "+" | "-" | "*" | "/" | "%"
| "|" | "^" | "&" | "in"
| "instanceof"
}
*/
case 'BinaryExpression': {
const left = evaluate.call(this, node.left, scope)
const right = evaluate.call(this, node.right, scope)
switch (node.operator) {
case '==': return left == right
case '!=': return left != right
case '===': return left === right
case '!==': return left !== right
case '<': return left < right;
case '<=': return left <= right
case '>': return left > right
case '>=': return left >= right
case '<<': return left << right
case '>>': return left >> right
case '>>>': return left >>> right
case '+': return left + right
case '-': return left - right
case '*': return left * right
case '/': return left / right
case '%': return left % right
case '|': return left | right
case '^': return left ^ right
case '&': return left & right
case 'in': return left in right
case 'instanceof': return left instanceof right
}
}
单关键字运算符 UnaryExpression
// enum UnaryOperator {"-" | "+" | "!" | "~" | "typeof" | "void" | "delete"}
case 'UnaryExpression': {
switch (node.operator) {
case '-': return -evaluate.call(this, node.argument, scope)
case '+': return +evaluate.call(this, node.argument, scope)
case '!': return !evaluate.call(this, node.argument, scope)
case '~': return ~evaluate.call(this, node.argument, scope)
case 'typeof': return typeof evaluate.call(this, node.argument, scope)
}
}
ObjectExpression 对象表达式
//对象
case 'ObjectExpression':
{
let props = node.properties
const obj = {}
props.forEach(p => {
obj[p.key.name] = evaluate.call(this, p.value, scope)
});
return obj
}
ArrayExpression 数组表达式
// 数组
case 'ArrayExpression': {
return node.elements.map(e => evaluate.call(this, e, scope)) || []
}
MemberExpression 成员变量表达式
返回读取的变量值或返回[对象,属性名]
组合
case 'MemberExpression': {
// 是否设置属性内部值
let isSetObjPropVal = config?.setObjPropVal
let obj = node.object.name ? scope.get(node.object.name) : evaluate.call(this, node.object, scope)
let pname = node.computed ? evaluate.call(this, node.property, scope) : node.property.name
let propValue = obj[pname]
if (propValue instanceof BlockInterruption) propValue = propValue.value
return isSetObjPropVal ? [obj, pname] : propValue
}
CallExpression 调用函数
// 调用执行函数
case 'CallExpression': {
// console.log('call 处 this', this.toString())
let getFun = evaluate.call(this, node.callee, scope, { setObjPropVal: true })
let ret
if (getFun instanceof Array) {
let [o, p] = getFun;
let f = o[p]
if (!(f instanceof Function)) {
let functionName = srcCode.substring(node.callee.start, node.callee.end)
throw new TypeError(`${functionName}is not a function`)
}
// 若通过对象的属性调取的函数,则绑定函数的this为该对象
ret = f.apply(o, node.arguments.map(arg => evaluate.call(this, arg, scope)))
} else {
// 若通过变量名直接调取的函数,则绑定this为上下文的this
if (!getFun instanceof Function) {
let functionName = srcCode.substring(node.callee.start, node.callee.end)
throw new TypeError(`${functionName}is not a function`)
}
ret = getFun.apply(this, node.arguments.map(arg => evaluate.call(this, arg, scope)))
}
return ret instanceof BlockInterruption ? ret.value : ret
}
FunctionExpression 普通函数表达式
// 普通函数
case 'FunctionExpression': {
let fun = function (...args) {
const funScope = new Scope('function', scope)
node.params.forEach((param, i) => {
funScope.declare('let', param.name, args[i])
})
return evaluate.call(this, node.body, funScope);
}
if (node.id !== null) {
scope.declare('var', node.id.name, fun)
}
fun.toString = () => srcCode.substring(node.start, node.end)
return fun
}
FunctionDeclaration 函数声明
case 'FunctionDeclaration': {
// 命名函数
return scope.declare('var', node.id.name, function (...args) {
const nodeScope = new Scope('function', scope)
node.params.forEach((param, i) => {
nodeScope.declare('let', param.name, args[i])
})
return evaluate.call(this, node.body, nodeScope);
})
}
实现new和this的指向
// new 构造函数
case 'NewExpression': {
const callee = evaluate.call(this, node.callee, scope)
if (callee.prototype === undefined) {
let funName;
node.callee
throw new TypeError(`${srcCode.substring(node.callee.start, node.callee.end)} is not a constructor`)
}
const args = node.arguments.map(arg => evaluate.call(this, arg, scope))
const o = Object.create(callee.prototype)
o.toString = () => `[object ${node.callee.name}]`
let k = callee.apply(o, args)
if (k instanceof BlockInterruption) {
return k.value instanceof Object ? k.value : o
} else {
return o
}
}
case 'ThisExpression': {
return this
}
循环表达式
以for为例
关于BlockInterruption
类的定义
class BlockInterruption {
constructor(type, value) {
this.type = type // return | break | continue
this.value = value
}
getType() {
return this.type
}
setLabel(label) {
this.label = label
}
getLabel() {
return this.label
}
}
for实现
// for语句
case 'ForStatement': {
let ret
let label = config?.label
// 包括定义索引等的定义域
const forScope = new Scope('block', scope)
for (evaluate.call(this, node.init, forScope); evaluate.call(this, node.test, forScope); evaluate.call(this, node.update, forScope)) {
// 每次循环内产生内作用域
const forInnerScope = new Scope('block', forScope)
// 运行while内代码
ret = evaluate.call(this, node.body, forInnerScope)
// continue
if (ret instanceof BlockInterruption && ret.getType() === 'continue') {
// 无label或指定当前label 跳过当前while本次循环
if (ret.getLabel() === undefined || ret.getLabel() === label) { continue }
// label不匹配 向上一级作用域抛
else return ret
}
// break
if (ret instanceof BlockInterruption && ret.getType() === 'break') {
if (ret.getLabel() === undefined || ret.getLabel() === label) { return }
else return ret
}
// return
if (ret instanceof BlockInterruption && ret.getType() === 'return') return ret
}
return
}
label标记
case 'LabeledStatement': {
return evaluate.call(this, node.body, scope, {
label: node.label.name
})
}
Try Catch Finally和 throw
// try
case 'TryStatement': {
try {
const tryScope = new Scope('block', scope)
evaluate.call(this, node.block, tryScope)
} catch (err) {
const catchScope = new Scope('block', scope)
catchScope.declare('let', node.handler.param.name, err)
return evaluate.call(this, node.handler.body, catchScope)
} finally {
const finallyScope = new Scope('block', scope)
evaluate.call(this, node.finalizer, finallyScope)
}
}
// throw
case 'ThrowStatement': {
throw evaluate.call(this, node.argument, scope)
}
异步操作
对于js这一个单线程的语言,有关异步操作有两种实现方式,分别为generator
和promise
,ES6中拥有为promise
服务的语法糖async
和await
。
实现generator
和实现async
和await
关键词的方法有:
1、比较朴素的实现方法是使用generator在每一次递归时进行处理
kumlowhup:用JS写JS解释器
2、采用非递归
先讲解析器写成非递归形式,配套tj/co
tj/co插件进行处理
实现:js编译器答案
ES6
还可以继续补充实现(gugugu)