hugh 的个人博客

Everyday is a new day

语法分析3 - 一步步实现JS语言编译执行

js语言最常见的就是变量定义了,上节中我们实现了基础的单表达式解析计算, 本节将支持变量的定义及变量的引用,实现多个语句一起执行

目标
实现赋值语句, 变量申明和初始化
完善PrimaryAstNode中遗留的Identifier计算问题
完善声明,赋值等表达式计算

词法部分

为支持声明,赋值等操作, 我们需要加入 =var等的token识别

实现声明和赋值语句

有文法规则了, 赋值表达式实现方式同前面的加法,乘法。
声明操作需要组合赋值表达式

/**
     * v0.0.3
     * 变量声明语句
     *
     * 简化语法如下
     * VariableStatement :
     *      var VariableDeclaration ;
     *
     * VariableDeclaration:
     *      Identifier Initialiser(opt)
     *
     * Initialiser:
     *      = AssignmentExpression
     *
     */
    variable() {

        let node = null;
        let nextToken = this.lexerLevel.tokenPeek()
        let tempToken = nextToken;
        if(nextToken && nextToken.type === this.lexerLevel.DfaState.Var) {
            node = new DeclarationAstNode("Declaration")
            this.lexerLevel.tokenRead();
            nextToken = this.lexerLevel.tokenPeek()
            if(nextToken && nextToken.type === this.lexerLevel.DfaState.Identifier) {
                // 表示var + 标识符
                let childId = new PrimaryAstNode(nextToken.type, nextToken.value)
                this.lexerLevel.tokenRead();
                node.addLeftChild(childId)
                // 检查是否有表达式
                nextToken = this.lexerLevel.tokenPeek()
                if(nextToken && nextToken.type === this.lexerLevel.DfaState.Assignment) {
                    this.lexerLevel.tokenRead();
                    let childInit = this.assignment();
                    if(childInit) {
                        node.addRightChild(childInit)
                    } else {
                        throw Error("no assignment expression after Assignment ")
                    }
                }
                nextToken = this.lexerLevel.tokenPeek()
                if(nextToken && nextToken.type === this.lexerLevel.DfaState.SemiColon) {
                    this.lexerLevel.tokenRead();
                }
            } else {
                throw Error("no Identifier after Var ")
            }
        }
        return node;
    }

实现多语句支持

在前一节中, 我们还都是对单个语句做ast,本节我们要支持多个语句, 其实就是加一个处理语句的入口
这时候,我们就需要一个根节点了

 /**
     * 解析生成AST
     */
    astParse() {
        // 涉及多个语句, 先初始化一个根节点
        let node = new AstNode("Program", "");
        let nextToken = this.lexerLevel.tokenPeek()
        while(nextToken) {

            // 1
            let cv = this.variable();
            if(!cv) {
                cv = this.assignment()
            }
            // 2
            if(!cv) {
                cv = this.bitwiseShift()
            }

            if(cv) {
                node.addChild(cv)
            } else {
                throw Error("not support current expression!")
            }

            nextToken = this.lexerLevel.tokenPeek()
        }

       return node
    }

如何支持变量

这边就涉及到一个变量消解了。 我们现在只需要支持多个语句,并且类型也有限。 可以使用一个对象来存储对应的变量,

this.variables = {}

有了存储空间,那么我们什么时候往里存放值呢?
考虑到js的var定义的变量,有个特性:变量提升

变量提升,是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。
有个这个指导, 我们就清楚做以下操作:

const stack = require("../Stack")
const AstNode = require("./AstNode")
// 标识一个变量声明节点
class DeclarationAstNode extends AstNode{
    constructor(type, value) {
        super(type, value)
        this.id = null;
        this.init = null
    }

    /**
     *
     * @returns {*}
     */
    getValue(){
       // 仅对变量赋值(真实调用时赋值)
        stack.setVal(this.id.value, this.init? this.init.getValue(): undefined);
        
       return stack.getVal(this.id.value);
    }
   // 左侧是声明的变量名
    addLeftChild(child) {
        this.id = child;
        super.addChild(child)
        // v0.0.3版本, 暂不考虑作用域链的情况
        // 表示我在词法解析的节点就已经将该变量提升了
        stack.addVar(child.value, undefined) // 初始为undefined
    }

    addRightChild(child) {
        this.init = child;
        super.addChild(child)
    }

}

module.exports = DeclarationAstNode

stack对应的方法内容如下

class Stack {

    constructor() {
        this.variables = {}
    }
    
    addVar(key, value) {
        this.variables[key] = value;
    }
    
    setVal(key, value) {
        this.variables[key] = value; 
    }
    
    deleteVar(key) {
        delete this.variables[key];
    }
    
    hasVar(key) {
        return this.variables.hasOwnProperty(key)
    }
    
    getVal(key) {
        return this.variables[key]
    }

}

module.exports = new Stack()

测试

  1. 测试语法树
 var a =2;
   var b = 3;
   a = a * b + 3;

生成的ast结构如下:

AstNode {
  type: 'Program',
  value: '',
  children: 
   [ DeclarationAstNode {
       type: 'Declaration',
       value: '',
       children: [Array],
       parent: [Circular],
       id: [PrimaryAstNode],
       init: [PrimaryAstNode] },
     DeclarationAstNode {
       type: 'Declaration',
       value: '',
       children: [Array],
       parent: [Circular],
       id: [PrimaryAstNode],
       init: [PrimaryAstNode] },
     AssignmentAstNode {
       type: 'Assignment',
       value: '',
       children: [Array],
       parent: [Circular],
       opera: undefined,
       left: [PrimaryAstNode],
       right: [BinaryAstNode] } ],
  parent: null }
  1. 测试异常, 即变量未定义就使用的
let syntaxLevel2 = new SyntaxLevel1(`
   var a =2;
   var b = 3;
   a = a * c + 3;
   `)
    console.log( syntaxLevel2.exe())

结果:

Error: Identifier c is not defined
    at PrimaryAstNode.getValue (/Users/learn/compilation/ast/PrimaryAstNode.js:21:23)
    at BinaryAstNode.getValue (/Users/learn/compilation/ast/BinaryAstNode.js:22:58)
  1. 测试执行
 let syntaxLevel = new SyntaxLevel1(`
   var a =2;
   var b = 3;
   a = a * b + 3;
   
   `)

   console.log( syntaxLevel.exe())

结果:
9


标题:语法分析3 - 一步步实现JS语言编译执行
作者:hugh0524
地址:https://blog.uproject.cn/articles/2020/02/27/1582784062569.html

0 浏览