Computer Science / Software Engineering Notes Network

Automated Code Generation

Matthew Barnes

Introduction        3

History of Software Reuse        3

Our solution        5

Concepts in Code Generation        6

Code Generator Structure        7

Code Generation Approaches        8

Transformations        9

Domain Modelling        11

C Macros        14

Macros        14

Macros in Code Generation        14

Macro Operators        15

Coding Idioms with Macros        15

Macro Hygiene        16

Why Syntax Macros Suck        16

Hygienic Macros        18

C++ Templates        21

Templates in C++        21

Template Parameters        23

Templates are Syntactic Macros        24

Why C++ Templates Suck        25

Templates Specialisation in C++        25

Template Metaprogramming        30

IF<>        30

WHILE<>        31

Loop Unrolling        32

ADTs        35

Expression Templates        37

Aspect Oriented Programming with AspectJ        38

Aspect Oriented Programming Concepts        38

Introduction to AspectJ        41

Call vs Execution        43

State-Based Pointcuts        43

Class JoinPoint        45

Advice Placement        45

CFlow        46

Inter-Type Declarations        47

Why AspectJ Sucks        48

Quote-Based Metaprogramming        48

Entry Points        50

Using ` and #        51

Using infer        52

Unquote Splice        52

Convenience Methods        53

Why Meta-AspectJ Sucks        55

Transformative Code Generation        55

Let’s Get Meta!        56

Listeners        60

Software Sec Example        64

Visitors        68

Binary Tree Example        69

Other Features        74

Alternative Labels        74

Rule Element Labels        74

Actions        75

Node Values (Attributes)        75

TokenStreamRewriter        76

Why ANTLR Sucks        78

Introduction

History of Software Reuse

Example: Why Libraries Suck

  • Let’s give a little example.
  • We have a library for matrix arithmetic, and we want to do this operation:
  • C = (A+B)T
  • Using this library, we could implement it this way:
  • add(A, B, M)
  • transpose(M, C)
  • Substituting in the function bodies, our code would really be:
  • for i in A.rows
  • for j in A.cols
  • M[i,j] = A[i,j] + B[i,j]
  • for i in M.rows
  • for j in M.cols
  • C[j,i] = M[i,j]
  • That looks fine, right?
  • Hmm... it could be better.
  • We could optimise this into one loop:
  • for i in A.rows
  • for j in A.cols
  • C[j,i] = A[i,j] + B[i,j]
  • ... but because our library is split into different operations, we can’t do it that way.

Automated Code Generation: “Everything you love sucks.”

(By the way, I was joking about everything sucking.

You can continue to use libraries and such if you wish.)

Our solution

Um... that’s not quite what I meant.

Concepts in Code Generation

Code Generator Structure

Code Generation Approaches

Generative or code-based

  • Assemble code from fragments
  • Macros, C++ templates, aspects
  • Template engines (JET, Velocity, ...)

Transformative or model-based

  • Refine model into code
  • MDA / MBSE / ...

Deductive or proof-based

  • Logically deduce code from specification
  • Amphion, KIDS / PlanWare/ SpecWare, ...

Transformations

Compositional

  • Compositional generators only apply vertical transformations.
  • It was introduced by Batory, and is typical for CASE tools; models built with GUIs.
  • They preserve structure, because semantics do not change. This way, generated code can be traced back to the initial model.

Holistic

  • Holistic generators apply both vertical and horizontal transformations.
  • In other words, “whole system” transformations, capable of:
  • Optimisation
  • Refactoring
  • Weaving

  • However, some transformations are a combination of both vertical and horizontal transformations, called oblique transformations.
  • These transformations can bridge bigger semantic gaps.

Example: Oblique Transformations

  • If you want an example of an oblique transformation, think of the matrix add + transpose example we had in the introduction, but instead, our “model” looks like this:

  • M = A + B
  • C = MT

  • We could take a horizontal step to optimise the model:

  • C = (A+B)T

  • Then, a vertical step to implement the model:

  • for i in A.rows
  • for j in A.cols
  • C[j,i] = A[i,j] + B[i,j]

  • This can all be done in a single oblique transformation!

I said Holistic, not Heuristic!

Domain Modelling

“Good domains (for code generation) have good domain models!”

- Julian Rathke

Domain experts

The red circled bits are solution concepts and variables + types inside the UML model.

C Macros

Macros

#define PI 3.14159

#define ADD(x,y) x + y

#define AREA(r) PI * r

Macros in Code Generation

Macro

Usage

#define SWAP(T,x,y) {T t=x;x=y;y=t;}

int lo = 10;
int hi = 20;
SWAP(
int,lo,hi);
 {int t=lo;lo=hi;hi=t;}

Macro

Usage

#define EXECUTE(s) s;

int a = 1, b = 2; c;
EXECUTE(
c = a + b);
→ c = a + b;

Macro Operators

Macro

Usage

#define VAR_NAME(x) #x

char* varName = VAR_NAME(foo);
→ char* varName = “foo”;

Macro

Usage

#define AS_CHAR(x) #@x

char myChar = AS_CHAR(f);
→ char myChar = ‘f’;

Macro

Usage

#define MAKE_VAR(T,id,val) T var##id = val

#define MAKE_VAR_NAME(T,name,id,val) T name##id = val

MAKE_VAR(char,56,10);
→ char var56 = 10;

MAKE_VAR(int,jojo,3,8);
→ int jojo3 = 8;

Coding Idioms with Macros

Macro

Usage

#define FOREACH(arr,len,i,st) { \

  int i = 0; \

  for (i = 0; i < len; i++) { \

    st; \

  } \

}

int arr[10] = { ... };

/* Sets all elements to 0 */
FOREACH(arr,
10,i,arr[i] = 0);

→ { int i = 0; for (i = 0; i < 10; i++) { arr[i] = arr[i] + 1; } }

Macro Hygiene

Why Syntax Macros Suck

Problem

Example + Solution

Operator priorities can interfere with complex macro arguments and bodies

Example

#define DEGTORAD(x) x*PI/180

DEGTORAD(a-b)

→ a-b*3.1415/180

It’s going to do

a-(b*3.1415/180)

When really we wanted

(a-b)*3.1415/180

Solution

Bracket the macro arguments, and add outer brackets, so calls like 1/DEGTORAD(a) will work as well:

#define DEGTORAD(x) ((x)*PI/180)

Macro body can interfere with surrounding syntax

Example

#define SWAP(x,y) t=x;x=y;y=t

if (a>b) SWAP(a,b);

→ if (a>b) t=a;a=b;b=t;

The part that says

t=x;

( t=a; in the expanded code)

terminates the if statement, so the rest of the macro, namely

x=y;y=t;

( a=b;b=t; in the expanded code)

will not be included in the if clause.

Solution

Proper “packaging” of the macro body:

#define SWAP(x,y) {t=x;x=y;y=t}

Side effects in macro arguments can be duplicated

Example

#define MIN(x,y) (((x)<(y)) ? (x) : (y))

int a = 1, b = 2;

int x = MIN(++a, ++b)

→ int x = (((++a)<(++b)) ? (++a) : (++b))

In the macro expansion, ++a or ++b are executed twice (depending on what is returned), leading to unwanted values:

a == 3, b == 3, x == 3

Solution

Use local temporary variables

#define MIN(x,y) \

  ({ typeof (x) _x = (x); \

     typeof (y) _y = (y); \

     _x < _y ? _x : _y; })

However, this is not ANSI-C; in other words, this does not conform to the C standard set by ANSI and ISO.

Variable declarations in body can capture arguments

Example

#define SWAP(T,x,y) { T t=x;x=y;y=t; }

int t = 0, s = 1;

SWAP(int,t,s);

→ { int t=t;t=s;s=t; };

The code:

int t = t;

t = s;

s = t;

does not swap t and s, because the instance of t outside the block is not changed.

Solution

Obfuscate local variables

#define SWAP(T,x,y) { T _t_SWAP=x;x=y;y=_t_SWAP; }

This isn’t foolproof; it just reduces the chance of this problem arising.

Hygienic Macros

Wash your macros before you use them.

Example: Matt’s Teach Yourself Scheme in 3 Minutes

  • Let’s create a macro in Scheme.
  • Why?
  • Because they make one in the slides, and I’m scared that a Scheme question might come up in the exam (probably not, but covering this puts my mind at ease).
  • If you wanna check Scheme out, I suggest you pick up a Scheme compiler (I used racket) and check out this site.

(define-syntax push
 (
syntax-rules ()
   ((
push v s)
     (
set! s (cons v s)))))

  • That’s a lot of brackets. So what does this do?

  • In layman’s terms, this defines a push macro that takes in a value and a list, and appends the value to the list.

  • Here’s how it works in more detail:
  • The define-syntax keyword creates a macro called push.
  • The syntax-rules block means this macro uses pattern-matching to find instances of where this macro is used, before replacing it with the macro body.
  • The (push v s) part means that if there is a call to push somewhere in our code, like:
  • (push “item” my_list)
  • ... we should replace it with this set! call, where “item” is the parameter v and my_list is the parameter s.
  • The set! call sets a value to a variable, and cons is like the list constructor from Haskell.

  • If you want to know more about this Scheme syntax, I suggest checking out the site linked above. It’s quite short.

  • Alright, so what? We have a macro in Scheme, now what?
  • If we tried to define a list called cons and tried to use that as an argument to our macro, we’d end up getting two cons’.

(define cons (list 1 2 3)) ; defines a list called 'cons' with (1 2 3) in it
(
push 4 cons)

→ (set! cons (cons 4 cons))

  • Oof! You see that? “cons 4 cons”. That’s not good.
  • To guarantee referential integrity, we make these terms refer to the right objects.
  • For example, when we want to use the ‘cons’ keyword to refer to our newly created list, it should point to the list, and not to the keyword for list construction.
  • One way we can do this is to timestamp names with nesting levels:

(define-syntax push
 (
syntax-rules ()
   ((
push v s)
     (
set! s (cons:0 v s)))))

(
define cons:1 (list 1 2 3)) ; defines a list called 'cons:1' with (1 2 3) in it
(
push 4 cons:1)
→ (set! cons:1 (cons:0 4 cons:1))

  • Boom! Our macro is now super cleanly.

C++ Templates

Templates in C++

class Stack {
 
std::vector<int> rep;
public:
 
void push(const int& t);
 
int pop();
 
bool is_empty();
};

template <class T>
class Stack {
 
std::vector<T> rep;
public:
 
void push(const T& t);
 
T pop();
 
bool is_empty();
};

Stack<int> intStack;
Stack<char> charStack;
Stack<🥞> pancakeStack;

With templates, you can make one of these in C++.

template <class T>
T min(const T& a, const T& b) {
 
if (a < b) return a; else return b;
}

Template Parameters

template <class T, int Max>
class Stack {
 ...
public:
 ...
 
bool is_full() { return size==Max }
};

template <template <class T> class TT>
class IntsAndChars {
 TT<
int> data1;
 TT<
char> data2;
}

IntsAndChars<Stack> myData;

template <class T = int, int Max = 10>

class A { ... }

A<>       ← T = int, Max = 10

A<char>   ← T = char, Max = 10

A<char,7> ← T = char, Max = 7

struct nil {};
template <class T, class U=nil, class V=nil>
class Tuple { ... };

tuple<
int>
tuple<
int, char>
tuple<
int, char, std::string>

Templates are Syntactic Macros

template <class T>
class Stack {
 
std::vector<T> s;
public:
 
void push(const T& t);
 T pop
();
 
bool is_empty();
};

Stack<int>

class __Stack_int {
 
std::vector<int> s;
public:
 
void push(const int& t);
 
int pop();
 
bool is_empty();
};

min<int>(1, 2);

::min(1, 2);

std::string str1("Cat"), str2("Hat");
std::string first = ::min(a, b);

Why C++ Templates Suck

Templates Specialisation in C++

// Primary template
template <class T, class U, class V>
class t { ... };

// Partial specialisation; T, U, and V are pointer types
template <class T, class U, class V>
class t<T* U* V*> { ... };

// Partial specialisation; some parameters are identical
template <class T, class U>
class t<T, T, U> { ... };

template <class T, class U>
class t<T, U, U> { ... };

// Partial specialisation; all parameters are identical
template <class T>
class t<T, T, T> { ... };

// Explicit specialisation; all must be integers
template <>
class t<int, int, int> { ... };

Example

To make a template special, you just have to believe it’s special.

T

T1

T2

template <class A, class B>

class c{ ... };

template <>

class c<int, int> { ... };

template <class A>

class c<A, A> { ... };

  • With the above templates, T1 is more specialised than T2, because:
  • T1’s arguments match T2’s (A can be set to int)
  • T2’s arguments do not match T1’s (int cannot be generalised to A)

  • In addition, T1 is maximally specialised because there are no templates that are more specialised than T1.

Example

Gay pride templates

A

template <class T, class U, class V>

class t { ... };

B

template <class T, class U, class V>

class t<T*, U*, V*> { ... };

C

template <class T, class U>

class t<T, T, U> { ... };

D

template <class T, class U>

class t<T, U, U> { ... };

E

template <class T>

class t<T, T, T> { ... };

F

template <>

class t<int, int, int> { ... };

  • Given these templates, which specialisation is selected?

t<int, bool, int>

A

t<int, int, bool>

C

t<int, int, int>

F - more specialised than E

t<int*, bool*, int*>

B

t<int*, bool*, bool*>

ERROR: both B and D are maximally specialised for this template. We have no idea which one to pick!

Example

Templates!

  • To do recursion with templates:
  • Implement the recursive step as the normal template
  • Implement the base case as a specialised template
  • We’ll implement factorial as an example:

Recursive step

Base case

template <int N>

struct Factorial {

  enum {

    RET = N * Factorial<N - 1>::RET

  };

};

template <>

struct Factorial<0> {

  enum {

    RET = 1

  };

};

  • Now, if you try and use this template, the compiler will expand it out like so:

Factorial<3>::RET;

struct Factorial_3 {

  enum { RET = 3 * Factorial_2::RET };

};

struct Factorial_2 {

  enum { RET = 2 * Factorial_1::RET };

};

struct Factorial_1 {

  enum { RET = 1 * Factorial_0::RET };

};

struct Factorial_0 {

  enum { RET = 1 };

};

  • Your compiler can even optimise away constant expressions (since those values will never change, what’s the harm in computing them in advance and hard-coding them?):

Factorial<3>::RET;

struct Factorial_3 {

  enum { RET = 6 };

};

struct Factorial_2 {

  enum { RET = 2 };

};

struct Factorial_1 {

  enum { RET = 1 };

};

struct Factorial_0 {

  enum { RET = 1 };

};

Normal boring fully-dynamic ‘power’ function

Totally epic half-compile-time half-run-time ‘power’ function

int power(int n, int m) {
 
if (n == 0) return 1;
 
else if (n == 1) return m;
 
else return m * power(n - 1, m);
}

template <int n>
int power(int m) {
 
return power<n - 1>(m) * m;
}
template <> int power<1>(int m) {return m;}
template <> int power<0>(int m) {return 1;}

Template Metaprogramming

IF<>

// Normal template is used when Cond = true
template <bool Cond, class Then, class Else>
struct IF {
 
typedef Then RET;
};

// Specialised template is used when Cond = false
template <class Then, class Else>
struct IF<false, Then, Else> {
 
typedef Else RET;
};

// Usage:
IF<
1 == 0, int, double>::RET f; // f is double
IF<
1 == 1, int, double>::RET g; // g is int

WHILE<>

template <class State>
struct STOP {
 
typedef State RET;
};

template <class Cond, class State>
struct WHILE {
 
typedef IF<Cond::template Code<State>::RET,
            WHILE<Cond,
typename State::Next>,
            STOP<State>
         >::RET::RET RET
};

Example

Fibonacci: the ‘hello world’ of loops

  • Let’s implement Fibonacci normally:

Normal way

// x == fib(i), y == fib(i-1)
int i; int x; int y;
i =
1; x = 1; y = 0;

while (i < n) {
 
i = i + 1;
 
x = x + y; // fib(i+1) = fib(i)+fib(i-1)
 
y = x;
}

  • We need to identify three things to “convert” this to WHILE<>:
  • State: the variables that are updated by the loop
  • integers i, x, and y
  • Code: loop condition
  • The predicate in the while loop; namely, i < n
  • Next: the new state after one iteration
  • i = i + 1, x = x + y, y = x

Template way

template <int n>
struct Fibonacci {
 
enum {
   RET = WHILE<FibCond<n>,
               FibState<
1,1,0>
         >::RET::x
 };
};

template <int i_, int x_, int y>
struct FibState {
 
enum { i = i_, x = x_ };
 
typedef FibState<i+1, x+y, x> Next;
};

template <int n>
struct FibCond {
 
template <class State>
 struct Code {
   
enum {
     RET =
State::i < n
   };
 };
};

Loop Unrolling

inline void addVect(int size, double *c, double *a, double *b) {
 
while (size--)
   *c++ = *a++ + *b++;
}

*c = *a + *b;
*(c +
1) = *(a + 1) + *(b + 1);
*(c +
2) = *(a + 2) + *(b + 2);

...
*(c +
n) = *(a + n) + *(b + n);

Approach #1

template <int n>
inline void addVect(double *c, double *a, double *b) {
 *c = *a + *b;
 addVect<n -
1>(c + 1, a + 1, b + 1);
}

template<>
inline void addVect<0>(double *c, double *a, double *b) {
}

Approach #2

template <int n, class B>
struct UNROLL
{
   
static void iteration(int i)
   {
       B::body(i);
       UNROLL<n -
1, B>::iteration(i + 1);
   }
};

template <class B>
struct UNROLL<0, B>
{
   
static void iteration(int i) {}
};

template <int n, class B>
struct FOR
{
   
static void loop(int m)
   {
       
for (int i = 0; i < m / n; i++)
       {
           UNROLL<n, B>::iteration(i * n);
       }
       
for (int i = 0; i < m % n; i++)
       {
           B::body(n * m / n + i);
       }
   }
};

// USAGE
double *a, *b, *c;

class AddVecBody
{
public:
   
static inline void body(int i)
   {
       *(c + i) = *(a + i) + *(b + i);
   }
};

FOR<
4, AddVecBody>::loop(1000);

On the topic of rolling, here is sushi being rolled.

ADTs

Normal code

Template code

struct Cons {
 
int head;
 Cons *tail;
}

struct Nil {
 
enum { head = ERROR };
 
typedef Nil Tail;
};

template <int head_, class Tail_ = Nil>
struct Cons {
 
enum { head = head_ };
 
typedef Tail_ Tail;
}

Normal code

Template code

int Length(Cons *l) {
 
if (l->tail == NULL)
   
return 1;
 
else
   
return Length(l->tail) + 1;
}

template <class List>
struct Length {
 
enum { RET =
      Length<
typename List::Tail>::RET + 1
      };
};

template <>
struct Length<Nil> {
 
enum { RET = 0 };
};

Normal code

Template code

void Append(Cons *list1, Cons *list2) {
 
if (list1->tail == NULL)
   list1->tail = list2;
 
else
   Append(list1->tail, list2);
}

template <class L1, class L2>
struct Append {
 
typedef Cons<L1::head,
       
typename Append<typename L1::Tail,
                        L2
                 >::RET
         > RET;
};

template <class L2>
struct Append<Nil, L2> {
 
typedef L2 RET;
};

Expression Templates

Aspect Oriented Programming with AspectJ

Aspect Oriented Programming Concepts

addUserRecord(user_record) {
 
if (user_record.age < 0) {
   
println("Failed to add user; invalid age");
   
throw new InvalidFieldException("Please input a valid age!");
 }

 
if (sql_in(user_record.name) || sql_in(user_record.age)) {
   
println("Failed to add user; SQL injection detected");
   
throw new SQLInjectionException("SQL injection detected!");
 }
 
var sql = "INSERT INTO User (name, age) VALUES ($user_record.name, $user_record.age)";

  println("Adding new user...");
 var success =
execute_sql(sql);

  if (success)
   
println("Added new user successfully!");

  else {

    println(“Failed to create new user; rolling back”);

    rollback();

  }
}

error_handling {

  before addUserRecord(user_record) {

    if (user_record.age < 0)

      throw new InvalidFieldException(“Please input a valid age!”);

  }

  after addUserRecord(success) {

    if (!success)

      rollback();

  }

}

security {

  before addUserRecord(user_record) {

    if (sql_in(user_record.name) || sql_in(user_record.age))

      throw new SQLInjectionException(“SQL injection detected!”);

  }

}

logging {

  before execute_sql {

    println(“Adding new user...”);

  }

  after addUserRecord(success) {

    if (success)

      println(“Added new user successfully!”);

  }

  before rollback() {

    println(“Failed to create new user; rolling back”);

  }

  after addUserRecord throwing InvalidFieldException {

    println(“Failed to add user; invalid age”);

  }

  after addUserRecord throwing SQLInjectionException {

    println(“Failed to add user; SQL injection detected”);

  }

}

addUserRecord(user_record) {
 
var sql = "INSERT INTO User (name, age) VALUES ($user_record.name, $user_record.age)";
 var success =
execute_sql(sql);

  return success;
}

Introduction to AspectJ

public aspect MyAspect {
 
// aspect code
}

public aspect MyAspect {
 pointcut setter
(): call(void set*(*));
}

public aspect MyAspect {
 pointcut setter
(): call(void set*(*));

 before(): setter() {
   System.out.println(
"I am before the setter call!");
 }


 after(): setter() {
   System.out.println(
"I am after the setter call!");
 }


 around(): setter() {
   System.out.println(
"I am here instead of the setter call!");
 }

 
// can also substitute pointcut directly
 before(): call(
void set*(*)) {
   System.out.println(
"I am before the setter!");
 }
}

public aspect MyAspect {
 
int count = 0;

 pointcut setter
(): call(void set*(*));

 before(): setter() {
   count++;
   System.out.println(
"You have setted " + count + " times so far.");
 }
}

Pattern type

Syntax

Examples

MethodPattern

return_type classpath.method(params)

int Adder.add(int[])

public static void main(*)

FieldPattern

type class.field

static int T.x

private final int MAX_NUMS

ConstructorPattern

classpath.new(params)

org.main.Dog.new()

MyString.new(char[])

TypePattern

type

int

char

Object

NullPointerException

Pointcut type

Syntax

Description

Method execution

execution(MethodPattern)

Captures method execution in its class.

Method call

call(MethodPattern)

Captures method calls from its parent scope.

Constructor execution

execution(ConstructorPattern)

Captures constructor execution in its own class.

Constructor call

call(ConstructorPattern)

Captures constructor calls from its parent scope.

Object pre-initialisation

preinitialization(ConstructorPattern)

Captures the moment just before an object is initialised. Can only be used with constructors.

Object initialisation

initialization(ConstructorPattern)

Captures the moment an object is initialised. Can only be used with constructors.

Static initialisation

staticinitialization(ConstructorPattern)

Captures the moment a static object is initialised.

Field set

set(FieldPattern)

Captures property mutation.

Field get

get(FieldPattern)

Captures property access.

Handler execution

handler(TypePattern)

Captures execution of exception handlers.

Advice execution

adviceexecution()

Captures execution of advice.

Call vs Execution

class Main {
 
void main() {
   var v =
new MyValue();
   v.getValue();
// ← call(MyValue.getValue())
 }
}

class MyValue {
 
public int getValue() { // ← execution(MyValue.getValue())
   
return 2;
 }
}

State-Based Pointcuts

class Main {
 
void main() {
   var v =
new MyValue();
   v.getValue();
// ← call(MyValue.getValue())
 }
}

class MyValue {
 
public int getValue() { // ← execution(MyValue.getValue())
   
return 2;
 }
}

call

target → instance of MyValue

this → instance of Main

execution

target → instance of MyValue

this → instance of MyValue

public aspect MyAspect {
 
// Only using target / this as a 'guard' using a TypePattern
 before(): call(* *.getValue()) && target(MyValue+) { ... }

 
// Capturing target / this in a parameter
 before(MyValue mv): call(* *.getValue()) && target(mv) { ... }
 before(Main main): call(* *.getValue()) &&
this(main) { ... }

 before(MyValue mv): execution(* *.getValue()) && target(mv) { ... }
 before(MyValue mv): execution(* *.getValue()) &&
this(mv) { ... }
}

class Main {
 
void main() {
   MyValue.setValue(
2);
 }
}

class MyValue {
 
public static void setValue(int val) { ... }
}

public aspect MyAspect {

  // You can also use ‘args’ as a guard using a TypePattern

  before(): call(* *.setValue(*)) && args(int) { ... }

  // ... or capture arguments in a parameter
 before(
int arg): call(* *.setValue(*)) && args(arg) {
   
// arg == 2
 }
}

public aspect MyAspect {
 after() returning(
int i): call(int *.getInt()) {
   System.out.println(
"Returned value:" + i);
 }
}

Class JoinPoint

class Main {
 
void main() {
   MyValue.setValue(
2);
 }
}

class MyValue {
 
public static void setValue(int val) { ... }
}

public aspect MyAspect {
 
// Captures call to setValue within a class that’s not in standard Java library
 before(): call(* *.setValue(*)) && !within(java..*) { ... }

 
// Captures call to setValue within the main method in Main
 before(): call(* *.setValue(*)) && withincode(* Main.main()) { ... }
}

public aspect MyAspect {
 
// Captures call to method when array passed in has more than 2 elements
 before(int[] arr): call(* *.method(*)) && args(arr) && if(arr.length > 2) { ... }
}

Advice Placement

public aspect MyAspect {
 Object around
(): call(* *(..)) {
   
// ...

   Object result = proceed();
// ← invokes original join point code
   
return result;
 }
}

public aspect FailureHandling {
 
final int N = 3;

 pointcut
getReplyMethod(): call(int *.getReply() throws RemoteException)

 
int around() throws RemoteException: getReplyMethod() {
   
int retry = 0;
   
while (true) {
     
try {
       
return proceed();
     }
     
catch (RemoteException ex) {
       System.out.println(
"Encountered " + ex);
       retry++;
       
if (retry == N) throw ex;
       System.out.println(
"Retrying...");
   }
 }
}

CFlow

public class Main {
 
void main() {
   FIB(
3);
 }


 
void FIB(int n) { // Just your average recursive fibonacci implementation
   
if (n == 0) return 0;
   
return FIB(n - 1) + n;
 }
}

public aspect MyAspect {
 pointcut fib
(): call(void FIB(int))

 pointcut p1
(): fib() && cflow(fib()) // ← Captures FIB(3), FIB(2), FIB(1) and FIB(0)
 pointcut p2
(): fib() && cflowbelow(fib()) // ← Captures FIB(2), FIB(1) and FIB(0)
 pointcut p3
(): fib() && !cflow(fib()) // ← Captures nothing
 pointcut p4
(): fib() && !cflowbelow(fib()) // ← Captures only top-level FIB(3)
}

FIB(3)

cflow

FIB(3)

cflowbelow

FIB(3)

FIB(2)

FIB(2)

FIB(1)

FIB(1)

FIB(0)

FIB(0)

!cflow

FIB(3)

!cflowbelow

FIB(3)

FIB(2)

FIB(2)

FIB(1)

FIB(1)

FIB(0)

FIB(0)

Inter-Type Declarations

public aspect MyAspect {
 
private String Line.label = ""; // ← String label is now in Line class

 
public void Line.setLabel(String s) { // ← setLabel is now in Line class
   label = s;
 }
 
public String Line.getLabel() { // ← getLabel is now in Line class
   
return label;
 }
}

class Line {
 
protected String protecc;
 
private String privatesOohLaLa;
}

public privileged aspect MyAspect {
 
public String Line.letMeSeeYourPrivates() { // ← can see Line's privates
   String iCanSeeThis = protecc;
   
return privatesOohLaLa;
 }
}

public class Dog {}

public aspect Barking {
 
public interface Barker {} // Marker interface 'Barker'

 
public void Barker.bark() { // Populating marker interface with 'bark' method
   System.out.println(
"Woof!");
 }

 declare parents : Dog extends Barker;
// Dog now inherits Barker

  // Pointcut that captures Dog constructor and binds created Dog instance
 pointcut dogConstructor
(Dog dog) : initialization(Dog.new()) && target(dog);


 after(Dog dog) : dogConstructor(dog) {
   dog.bark();
// Can now use 'bark' method in any Dog
 }
}

public aspect MyAspect {
 pointcut illegalNew
():
   call
(FigureElement+.new(..)) && // Calling constructor
   !withincode
(* FigureElementFactory.mk*(..)); // Not in a factory method

 declare error: illegalNew() :
// Static check
   
"Use factory method";
}

Why AspectJ Sucks

Quote-Based Metaprogramming

#1: Let the compiler build the AST

#2: Splice meta-level values into AST

VarDec = `[ int i; ];

Object language (AspectJ)

Glue language (Meta-AspectJ)

Meta language (Meta-AspectJ)

VarDec = `[ int i; ];
Class cl = Foo.class;


ClassDec c =
`[

  class #[cl.getName()] {
   
#v
 
}

];

Entry Points

Entry Point

AST Class

(all paths prefixed by org.aspectj.compiler)

class

interface

Example

Single Identifier

base.ast.IDENT

IDENT foo = `[ MyClass ];

Identifier

base.ast.Identifier

Identifier foo = `[ java.util.Vector ];

Identifier Pattern

crosscuts.ast.NamePattern

NamePattern foo = `[ * ];

NamePattern bar = `[ java.lang.* ];

Modifiers

base.ast.Modifiers

Modifiers foo = `[ public static ];

Import

base.ast.Import

Import foo = `[ import java.lang.*;  ];

Type Spec.

base.ast.TypeD

TypeD foo = `[ MyClass ];

TypeD builtIn = `[ int ];

Formal Declaration

base.ast.FormalDec

FormalDec foo = `[ int x ];

Variable Declaration

base.ast.VarDec

VarDec foo = `[int x = 0; ];

VarDec bar = `[ Object x, y; ];

Expression

base.ast.JavaExpr

JavaExpr myInt = `[ 32 ];

JavaExpr myBool = `[ true && myInt == 32 ];

JavaExpr myMethCall = `[ a.foo(3, 5); ];

Statement

base.ast.Stmt

Stmt myIncr = `[ x++; ];

Stmt myFor = `[

  for (int x = 0; x < 5; x++) {

    foo(x);

  } ];

Stmt myEmpty = `[ {} ];

Method Declaration

base.ast.MethodDec

MethodDec meth1 = `[

  public void getY() {

    return y;

  } ];

Constructor Declaration

base.ast.ConstructorDec

ConstructorDec myCons = `[

  public Foo() {}

];

Class Declaration

base.ast.ClassDec

ClassDec classFoo = `[

  public class Foo {

    public static final int x = 0;

  } ];

Interface Declaration

base.ast.InterfaceDec

InterfaceDec myInterface = `[

  public interface myInterface {

    public void foo();

  } ];

Compilation Unit Member (Java)

base.ast.ClassMember

Any Class Declaration or Interface Declaration

Compilation Unit Member (AspectJ)

base.ast.AspectMember

Any Class Declaration, Interface Declaration, or Aspect Declaration

Compilation Unit

base.ast.CompUnit

CompUnit myCompUnit = `[

  import java.lang.*;

  import java.util.*;

  public class Foo {

    private int m_iX = 0;

    public Foo() {

    }

  }];

Intertype Declare

crosscuts.ast.DeclareDec

DeclareDec d1 = `[

  declare parents : myClass implements java.io.Serializable

];

Advice Declaration

crosscuts.ast.AdviceDec

AdviceDec myAdv = `[

  before(): call(void set*(*)) {}

];

Pointcut

crosscuts.ast.Pcd

Pcd pcd1 = `[

  call(void Foo.m(int, int, ...))

];

Pcd pcd2 = `[

  execution(Foo.new(int))

];

Pointcut Declaration

crosscuts.ast.PointcutDec

PointcutDec pdec = `[

  private pointcut foo(Object o);

];

Aspect Declaration

crosscuts.ast.AspectDec

AspectDec myAspect = `[

  public aspect Foo {

    declare precedence: Security, Logging;

    before(): call(void set*(*)) {}

  } ];

Using ` and #

`[ AspectJ code here ]

Identifier myIdent = `[ foo ];
VarDec myVar = `[
int #myIndent = 0; ]; // int foo = 0;

#id

#[expression]

Identifier name = `[ foo ];
int val = 2;

VarDec dec = `[
int #name = #val; ];

// int foo = 2;

int baseVal = 6;

VarDec dec = `[

  int foo = #[baseVal + 10];

];
// int foo = 16;

Dcl v = `(Dcl)[ int x = 0; ];
Stm s = `(Stm)[
if (#(Dcl)v) x++; ]; // bad
Dcl c = `(Dcl)[
class C { #(Dcl)v } ]; // OK

Using infer

infer type = `[ int ];

infer name = `[ foo ];
infer value = `[ 2 ];

infer myVar = `[ #type #name = #value ]; // int foo = 2;

infer type;            // Boo! 👎

infer name = `[ foo ]; // Yay! 👍

Unquote Splice

Parameters

Class methods

FormalDec[] args = new FormalDec[5];

// Creates arguments
for (int i = 0; i < 5; i++)
 args[i] = `[
int p#i ];

// Creates function from arguments
infer func = `[
int func(#args) { } ];

/* Output:

int func(int p0, int p1, int p2, int p3, int p4) { }

*/

String[] strings = new String[] {
 
"Julian",
 
"Rathke",
 
"Loves",
 
"Monads"
};

MethodDec[] methods =
new MethodDec[strings.length];

// Creates methods from strings
for (int i = 0; i < strings.length; i++) {

  String string = strings[i];
 infer method = `[

    public void #string() {}

  ];
 methods[i] = method;
}

// Creates class
infer myClass = `[
 
public class MyClass {
   #[methods.toArray();]
 }];

/* Output:

public class MyClass {

  public void Julian() {

  }

  public void Rathke() {

  }

  public void Loves() {

  }

  public void Monads() {

  }

}

*/

Convenience Methods

infer var = `[ int x = 1; ];

var.unparse(); // “int x = 1;”

infer myClass = `[ class MyClass {} ];
infer myMethod = `[
public void addOne(int x) { return x + 1; } ];

// ClassDec has an addMethod method
myClass.addMethod(myMethod);

public void addMethod(String className, String methodName) {
 infer declaration = `[
public void #className.#methodName() {} ];
 infer aspect = `[
public aspect #[className + methodName] { #declaration } ];

 System.out.println(aspect.unparse());
}

public void addField(String className, String fieldName) {
 infer declaration = `[
public int #className.#fieldName ];
 infer aspect = `[
public aspect #[className + fieldName] { #declaration } ];

 System.out.println(aspect.unparse());
}

public class MyClass extends MyOtherClass {

  int x, y;

  public MyClass() { ... }

  public MyClass(int x) { ... }

  public int getX() { ... }

  public int getY() { ... }

}

Class myClass = Class.forName(
"MyClass");

myClass.getName() // Returns the class name: 'MyClass'
myClass.isInterface()
// Checks if it is an interface: 'false'
myClass.isArray()
// Checks if it is an array: 'false'
myClass.isPrimitive()
// Checks if it is primitive: 'false'
myClass.getSuperclass()
// Returns superclass reference: MyOtherClass
myClass.getDeclaredFields()
// Fields: [int x, int y]
myClass.getDeclaredMethods()
// Methods: [int getX(), int getY()]
myClass.getDeclaredConstructors()
// Constructors: [MyClass, MyClass(int x)]
myClass.getDeclaredMethod(
"getX", new Class[0]) // Returns specified method

Why Meta-AspectJ Sucks

  1. Everything sucks
  2. Meta-AspectJ is a part of ‘everything’
  3. Therefore, Meta-AspectJ sucks

  1. For one thing, it’s Java only.
  2. Secondly, it’s so meta that you’d probably end up tangling yourself if you were to use this in a big project.
  1. Thirdly, it’s quite obscure.
  1. Fourthly, I want to keep the pattern going for my own amusement.

Transformative Code Generation

If you remember (or even did) Programming Language Concepts,

ANTLR won’t be so hard.

Let’s Get Meta!

Matt’s Teach Yourself ANTLR in 2 minutes

  • ANTLR is similar to Backus-Naur form back in PLC.
  • An ANTLR grammar starts with:
  • A ‘grammar’ line to define the name of the grammar
  • A ‘prog’ line to define the starting grammar rule, where parsing begins (this is just a grammar rule, and can be anything you want; calling it ‘prog’ to signify the root is a convention)

grammar myGrammarName ;

prog: grammarRulesForThisLanguage ;

  • A grammar rule starts with a name, then a colon :, then the rule itself, and ends with a semicolon ;
  • You can add alternatives (different parsing paths) with the bar |

myrule: alternative1

      | alternative2

      | alternative3;

  • There are non-terminals and terminals.
  • Non-terminals are the rules we define: they can be split further
  • Terminals are the constants that cannot be split further
  • You can express terminals using a regex-like syntax.

Functionality

Example

Accepted sentences

Strings

HELLO: ‘hello’ ;

hello

bracket_int: ‘(‘ INT ‘)’ ;

(5)

(26)

(456)

Or

VOWEL: (‘a’|‘e’|‘i’|‘o’|‘u’) ;

a

e

i

o

u

Match anything in set

VOWEL: [aeiou] ;

a

e

i

o

u

Range

DIGIT: [0-9] ;

2

7

9

ALPHA: [0-9a-zA-Z] ;

8

y

B

0 or more

MANY_C: ‘c’* ;

(nothing; empty string)

c

cccc

1 or more

INT: DIGIT+ ;

3

46

7723

0 or 1

PLURAL: ‘one’ ‘s’?

one

ones

  • There are two kinds of rules: lexer rules and parser rules.

Lexer rules

Parser rules

  • Denoted in UPPER CASE.
  • Describes tokens

ID: [a-zA-Z]+ ;

INT: [0-9]+ ;

NEWLINE: ‘\r’? ‘\n’;

WS: [ \t]+ -> skip ;

  • Denoted in lower case.
  • Describes syntax

stat: expr NEWLINE

    | ID ‘=’ expr NEWLINE

    | NEWLINE

    ;

expr: expr (‘*’|’/’) expr

    | expr (‘+’|’-’) expr

    | INT

    | ID

    | ‘(‘ expr ‘)’

    ;

  • Full example:

grammar Expr ;

prog: stat+ ;

stat: expr NEWLINE

    | ID ‘=’ expr NEWLINE

    | NEWLINE

    ;

expr: expr (‘*’|’/’) expr

    | expr (‘+’|’-’) expr

    | INT

    | ID

    | ‘(‘ expr ‘)’

    ;

ID: [a-zA-Z]+ ;

INT: [0-9]+ ;

NEWLINE: ‘\r’? ‘\n’ ;

WS: [ \t]+ -> skip ; // ‘-> skip’ is used to ignore whitespace

Grammar rule

Matches

assign : ID '=' expr ';' ;

foo = 1;

bar = 2;

The actual code

void assign() {
 match(ID);
 match(
'=');
 expr();
 match(
';');
}

Grammar rule

Matches

stat: assign

    | ifstat

    | whilestat

   ...

    ;

foo = 1;

bar = 2;

if (true) { ... }

while (1 == 1) { ... }

The actual code

void stat() {
 
switch ( <<current input token>> ) {
   
case ID: assign(); break;
   
case IF: ifstat(); break;
   
case WHILE: whilestat(); break;
   ...
   
default: <<raise no viable alternative exception>>
 }
}

Example

Windows 98 maze screensaver

  • Let’s say there’s a maze, with one entrance and one exit.
  • There are words all across the floor. Every sequence of words along a path from the entrance to the exit represents a valid sentence in our language.
  • Given a sentence, if you can make a path from the entrance to the exit using the words in the sentence, you have a valid sentence in the language.

  • If you hit a fork in the road, which path do you choose?
  • You look at each path and see which words correlate to the next words in your sentence.
  • Basically, you lookahead!

  • What if there are multiple ways to reach the exit with the same sentence?
  • Then the sentence is ambiguous in its semantics.

“You Can’t Put Too Much Water into a Nuclear Reactor”

“To my Ph.D supervisor, for whom no thanks is too much”

Grammar rule

Matches

stat: expr ';'
   | ID
'(' ')' ';'
   
;
expr: ID '(' ')'
   | INT
   
;

f();

foo();

bar();

f(); as expr

f(); as stat

Grammar rule

Matches

BEGIN: ‘begin’ ;
ID: [a-z]+ ;

begin

Listeners

enterStat(StatContext)

enterAssign(AssignContext)

visitTerminal(TerminalNode)

visitTerminal(TerminalNode)

enterExpr(ExprContext)

visitTerminal(TerminalNode)

exitExpr(ExprContext)

visitTerminal(TerminalNode)

exitAssign(AssignContext)

exitStat(StatContext)

Software Sec Example

ArrayInit.g4

grammar ArrayInit;

// A rule called list that matches comma-separated values between { ... }.
list: '{' value (',' value)* '}' ; // must match at least one value

// A value can be either a nested array/struct or a simple integer (INT)
value: list
    | INT
    ;

// Parser rules start with lowercase letters, lexer rules with uppercase
INT: [0-9]+ ;            // Define token INT as one or more digits
WS: [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out

Of course! I’m well-behaved and I follow the rules.

No way! ANTLR sucks like everything else.

Good job!

I don’t have any rewards to give you, so, um... here’s a monad.

(>>=) :: ma → (a → mb) → mb

Yare yare... you can’t follow along if you don’t have ANTLR.

Go and follow the instructions on the official site to install it, as well as the commands.

Or, if you really don’t care, feel free to read on.

$ antlr4 ArrayInit.g4

$ javac ArrayInit*.java -d out

$ cd out

$ grun ArrayInit list -gui

{1,2,3}

CTRL + D

ShortToUnicodeString.java

public class ShortToUnicodeString extends ArrayInitBaseListener {

  @Override

  public void enterList(ArrayInitParser.ListContext ctx) {
   System.
out.print('"');
 }

 
@Override

  public void enterValue(ArrayInitParser.ValueContext ctx) {

    int value = Integer.valueOf(ctx.INT().getText());
   System.
out.printf("\\u%04x", value);
 }

 
@Override

  public void exitList(ArrayInitParser.ListContext ctx) {
   System.
out.println('"');
 }
}

  1. Loads up some ArrayInit source code
  2. Computes the parse tree
  3. Walks through the parse tree with our new listener

Main.java

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import static org.antlr.v4.runtime.CharStreams.fromFileName;

public class Main {
 
public static final String FILENAME = "test.txt";

 
public static void main(String[] args) throws Exception {
   
var inputStream = getInputStream();
   
var parseTree = getParseTree(inputStream);
   
walkThroughParseTreeWithListener(parseTree, new ShortToUnicodeString());
 }

  public static CharStream getInputStream() throws Exception {
   
return fromFileName(FILENAME);
 }

  public static ParseTree getParseTree(CharStream inputStream) {
   
var lexer = new ArrayInitLexer(inputStream);
   
var tokens = new CommonTokenStream(lexer);
   
var parser = new ArrayInitParser(tokens);

   
return parser.list();
 }

  public static void walkThroughParseTreeWithListener(ParseTree parseTree, ParseTreeListener listener) {
   
var walker = new ParseTreeWalker();
   walker.walk(listener, parseTree);
 }
}

out/test.txt

{1,6,7,83,12}

$ javac *.java -d out

$ cd out

$ java Main

"\u0001\u0006\u0007\u0053\u000c"

Visitors

Binary Tree Example

BinaryTree.g4

grammar BinaryTree ;

binarytree: '(' value subtree subtree ')';

subtree: value | binarytree ;

value: INT ;

INT: [0-9]+ ;            // Define token INT as one or more digits
WS: [ \t\r\n]+ -> skip ; // Define whitespace rule, toss it out

Debugger’s note

As I was writing this binary tree grammar, I kept running into syntax errors. It was really strange; I could’ve sworn my ANTLR syntax was perfect.

Turns out, ANTLR doesn’t like the word ‘tree’ as a parsing rule name.

¯\_(ツ)_/¯

$ antlr4 ArrayInit.g4 -visitor

$ javac *.java -d out

$ cd out

$ grun BinaryTree binarytree -gui

(5 (6 7 (8 9 10)) (11 12 13))

CTRL + D

BinaryTreeSumVisitor.java

public class BinaryTreeSumVisitor extends BinaryTreeBaseVisitor<Integer> {

  @Override
 
public Integer visitBinarytree(BinaryTreeParser.BinarytreeContext ctx) {
   
int val = visit(ctx.value());
   
int leftVal = visit(ctx.subtree(0));
   
int rightVal = visit(ctx.subtree(1));
   
return val + leftVal + rightVal;
 }

  @Override
 
public Integer visitSubtree(BinaryTreeParser.SubtreeContext ctx) {
   
return visitChildren(ctx);
 }

 
@Override
 
public Integer visitValue(BinaryTreeParser.ValueContext ctx) {
   
int val = Integer.valueOf(ctx.INT().getText());
   
return val;
 }
}

Main.java

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import static org.antlr.v4.runtime.CharStreams.fromFileName;

public class Main {
 
public static final String FILENAME = "test.txt";

 
public static void main(String[] args) throws Exception {
   
var inputStream = getInputStream();
   
var parseTree = getParseTree(inputStream);
   
var sum = walkThroughParseTreeWithSumVisitor(parseTree);

    System.out.println("Sum: " + sum);
 }

  public static CharStream getInputStream() throws Exception {
   
return fromFileName(FILENAME);
 }

  public static ParseTree getParseTree(CharStream inputStream) {
   
var lexer = new ArrayInitLexer(inputStream);
   
var tokens = new CommonTokenStream(lexer);
   
var parser = new ArrayInitParser(tokens);

   
return parser.binarytree();
 }

  public static int walkThroughParseTreeWithSumVisitor(ParseTree parseTree) {
   var visitor =
new BinaryTreeSumVisitor();
   var sum = visitor.visit(parseTree);
   
return sum;
 }
}

out/test.txt

(5 (6 7 (8 9 10)) (11 12 13))

$ javac *.java -d out

$ cd out

$ java Main

Sum: 81

BinaryTreeMaxVisitor.java

public class BinaryTreeSumVisitor extends BinaryTreeBaseVisitor<Integer> {
 
@Override
 
public Integer visitBinarytree(BinaryTreeParser.BinarytreeContext ctx) {
   
int val = visit(ctx.value());
   
int leftVal = visit(ctx.subtree(0));
   
int rightVal = visit(ctx.subtree(1));

   
if (val > leftVal && val > rightVal)
     
return val;
   
if (leftVal > rightVal)
     
return leftVal;
   
return rightVal;
 }
 
@Override
 
public Integer visitSubtree(BinaryTreeParser.SubtreeContext ctx) {
   
return visitChildren(ctx);
 }

 
@Override
 
public Integer visitValue(BinaryTreeParser.ValueContext ctx) {
   
int val = Integer.valueOf(ctx.INT().getText());
   
return val;
 }
}

Main.java

...

public class Main {
 
public static final String FILENAME = "test.txt";

 
public static void main(String[] args) throws Exception {
   
...
   
var max = walkThroughParseTreeWithMaxVisitor(parseTree);

    System.out.println("Max: " + max);
 }

  public static int walkThroughParseTreeWithMaxVisitor(ParseTree parseTree) {
   var visitor =
new BinaryTreeMaxVisitor();
   var max = visitor.visit(parseTree);
   
return max;
 }
}

$ javac *.java -d out

$ cd out

$ java Main

Sum: 81

Max: 13

Other Features

Alternative Labels

expr: expr (‘*’|’/’) expr # MulDiv

    | expr (‘+’|’-’) expr # AddSub

    | INT                 # int

    | ID                  # id

    | ‘(‘ expr ‘)’        # parens

    ;

Rule Element Labels

expr: value op=(‘+’|’-’|‘*’|’/’) value ;

ADD: ‘+’;

SUB: ’-’;

MUL: ‘*’;

DIV: ’/’;

public Integer visitExpr(MyGrammarParser.ExprContext ctx) {
 
switch (ctx.op.getType())
 {
   
case MyGrammarParser.ADD:
     System.out.println(
"This is an addition expression");
     
break;
   
case MyGrammarParser.SUB:
     System.out.println(
"This is a subtraction expression");
     
break;
   
case MyGrammarParser.MUL:
     System.out.println(
"This is a multiplication expression");
     
break;
   
case MyGrammarParser.DIV:
     System.out.println(
"This is a division expression");
     
break;
 }
}

Actions

expr: INT ‘+’ INT { System.out.println(“You’ve just added”); } ;

expr: a=INT ‘+’ b=INT { Integer.parseInt($a.text) >= 0 &&

                        Integer.parseInt($b.text) >= 0; }? ;

Node Values (Attributes)

expr: INT { System.out.println(“int value: “ + $INT.text); };

expr returns [int value]

    : a=INT '+' b=INT {$value = Integer.parseInt($a.text) +

                                Integer.parseInt($b.text); }

    ;

public Integer visitExpr(MyGrammarParser.ExprContext ctx) {
 ctx.value;
// This will equal the matched ‘a’ + the matched ‘b’
}

TokenStreamRewriter

TSR.g4

grammar TSR;

program: programm EOF;

programm: bold

        | deleteme

        | bold programm

        | deleteme programm;

bold: BOLD;

deleteme: DELETEME;

BOLD: ‘bold’;

DELETEME: ‘deleteme’;
WS: [ \t\r\n]+ -> skip ;

TSRMyListener.java

import org.antlr.v4.runtime.*;

public class TSRMyListener extends TSRBaseListener {
 
public TokenStreamRewriter rewriter;

 
public TSRMyListener(TokenStream tokens) {
   rewriter =
new TokenStreamRewriter(tokens);
 }

 
@Override
 
public void enterBold(TSRParser.BoldContext ctx) {
   rewriter.insertBefore(ctx.BOLD().getSymbol(),
'*');
   rewriter.insertAfter(ctx.BOLD().getSymbol(),
'*');
 }

 
@Override
 
public void enterDeleteme(TSRParser.DeletemeContext ctx) {
   rewriter.delete(ctx.DELETEME().getSymbol());
 }
}

Main.java

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import static org.antlr.v4.runtime.CharStreams.fromFileName;

public class Main {
 
public static final String FILENAME = "test.txt";

 
public static void main(String[] args) throws Exception {
   var inputStream = fromFileName(FILENAME);

   var lexer =
new TSRLexer(inputStream);
   var tokens =
new CommonTokenStream(lexer);
   var parser =
new TSRParser(tokens);
   var parseTree = parser.program();

   var walker =
new ParseTreeWalker();
   var listener =
new TSRMyListener(tokens);
   walker.walk(listener, parseTree);

   
// Reads new token stream
   System.out.println(listener.rewriter.getText());
 }
}

out/test.txt

bold deleteme bold deleteme bold

$ javac *.java -d out

$ cd out

$ java Main

*bold**bold**bold*

Why ANTLR Sucks