"Digestion of variable references" means determining which variable to point to. For example, the variable "I" may be a global variable I, it may be a static variable I, or it may be a local variable i. This process is used to eliminate this uncertainty and determine which variable is referenced.
To eliminate this uncertainty, we need to correlate all the variables with their definitions, which are called "digestion of variable references". Specifically, the information for the definition of the variable (Variable object) is added for all Variablenode objects in the abstract syntax tree that represent reference variables.
Localresolver is a class used to process variable reference digestion, inheriting from visitor ("Digestion of variable references" and "static type Checking"). The entry method is:
/* Portal * generates a tree of scope objects with Toplevelscope as the root node, and associates all variablenode with their definitions. *///#@ @range/resolve{public void Resolve (AST ast) throws Semanticexception {/* * 1th part Toplevelscope Object , and then add the resulting Toplevelscope object with Scopestack.add (toplevel) to Scopestack. So there are 1 scope objects inside the stack */toplevelscope toplevel = new Toplevelscope (); Scopestack.add (toplevel); /* Add to variable definition. * Two foreach statements add global variables, functions, and types to Toplevelscope. * Both are called toplevelscope#declareentity to add a definition or declaration to the Toplevelscope object. * The 1th foreach statement adds the external variables and functions declared in the import file (*.HB) * The 2nd foreach statement is used to import variables and functions defined in the compiled file *//#@ @range/declaretop level{for (Entity decl:ast.declarations ()) {toplevel.declareentity (decl); } for (Entity ent:ast.definitions ()) {toplevel.defineentity (ENT); }//#@@}//#@ @range/resolverefs{resolvegvarinitializers (Ast.definedvariables ());//traverse global variable RE Solveconstantvalues (ast.cOnstants ());//The initialization expression of the traversal constant Resolvefunctions (ast.definedfunctions ());//The most important//#@@} Toplevel.checkrefer Ences (ErrorHandler); if (errorhandler.erroroccured ()) {throw new Semanticexception ("Compile failed."); }/* In the final section, the Toplevelscope objects and Constanttable objects generated in this class are saved to the AST object. These two objects are used when generating code, and are saved to the AST object in order to pass the information to the next stage. */Ast.setscope (toplevel); Ast.setconstanttable (constanttable); /* * This is the end of the argument reference's digestion process, which generates a tree of scope objects with Toplevelscope as the root node, and associates all variablenode with their definitions. */ }
Adding Toplevelscope,toplevelscope to the stack first represents the scope of the top level of the program, saving functions and global variables.
Then add various global variables and functions to the top-level scope.
Here the emphasis is resolvefunctions (ast.definedfunctions ()); This method, the digestion of all functions in the abstract syntax tree:
/ * * Processing of function definitions. * ///#@ @range/resolvefunctions{ private void Resolvefunctions (List<definedfunction> funcs) {for (Definedfunction Func:funcs) { //Call the Pushscope method, generate the scope containing the function parameters, and press the scope into the stack (scopestack) Pushscope (Func.parameters ()); Use the Resolve (Func.body ()) method to traverse the function's own syntax Tree resolve (Func.body ()); Call the PopScope method to eject the scope object that was just pressed into the stack, and add the scope object to the function with Func.setscope //// Func.setscope (PopScope ());} }
Traversing all function nodes, first press all the parameters in a single function node or the defined local variables into the scope of a temporary variable. Then press the scope of the temporary variable Localscope into the Scopestack:
The Pushscope method is the method of pressing the new Localscope object into the scope stack//#@ @range/pushscope{private void Pushscope (list<? extends Defined Variable> VARs) {//Generate Localscope object with CurrentScope () as parent scope//currentscope is the method to return the scope object at the top of the current stack Localscope SC Ope = new Localscope (CurrentScope ()); /* * Then add the variable VARs to the Localscope object using the foreach statement. That is, add the variables defined on this scope to the Localscope object. In particular, in the upper-level localscope of the function, you add the definition of the formal parameter. */for (definedvariable var:vars) {//Use the Scope.isdefinedlocally method first to check if a variable with the same name has already been defined if (scope.isdef Inedlocally (Var.name ())) {error (Var.location (), "duplicated variable in scope:" + var. Name ()); }//Then Add. Use the Definevariable method when adding a variable to a Localscope object (else {scope.definevariable (Var)); The resulting Localscope object is finally pushed to the top of the scope by calling Scopestack.addlast (scope). This can represent the nesting of scopes. */Scopestack.addlast (scope); }
Then resolve (Func.body ()) is the function body that dissolves the current function, that is, the Blocknode node, which, according to the polymorphic nature of Java, ultimately calls the following methods:
/ * Add a staging scope. * C language (c?) in the program block ({...} Block) will also introduce a new variable scope. * ///#@ @range/blocknode{public Void visit (blocknode node) {/ * * First Call pushscope method, Generates a scope object that stores the variables defined on this scope, and then presses into the scope stack. * /Pushscope (Node.variables ()); /* * Then execute super.visit (node), perform the processing defined in the base class visitor, i.e. traverse the code of the program block. Visit (variablenode node) * /super.visit (node); / * * Finally, the PopScope method pops up the scope object at the top of the stack, invoking the Setscope method of the Blocknode object to hold the scope object corresponding to the node. * /Node.setscope (PopScope ()); return null; }
Another Pushscope (Node.variables ()); Saves the list of temporary variable declarations in the function body in a localscope, and then presses the localscope into the scopestack stack. And then the key comes, super.visit (node), call the visit method in the visitor class to dissolve the local variables in the function body,
Public Void Visit (blocknode node) {for (definedvariable var:node.variables ()) { if (Var.hasinitializer ()) {
visitexpr (Var.initializer ()); } } Visitstmts (Node.stmts ()); return null; }
VISITSTMTS is key because the reference to the variable is saved in the Variablenode node and is eventually called:
/* * Establish associations for Variablenode and variable definitions. * Using the previous code has successfully generated a scope object tree, the following as long as the implementation of tree lookup and reference to digest the code can be. * ///#@ @range/variablenode{public Void visit (variablenode node) { try { //CurrentScope () first. Get the definition of a variable in the current scope Entity ent = CurrentScope (). Get (Node.name ()); /* * After the definition is obtained, the reference information of the definition is recorded by calling Ent.refered () so that it can be given a warning when the variable is not used. * /ent.refered (); /* * also use node.setentity (ENT) to save the definition to a variable node so that the definition of the variable can be obtained from the Variablenode at any time. Establish the association of VARIABLENODE and variable definitions */ node.setentity (ENT); } /* * If the definition of the variable cannot be found, CurrentScope (). Get throws a Semanticexception exception and then outputs it to the error message after it snaps. * /catch (Semanticexception ex) { Error (node, ex.getmessage ()); } return null; }
The first layer of currentscope () that is closest to the stack is looked up, and the association of VARIABLENODE and variable definitions is called setentity if found. The key here is this get method, which first calls Localscope's get:
/* Gets the variable definition from the scope tree. * Localscope#get is the method of getting variable definitions from the scope tree. * First Call variables.get in the symbol table to find a variable named name, if found to return the variable, if not found, then call the parent scope (parent) of the Get method to continue to find. If the parent scope is a Localscope object, the same method is called for recursive lookups. If the parent scope is toplevelscope, call 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); } }
If you can't find it, go straight to the parent scope and find the Toplevelscope get:
/ * If the definition of a variable is not found in toplevelscope by looking up entities, the semanticexception exception will be thrown because there is no upper scope. * //** searches and gets entity searching scopes upto toplevelscope. * //#@ @range/get{public entity ge T (String name) throws Semanticexception { Entity ent = entities.get (name); if (ent = = null) { throw new semanticexception ("Unresolved reference:" + name); } return ent; }
Throws an exception if it is not found. So, in general, a reference to a variable is the definition of the nearest scope that is found.
Compiler Development Series--ocelot Language 2. Digestion of variable references