JTB 
JAVA TREEBUILDER
TUTORIAL
JTB Home 

What's New 

Release Notes 

Download 

Documentation 

Examples 

Tutorial 

  • Introduction
  • Preliminaries
  • Calling Visitors
  • Programming Visitors

  •  

    Why Visitors? 

    Other Tools 

    User Comments 



    Special Edition for GJ 



    Links: 
  • JavaCC
  • Design Patterns
  • Javasoft
  • GJ
  •  
    Jump to Java!

    Introduction

    This file is a basic guided tour to using JTB.  We will run through the process of using JTB to generate the files needed to build a syntax tree.  We'll also write a visitor to pretty print the contents of the syntax tree.  The grammar we'll use for our example will be the Java1.1.jj grammar included with the JavaCC 0.6.1 distribution. 

    You may want to take a look at the pretty printer example in the Examples page.  This tutorial is basically a run-through of that example.  We also recommend that you read the Documentation page to familiarize yourself with the generated files. 
     

    Preliminaries

    Before we start, you should know your grammar should be "bare-bones", i.e. it should not contain embedded Java code and all the productions should have a return type of void.  As an example, see the Java 1.1 grammar.  It is just a language grammar and contains no added functionality beyond simply parsing a Java file. 

    Now to the tour.  As mentioned above, the grammar file we'll be using is Java1.1.jj.  This diagram illustrates the process of using JTB: 

    Usage diagram

    The first few steps are fairly obvious. 

    • Run JTB on the grammar.  On Unix systems, this would be done with the following command:
      •  
        % jtb Java1.1.jj

      This will generate the syntaxtree directory which contains your tree node classes, the visitor directory which contains Visitor interface, DepthFirstVisitor class, ObjectVisitor interface and ObjectDepthFirst class, and jtb.out.jj, the grammar file with code inserted to build the syntax tree. 

    • Run JavaCC on the annotated grammar file to generate the parser.
    If your grammar remains stable, that is really all you need to do with JTB.  The rest of the process involves programming the generated tree.  The following codes and demonstration are based on Visitor and DepthFirstVisitor.  For specifications of ObjectVisitor and ObjectDepthFirst, please refer to Documentation page. 
     

    Calling the Parser and Visitors

    Your grammar will need a main() method to call the parser and visitors.  The JavaCC examples usually place this method in the grammar file whereas I prefer to create a separate Main class in a different file.  This allows you to make changes to main() without having to continuously reprocess the grammar file with JTB and JavaCC when you want to recompile it.  A simple main() method could look like this: 
    import syntaxtree.*;
    import visitor.*;
    
    public class Main {
       public static void main(String args[]) {
          JavaParser parser = new JavaParser(System.in);
    
          try {
             Node root = parser.CompilationUnit();
             System.err.println("Java program parsed successfully.");
             root.accept(new DepthFirstVisitor());
          }
          catch (ParseException e) {
             System.err.println("Encountered errors during parse.");
          }
       }
    }
    Notice that we must import the syntaxtree and visitor packages.  We call parser.CompilationUnit() to start the parsing of the input.  JTB will alter this method to return a reference to a CompilationUnit object (which implements Node).  We store this reference in the variable root.  Next we call the accept() method of the root Node, creating a new DepthFirstVisitor object and passing it as an argument.  Since DepthFirstVisitor implements Visitor interface and its methods by default visit each node of the tree, this program will parse a Java program from standard input, build a tree during parsing, and then visit each node in the tree. 

    The main() method in the actual pretty printer example is a little more complex, but this is only so it can read input from files if an argument is provided on the command line. 
     

    Programming a Visitor

    Now all we have left to do is write a visitor.  Typcally, your visitors should go in the visitor directory and package.  If you take a look at the automatically generated DepthFirstVisitor class, you'll notice it has handy comments above each method indicating which fields refer to which portions of the production.  While writing the pretty printer I found the comments so handy, in fact, that I just copied the whole class over and replaced the name DepthFirstVisitor with PrettyPrinter and rewrote the methods that needed to be rewritten. 

    However, if your visitor is small, maybe one or two methods, writing it from scratch works just as well.  Remember that your visitor must implement the interface Visitor or ObjectVisitor directly or indirectly. 

    As a simpler example of a visit method in the pretty printer, here is the method which prints out break statements: 

         //
         // f0 -> "break"
         // f1 -> [ < IDENTIFIER >  ]
         // f2 -> ";"
         //
         public void visit(BreakStatement n) {
            n.f0.accept(this);
      
            if ( n.f1.present() )
                n.f1.choice.accept(this);
      
            n.f2.accept(this);
         }
    The visit(NodeToken n) method (which is called by n.f0.accept() and n.f2.accept()) has been overridden to simply print out the token.  n.f1.choice.accept() will also invoke the visit(NodeToken n) method because of the way the production rule is.  It is probably easy to figure out what this method does. 

    After the visitor has been completed, all that remains to be done is to compile the code and run it!   Again, you are encouraged to take a good look at the source code of the pretty printer example available for download. 
     


    Maintained by Wanjun Wang, wanjun@purdue.edu.
    Created September 14, 1997. 
    Last modified May 5, 2000.