| # The AST |
| |
| An AST (abstract syntax tree) is a representation of the syntactic (or lexical) |
| structure of Dart code. The semantic structure of the code is modeled by the |
| [element][element] and [type][type] models. |
| |
| ## The Structure of an AST |
| |
| An AST is composed of nodes (instances of `AstNode`) arranged as a tree. That |
| is, each node can have zero or more children, each of which is also a node, and |
| every node other than the root node has exactly one parent. |
| |
| The root of the tree is typically a `CompilationUnit`. Compilation units have |
| children representing language constructs such as import directives and class |
| declarations. Class declarations have children representing, among other things, |
| each of the members of the class. The structure of the nodes is similar to, but |
| not identical to, the Dart language grammar. |
| |
| As implied above, there are subclasses of `AstNode` for each production in the |
| grammar. The subclasses are defined in `package:analyzer/dart/ast/ast.dart`. |
| |
| Every class of node provides access to its parent and children through getters. |
| For example, the class `BinaryExpression` defines the getters `parent`, |
| `leftOperand`, and `rightOperand`. It also provides getters for the tokens that |
| are a part of the construct (but not part of a child construct). In a binary |
| expression, for example, there is a getter to access the `operator`. |
| |
| Every class of node and every token carries position information. You can ask |
| for the character `offset` of the beginning of the entity from the start of the |
| containing file, as well as the character `length`. For AST nodes, the offset |
| is the offset of this first token in the structure and the length includes the |
| end of the last token in the structure. Any whitespace before the first token or |
| after the last token is considered to be part of a parent node. |
| |
| ## The States of an AST |
| |
| An AST can be in either of two states: unresolved or resolved. An unresolved |
| AST is one in which none of the nodes has any resolution information associated |
| with it. In an unresolved AST, the getters that access resolution information |
| will return `null`. A resolved AST is one in which all of the nodes have |
| resolution information associated with them. |
| |
| So what do we mean by "resolution information"? Resolution is the process of |
| associating [element][element] and [type][type] information with an AST. These |
| topics are discussed in separate sections. |
| |
| ## Getting a Compilation Unit |
| |
| If you have followed the steps in [Performing Analysis][analysis], and you want |
| to get the compilation unit for a file at a known `path`, then you can ask the |
| analysis session for an AST. |
| |
| If you need an unresolved AST, then you can use the following method to access |
| the AST: |
| |
| ```dart |
| processFile(AnalysisSession session, String path) { |
| ParsedUnitResult result = session.getParsedUnit(path); |
| CompilationUnit unit = result.unit; |
| } |
| ``` |
| |
| If you need a resolved AST, then you need to use the following asynchronous |
| method to access it: |
| |
| ```dart |
| processFile(AnalysisSession session, String path) async { |
| ResolvedUnitResult result = await session.getResolvedUnit(path); |
| CompilationUnit unit = result.unit; |
| } |
| ``` |
| |
| ## Traversing the Structure |
| |
| There are two ways to traverse the structure of an AST: getters and visitors. |
| |
| ### Getters |
| |
| Every node defines getters for accessing the parent and the children of that |
| node. Those getters can be used to traverse the structure, and are often the |
| most efficient way of doing so. For example, if you wanted to write a utility to |
| print the names of all of the members of each class in a given compilation unit, |
| it might look something like this: |
| |
| ```dart |
| void printMembers(CompilationUnit unit) { |
| for (CompilationUnitMember unitMember in unit.declarations) { |
| if (unitMember is ClassDeclaration) { |
| print(unitMember.name.name); |
| for (ClassMember classMember in unitMember.members) { |
| if (classMember is MethodDeclaration) { |
| print(' ${classMember.name}'); |
| } else if (classMember is FieldDeclaration) { |
| for (VariableDeclaration field in classMember.fields.variables) { |
| print(' ${field.name.name}'); |
| } |
| } else if (classMember is ConstructorDeclaration) { |
| if (classMember.name == null) { |
| print(' ${unitMember.name.name}'); |
| } else { |
| print(' ${unitMember.name.name}.${classMember.name.name}'); |
| } |
| } |
| } |
| } |
| } |
| } |
| ``` |
| |
| ### Visitors |
| |
| Getters work well for cases like the above because compilation units cannot be |
| nested inside other compilation units, classes cannot be nested inside other |
| classes, etc. But when you're dealing with a structure that can be nested inside |
| similar structures (such as expressions, statements, and even functions), then |
| nested loops don't work very well. For those cases, the analyzer package |
| provides a visitor pattern. |
| |
| There is a single visitor API, defined by the abstract class `AstVisitor`. It |
| defines a separate visit method for each class of AST node. For example, the |
| method `visitClassDeclaration` is used to visit a `ClassDeclaration`. If you |
| ask an AST node to accept a visitor, it will invoke the corresponding method on |
| the visitor interface. |
| |
| If you want to define a visitor, you would create a subclass of one of the |
| concrete implementations of `AstVisitor`. The concrete subclasses are defined in |
| `package:analyzer/dart/ast/visitor.dart`. A couple of the most useful include |
| - `SimpleAstVisitor` which implements every visit method by doing nothing, |
| - `RecursiveAstVisitor` which will cause every node in a structure to be |
| visited, and |
| - `GeneralizingAstVisitor` which makes it easy to visit general kinds of nodes, |
| such as visiting any statement, or any expression. |
| |
| As an example, let's assume you want to write some code to count the number of |
| `if` statements in a given structure. You need to visit every node, because you |
| can't know ahead of time where the `if` statements will be located, but there is |
| one specific class of node that you need to visit, so you don't need to handle |
| the general "groups" of nodes. The best approach for this example is to create a |
| subclass of `RecursiveAstVisitor`. |
| |
| ```dart |
| class IfCounter extends RecursiveAstVisitor<void> { |
| int ifCount = 0; |
| |
| @override |
| void visitIfStatement(IfStatement node) { |
| ifCount++; |
| super.visitIfStatement(node); |
| } |
| } |
| ``` |
| |
| ## Differences From the Specification |
| |
| Earlier we said that the structure of the tree is similar but not identical to |
| the grammar of the language. In addition to some minor differences, there is |
| one significant difference you should be aware of: the AST can express invalid |
| code. This is intentional. It allows the analyzer to recover better in the |
| presence of invalid code. |
| |
| As an example, every function has a (possibly empty) list of parameters |
| associated with it. In Dart, parameters can either be positional or named, and |
| all of the positional parameters must be listed before the named parameters. But |
| in the AST, the parameters are allowed to occur in any order. The consequence of |
| this is that any code that traverses function parameters needs to be prepared |
| for them to occur in any order. |
| |
| [analysis]: analysis.md |
| [element]: element.md |
| [type]: type.md |