編譯器開發系列--Ocelot語言2.變量引用的消解

「變量引用的消解」是指肯定具體指向哪一個變量。例如變量「i」多是全局變量i,也多是靜態變量i,還多是局部變量i。經過這個過程來消除這樣的不肯定性,肯定所引用的究竟是哪一個變量。java

爲了消除這樣的不肯定性,咱們須要將全部的變量和它們的定義關聯起來,這樣的處理稱爲「變量引用的消解」。具體來講,就是爲抽象語法樹中全部表示引用變量的VariableNode 對象添加該變量的定義(Variable 對象)的信息。node

LocalResolver就是用來處理變量引用消解的類,繼承自Visitor(「變量引用的消解」和「靜態類型檢查」等一連串的處理)。入口方法爲:函數

    /*入口
     * 生成了以ToplevelScope 爲根節點的
		Scope 對象的樹,而且將全部VariableNode 和其定義關聯起來了。
     */
    // #@@range/resolve{
    public void resolve(AST ast) throws SemanticException {
    	/*
    	 * 第1 部分先生成ToplevelScope 對象, 而後將生成的ToplevelScope 對象用
			scopeStack.add(toplevel) 添加到scopeStack。這樣棧裏面就有了1 個Scope 對象
    	 */
        ToplevelScope toplevel = new ToplevelScope();
        scopeStack.add(toplevel);

        /*變量定義的添加.
         * 兩個foreach 語句都是將全局變量、函數以及類型添加到ToplevelScope 中。
         * 二者都是調用ToplevelScope#declareEntity 往ToplevelScope 對象中添加定義或聲明。
         * 第1個foreach 語句添加導入文件(*.hb)中聲明的外部變量和函數
         * 第2 個foreach 語句用於導入所編譯文件中定義的變量和函數
         */
        // #@@range/declareToplevel{
        for (Entity decl : ast.declarations()) {
            toplevel.declareEntity(decl);
        }
        for (Entity ent : ast.definitions()) {
            toplevel.defineEntity(ent);
        }
        // #@@}
        // #@@range/resolveRefs{
        resolveGvarInitializers(ast.definedVariables());//遍歷全局變量
        resolveConstantValues(ast.constants());//遍歷常量的初始化表達式
        resolveFunctions(ast.definedFunctions());//最重要的
        // #@@}
        toplevel.checkReferences(errorHandler);
        if (errorHandler.errorOccured()) {
            throw new SemanticException("compile failed.");
        }

        /*
         * 在最後部分中,將在此類中生成的ToplevelScope 對象和ConstantTable 對象保存到
			AST 對象中。這兩個對象在生成代碼時會用到,爲了將信息傳給下一階段,因此保存到AST 對
			象中。
         */
        ast.setScope(toplevel);
        ast.setConstantTable(constantTable);
        /*
         * 至此爲止變量引用的消解處理就結束了,上述處理生成了以ToplevelScope 爲根節點的
			Scope 對象的樹,而且將全部VariableNode 和其定義關聯起來了。
         */
    }

先往棧中添加ToplevelScope,ToplevelScope表示程序頂層的做用域,保存有函數和全局變量。對象

而後往這個頂層的做用域添加各類全局變量和函數。blog

這裏着重說下resolveFunctions(ast.definedFunctions());這個方法,對抽象語法樹中的全部函數的消解:繼承

    /*
     * 函數定義的處理.
     */
    // #@@range/resolveFunctions{
    private void resolveFunctions(List<DefinedFunction> funcs) {
        for (DefinedFunction func : funcs) {
        	//調用pushScope 方法,生成包含函數形參的做用域,並將做用域壓到棧(scopeStack)中
            pushScope(func.parameters());
            //用resolve(func.body()) 方法來遍歷函數自身的語法樹
            resolve(func.body());
            //調用popScope 方法彈出剛纔壓入棧的Scope 對象,將該Scope 對象用func.setScope
            //添加到函數中
            func.setScope(popScope());
        }
    }

遍歷全部的函數節點,首先將單個函數節點裏的全部形參或者已定義的局部變量壓入一個臨時變量的做用域。而後再將這個臨時變量的做用域LocalScope壓入scopeStack:遞歸

    //pushScope 方法是將新的LocalScope 對象壓入做用域棧的方法
    // #@@range/pushScope{
    private void pushScope(List<? extends DefinedVariable> vars) {
    	//生成以currentScope() 爲父做用域的LocalScope 對象
    	//currentScope 是返回當前棧頂的Scope 對象的方法
        LocalScope scope = new LocalScope(currentScope());
        /*
         * 接着用foreach 語句將變量vars 添加到LocalScope 對象中。也就是說,向LocalScope
			對象添加在這個做用域上所定義的變量。特別是在函數最上層的LocalScope 中,要添加形參
			的定義。
         */
        for (DefinedVariable var : vars) {
        	//先用scope.isDefinedLocally 方法檢查是否已經定義了同名的變量
            if (scope.isDefinedLocally(var.name())) {
                error(var.location(),
                    "duplicated variable in scope: " + var.name());
            }
            //而後再進行添加。向LocalScope 對象添加變量時使用defineVariable 方法
            else {
                scope.defineVariable(var);
            }
        }
        /*
         * 最後經過調用scopeStack.addLast(scope) 將生成的LocalScope 對象壓到做用域
			的棧頂。這樣就能表示做用域的嵌套了。
         */
        scopeStack.addLast(scope);
    }

而後resolve(func.body());是消解當前函數的函數體,也就是BlockNode節點,根據java的多態特性,最終是調用如下方法:作用域

    /*
     * 添加臨時做用域.
     * C 語言(C♭)中的程序塊({...}block)也會引入新的變量做用域。
     */
    // #@@range/BlockNode{
    public Void visit(BlockNode node) {
    	/*
    	 * 首先調用pushScope 方法,生成存儲着這個做用域上定義的變量的Scope 對象,而後壓
			入做用域棧。
    	 */
        pushScope(node.variables());
        /*
         * 接着執行super.visit(node);,執行在基類Visitor 中定義的處理,即對程序塊的
			代碼進行遍歷。
			visit(VariableNode node)
         */
        super.visit(node);
        /*
         * 最後用popScope 方法彈出棧頂的Scope 對象,調用BlockNode 對象的setScope 方
			法來保存節點所對應的Scope 對象。
         */
        node.setScope(popScope());
        return null;
    }

又是一個pushScope(node.variables());將函數體中的臨時變量聲明的列表保存在一個LocalScope中,而後將這個LocalScope壓入scopeStack棧中。而後最關鍵的來了,super.visit(node);調用Visitor類中的visit方法來對函數體中的局部變量進行消解,get

    public Void visit(BlockNode node) {
        for (DefinedVariable var : node.variables()) {
            if (var.hasInitializer()) {
                visitExpr(var.initializer());
            }
        }
        visitStmts(node.stmts());
        return null;
    }

visitStmts是關鍵,由於變量的引用是保存在VariableNode節點中,最終會調用:it

    /*
     * 創建VariableNode 和變量定義的關聯.
     * 使用以前的代碼已經順利生成了Scope 對象的樹,下面只要實現樹的查找以及引用消解的
		代碼就能夠了。
     */
    // #@@range/VariableNode{
    public Void visit(VariableNode node) {
        try {
        	//先用currentScope().get 在當前的做用域中查找變量的定義
            Entity ent = currentScope().get(node.name());
            /*
             * 取得定義後,經過調用ent.refered() 來記錄定義的引用信息,這樣當變量沒有被用到
				時就可以給出警告。
             */
            ent.refered();
            /*
             * 還要用node.setEntity(ent) 將定義保存到變量節點中,以便隨時可以從VariableNode
				取得變量的定義。
				創建VariableNode 和變量定義的關聯
             */
            node.setEntity(ent);
        }
        /*
         * 若是找不到變量的定義,currentScope().get 會拋出SemanticException 異常,
			將其捕捉後輸出到錯誤消息中。
         */
        catch (SemanticException ex) {
            error(node, ex.getMessage());
        }
        return null;
    }

首先查找離棧最近的一層currentScope(),若是找到了就調用setEntity創建VariableNode 和變量定義的關聯。這裏比較關鍵的是這個get方法,它首先調用LocalScope的get:

    /*從做用域樹取得變量定義.
     * LocalScope#get 是從做用域樹獲取變量定義的方法。
     * 首先調用variables.get 在符號表中查找名爲name 的變量,若是找到的話就返回
		該變量,找不到的話則調用父做用域(parent)的get 方法繼續查找。若是父做用域是
		LocalScope 對象,則調用相同的方法進行遞歸查找。
		若是父做用域是ToplevelScope 的話,則調用ToplevelScope#get
     */
    // #@@range/get{
    public Entity get(String name) throws SemanticException {
        DefinedVariable var = variables.get(name);
        if (var != null) {
            return var;
        }
        else {
            return parent.get(name);
        }
    }

若是找不到就一直往父做用域上找,一直找到ToplevelScope的get:

    /*
     * 若是在ToplevelScope 經過查找entities 找不到變量的定義, 就會拋出
		SemanticException 異常,由於已經沒有更上層的做用域了。
     */
    /** Searches and gets entity searching scopes upto ToplevelScope. */
    // #@@range/get{
    public Entity get(String name) throws SemanticException {
        Entity ent = entities.get(name);
        if (ent == null) {
            throw new SemanticException("unresolved reference: " + name);
        }
        return ent;
    }

若是還找不到就拋出異常。因此整體來講,變量的引用是發現最近做用域的定義。

相關文章
相關標籤/搜索