【前端与编译原理】用JS写一个JS解释器

使用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 可能有两种情况,IdentifierMenberExpression
若为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这一个单线程的语言,有关异步操作有两种实现方式,分别为generatorpromise,ES6中拥有为promise服务的语法糖asyncawait
实现generator和实现asyncawait关键词的方法有:
1、比较朴素的实现方法是使用generator在每一次递归时进行处理
kumlowhup:用JS写JS解释器
2、采用非递归
先讲解析器写成非递归形式,配套tj/cotj/co插件进行处理
实现:js编译器答案

ES6

还可以继续补充实现(gugugu)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值