Course Exercises

Strategic Programming - IPA Basic Course on Software Technology 2006

Getting Started

To start a proper shell and initialize your path, run the following commands. (guest16 is on purpose. If you are a different guest, don't change the guest number to your own account).

$ bash
$ source /home/guest16/stratego-env.sh

Don't forget to run these commands again if you open a new shell.

Download the file ipa06-templates.tar.gz to your home directory, create a working directory for yourself, and unpack the file:

$ mkdir <yourname>
$ cd <yourname>
$ tar zxvf ~/ipa06-templates.tar.gz

The tarball contains a very small Stratego program to test the Stratego/XT installation. If the following commands do not succeed, then please scream for help.

$ cd welcome
$ strc -i welcome.str -la stratego-lib
$ ./welcome
Great, Stratego/XT works!
$ cd ..

For experimenting with the Stratego language the Stratego Shell can be useful. A small session that you can try to see if it works.:

$ stratego-shell
stratego> import libstratego-lib
stratego> ![1, 2, 3]
[1,2,3]
stratego> fetch(?x)
[1,2,3]
stratego> :binding x
x is bound to 1
stratego> :quit
$ 

Java Tools

(work in directory tools)

In these exercises we will use the Java support packages of Stratego/XT. The tool parse-java is used to parse Java programs to an abstract syntax tree represented as an ATerm.

$ echo "class Foo {}" | parse-java | pp-aterm
CompilationUnit(
  None()
, []
, [ ClassDec(
      ClassDecHead([], Id("Foo"), None(), None(), None())
    , ClassBody([])
    )
  ]
)

For parsing simple expressions, you don't have to create an entire class:

$ echo "1+2" | parse-java -s Expr | pp-aterm
Plus(Lit(Deci("1")), Lit(Deci("2"))

You can also put the class in a file, which is useful if you are going to try bigger examples. For files, use parse-java -i Foo.java.

Java-front also features a pretty-printer for Java. You can apply this pretty-printer in a pipeline with the parser:

$ parse-java -i HelloWorld.java | pp-java

Compile a simple Java source file to a .class file and decompile it using the tool class2aterm:

$ javac HelloWorld.java
$ class2aterm -i HelloWorld.class | pp-aterm

Take a brief look at the structure of this aterm. This invocation does not disassemble the code. For this, you have to pass -c as an argument to class2aterm:

$ class2aterm -i HelloWorld.class -c | pp-aterm

As you can see, the output is quite big. If you want to scroll through the output, then you can add less to the pipeline. You can exit less and return to the command-line using q.

$ class2aterm -i HelloWorld.class -c | pp-aterm | less

Rewriting strategies

Introduce Braces for Control-flow Statements

(work in directory introduce-braces)

Implement a set of conditional rewrite rules that introduces blocks in the branches of control-flow statements, but only if the blocks are missing. Cover a few different control-flow statements. Use the template introduce-braces.str.

Compile and apply:

$ cd introduce-braces
$ ./compile.sh
$ parse-java -i Sample1.java | ./introduce-braces | pp-java
public class Sample1
{
  int foo()
  {
    if(true)
    {
      return 1;
    }
    else
    {
      return 2;
    }
  }
}

$ parse-java -i Sample2.java | ./introduce-braces | pp-java
public class Sample2
{
  void foo()
  {
    while(true)
    {
      doWhatever();
    }
    oops();
  }
}

Warn for Return Statements in Finally Clause

(work in directory return-finally)

Note: If the previous exercises took you more than an hour, then you might want to skip to the next exercises on type-unifying strategies.

A return statement in a finally clause should be avoided, because this return statement can completely change the original result of the method: a returned value or a thrown exception in the try part. This is not the purpose of a finally clause, therefore compilers often warn about this:

class ReturnFinally
{
  boolean error;

  int g()
  {
    if(error)
      throw new IllegalStateException();
    else
      return 4;
  }

  int f()
  {
    try
    {
      return g();
    }
    finally
    {
      return 5;
    }
  }
}

$ javac ReturnFinally.java -Xlint
ReturnFinally.java:22: warning: [finally] finally clause cannot complete normally
    }
    ^
1 warning

For this exercise, write an transformation that inserts a comment before return statements in a finally clause. This is not entirely trivial. Thanks to local and anonymous class declarations, not every return statement in a finally clause is incorrect. So, you need to handle these constructs in a special way.

It is useful to think about this transformation as format checking. If a Java source file does not use return statements in finally clauses, then the program can be described as being written in two different languages. First, in a finally clause the used language is a subset of the complete Java language: no return statements are allowed. Second, outside finally clauses, the full Java language is used. However, for local and anonymous class declarations in finally clauses the full language is supported as well.

A useful pattern for the implementation of these problems is to have two strategies, one for the complete language and one for the subset. In both strategies, specific cases are handled and maybe the context is switched to the other strategy. By default, they just traverse.

You need about 10 lines of code for this, so if your strategy is much longer, then you're doing something wrong ;)

Compile and apply:

$ cd return-finally
$ ./compile.sh
$ parse-java -i ReturnFinally.java | ./return-finally | pp-java
class ReturnFinally
{
  boolean error;

  int g()
  {
    if(error)
      throw new IllegalStateException();
    else
      return 4;
  }

  int f()
  {
    try
    {
      return g();
    }
    finally
    {
      // WARNING: return in finally
      return 5;
    }
  }
}

ReturnFinallyTricky.java is an example with a local class declaration.

Type-unifying strategies

Collect Literals

(work in directory collect-literals)

Note: If you have less than 45 minutes left, then it is a good idea to skip this exercise and only do the bytecode collect exercise.

Collect all literals (booleans, string, integers, etc) used in a Java source file and return them as a list. Use the template collect-literals.str.

Compile and apply:

$ cd collect-literals
$ ./compile.sh
$ parse-java -i SimpleProxyServer.java | ./collect-literals | pp-aterm
[ Deci("3")
, String([Chars("Wrong number of arguments.")])
, Deci("2")
, String([Chars("Starting proxy for ")])
, String([Chars(" on port ")])
, String([Chars("Usage: java SimpleProxyServer ")])
, String([Chars("<host> <remoteport> <localport>")])
, Deci("1024")
, Deci("4096")
, Bool(True())
, String([Chars("Proxy server cannot connect to ")])
, String([Chars(":")])
, String([Chars(":"), NamedEscape(110)])
, Deci("1")
, Deci("0")
, Null()
]

Collect Classes from Bytecode

(work in directory collect-classes)

Given a Java .class file, return the classes that are referred to using constructor calls (NEW), field accesses or method invocations. Use the template collect-classes.str.

Compile and apply:

$ cd collect-classes
$ ./compile.sh
$ javac YesNoDialog.java 

$ class2aterm -c -i YesNoDialog.class | ./collect-classes | pp-aterm
[ "java.awt.Dialog"
, "java.awt.BorderLayout"
, "MultiLineLabel"
, "YesNoDialog$1"
, "java.awt.FlowLayout"
, "java.awt.Button"
, "java.awt.Panel"
, "java.awt.AWTEventMulticaster"
, "java.awt.Frame"
, "YesNoDialog$2"
, "YesNoDialog"
]

$ class2aterm -c -i MultiLineLabel.class | ./collect-classes | pp-aterm
[ "java.awt.Canvas"
, "java.awt.Dimension"
, "java.awt.Graphics"
, "java.util.StringTokenizer"
, "java.awt.Toolkit"
, "java.awt.FontMetrics"
, "MultiLineLabel"
]

Note that is very easy to collect other information, for example constructors invoked from this class, methods that are invoked, fields that are accessed, etc.

Context-sensitive transformations

(work in the directory trace-instrument)

Implement a simple trace instrumentation tool that prints a message when a program enters and exits a method. Include the fully qualified name of the declaring class of the method in the message, wich you can propagate during the traversal using a scoped dynamic rule. Support member classes.

Compile and apply:

$ cd trace-instrument
$ ./compile.sh

$ parse-java -i Factorial2.java | ./trace-instrument  | pp-java -o Factorial.java
$ cat Factorial.java
public class Factorial
{
  public static long factorial(long x)
  {
    try
    {
      System.out.println("Enter Factorial.factorial");
      if(x == 1)
        return 1;
      else
        return x * factorial(x - 1);
    }
    finally
    {
      System.out.println("Exit Factorial.factorial");
    }
  }

  public static void main(String[] ps)
  {
    try
    {
      System.out.println("Enter Factorial.main");
      System.out.println("Factorial of 4 is " + factorial(4));
    }
    finally
    {
      System.out.println("Exit Factorial.main");
    }
  }
}

$ javac Factorial.java
$ java Factorial
Enter Factorial.main
Enter Factorial.factorial
Enter Factorial.factorial
Enter Factorial.factorial
Enter Factorial.factorial
Exit Factorial.factorial
Exit Factorial.factorial
Exit Factorial.factorial
Exit Factorial.factorial
Factorial of 4 is 24
Exit Factorial.main

fact/Tricky2.java is a bit more difficult:

$ parse-java -i fact/Tricky2.java | ./trace-instrument | pp-java -o fact/Tricky.java
$ javac fact/Tricky.java
$ java fact.Tricky
Enter fact.Tricky.main
Enter fact.Tricky.Factorial.apply
Enter fact.Tricky.Factorial.apply
Enter fact.Tricky.Factorial.apply
Enter fact.Tricky.Factorial.apply
Exit fact.Tricky.Factorial.apply
Exit fact.Tricky.Factorial.apply
Exit fact.Tricky.Factorial.apply
Exit fact.Tricky.Factorial.apply
Factorial of 4 is 24
Enter fact.Tricky.Multiply.apply
Exit fact.Tricky.Multiply.apply
4*4 is 16
Exit fact.Tricky.main