TRex Symbol Table Documentation
Location
The whole symbol table implementation is located in the plug-in de.ugoe.cs.swe.trex.core in the packages:
de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable
de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable.importconfiguration
Description
This document contains usage instructions for the TRex symbol table. The term "symbol table" essentially describes a data structure which stores information about any non-keyword identifier within a source code. Such information is, for example, the type of an identifier or its location. TRex implements a symbol table as well which is used heavily for the features available. A particulary easy example for the symbol table usage in TRex is the "Open Declaration" functionality. Whenever this feature is selected from the context menu (or through the F3 key command), a symbol table lookup is performed. The obtained information contains the location of the identifiers (i.e. the symbols) declaration. Subsequently, a few Eclipse commands are invoked which take care of opening the correct file in the editor and jumping to the concrete line number of the declaration.
In TRex, the symbol tables are linked to scopes. Scopes are visibility units within the source code. For example, each module contains a module scope and each function contains a function scope. The module scope is visibile to each function within the module, but the scope of a function A (and any symbols declared locally in this function) is not visible to another function B within the same module. These visibility rules are defined in the TTCN-3 standard. The following picture shows such a scope partitioning of a TTCN-3 module.
The function myFunc, for example, can access all symbols declared within the module ExampleModule, but it cannot, however, directly access parts of ExampleType like ipv6. The scopes in TRex are organized in a tree shown in the following picture.
Each identifier node in the syntax tree is linked to its scope. Hence, to obtain a scope, the method getSurroundingScope() and getScope() on a LocationAST object can be used. In the special case of function, testcases, altsteps etc. there is a difference between getSurroundingScope() and getScope(). While getSurroundingScope() returns the scope that surrounds the symbol, the getScope() methods returns the scope that is introduced by the function/testcase/altstep name identifier, for example. Each scope object contains several functions called "resolve". Currently, only the function with the signature "public Symbol resolve(LocationAST node)" should be used. Essentially, the resolve method inspects the scope's symbol table for matching symbols. If unsuccessful, the resolve method of the parent scope is called (the actual implementation is more complex than that though, but this should only concern the symbol table maintainer). The following UML class diagram illustrates how the different classes work together.
The class diagram shows how the symbol table is part of a scope. In addition, each scope contains an import configuration. However, this is generally not of interest for the symbol table user as it is automatically respected by the resolve methods.
The result of a "resolve" call is a Symbol which is an abstract class. It implements the ITTCN3EclipseSymbol interface which is used for displaying symbol information (used in the text hover for example). Its concrete representation depends on the symbol. For function names, the symbol is an instatiation of FunctionSymbol. The available symbol table classes can be found in the package de.ugoe.cs.swe.trex.core.analyzer.rfparser.symboltable. To access the concrete symbol information, it can be casted to its type.
Finding References
Another practical usage example for the symbol table quite often used is finding references. For various static code analyses, it is necessary to find every reference of an identifier. To achieve this, the declaration location of the identifier must be found first (again: do not try to use getSurroundingScope() or getScope() on a non-identifier LocationAST object. It won't return anything useful!). In order to achieve this, the scope of an identifier is obtained first by using the getSurroundingScope() method on the LocationAST object. In the next step, the resolve method is invoked on this scope using the LocationAST object as actual parameter which was just used for getting the scope. The result should be the symbol of this identifier which contains type information, but also the declaration node (again a LocationAST object). This information is used by a reference finder algorithm which is already implemented in the class ReferenceFinder of package de.ugoe.cs.swe.trex.core.analyzer.astutil. Its "findReferences" methods can be used to find the references:
public Map<String, List<ReferenceWithContext>> findReferences(Symbol s) public Map<String, List<ReferenceWithContext>> findReferences(List<Symbol> symbols)
Basically, the symbol is simply passed as actual parameter to the findReferences method which scans all subscopes of the declaration scope where the concerned symbol is visible (respecting also import configurations).
The following code snippet demonstrates how to find all references from an IDENTIFIER node (of type LocationAST) in the syntax tree:
ReferenceFinder referenceFinder = new ReferenceFinder(); Scope scope = identifierNode.getScope(); Symbol symb = scope.resolve(identifierNode); Map<String, List<ReferenceWithContext>> result = referenceFinder.findReferences(symb);
The resulting map is storing the module name as string and the reference results in a list of the ReferenceWithContext type.
Working with Scopes
As mentioned in the previous section, there are two methods on an LocationAST object working with scopes: getScope() and getSurroundingScope(). The difference between those two methods is not exactly intuitive without an explanation. First, it should be noted that not every single LocationAST object knows its scope. This is due to the nature of how the parser was written and there is currently no easy way to change this. However, this is not too bad, because this "feature" should not be needed in practice.
getScope() and getSurroundingScope() deliver differing results when the corresponding IDENTIFIER node introduces a new scope, e.g. the IDENTIFIER node of a FunctionDef (Subtree FunctionDef/Identifier/IDENTIFIER) returns the introduces scope by the function, i.e. the function scope, while getSurroundingScope() returns the module scope, i.e. the scope to which the identifier technically belongs. This can be confusing at first, but this differentiation is really useful in practice.



