編輯:關於android開發
“變量引用的消解”是指確定具體指向哪個變量。例如變量“i”可能是全局變量i,也可能是靜態變量i,還可能是局部變量i。通過這個過程來消除這樣的不確定性,確定所引用的到底是哪個變量。
為了消除這樣的不確定性,我們需要將所有的變量和它們的定義關聯起來,這樣的處理稱為“變量引用的消解”。具體來說,就是為抽象語法樹中所有表示引用變量的VariableNode 對象添加該變量的定義(Variable 對象)的信息。
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表示程序頂層的作用域,保存有函數和全局變量。
然後往這個頂層的作用域添加各種全局變量和函數。
這裡著重說下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方法來對函數體中的局部變量進行消解,
public Void visit(BlockNode node) { for (DefinedVariable var : node.variables()) { if (var.hasInitializer()) { visitExpr(var.initializer()); } } visitStmts(node.stmts()); return null; }
visitStmts是關鍵,因為變量的引用是保存在VariableNode節點中,最終會調用:
/* * 建立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; }
如果還找不到就拋出異常。所以總體來說,變量的引用是發現最近作用域的定義。
FFmpeg使用手冊 - FFmpeg 的基本組成1 FFmpeg 的基本組成FFmpeg 的基本組成包含Format、Codec、Filter、Devices、Util
基於WPF的窗體控件自適應思考WinForm要實現窗體、控件的自適應還是比較困難的。不是說不行,單靠算比較因子和坐標的辦法是解決不了根本問題的。 得轉變思路,考慮用WPF
PostgreSQL 9.5新特性:IMPORT FOREIGN SCHEMA本次以故事的形式講述一名不緊跟時代步伐、不主動積極學習的員工在不了解最新特性的情況下,如何失
Android自定義對話框,android自定義在android中有自帶的對話框,為了美觀,很多開發者會使用自定義對話框,如下圖: 點擊“彈出自定義對話框按