Lexing and Abstract Syntax Trees

CSC447

Concepts of Programming Languages

Instructor: James Riely

Pastries: Simple Java

abstract class Pastry {
  public void printMe (); 
}
class Beignet extends Pastry {
  @Override
  public void printMe() {
    System.out.println ("It's a beignet");
  }
}
class Cruller extends Pastry {
  @Override
  public void printMe() {
    System.out.println ("It's a cruller");
  }
}

class PastryTest {
  public static void main (String[] args) {
    var ps = new Pastry[] {new Beignet(), new Cruller()};
    for (var p : ps) {
      p.printMe();
    }
  }
}
$ javac PastryTest.java 
$ java PastryTest 
It's a beignet
It's a cruller

Pastries: Scala match

enum Pastry:
  case Beignet
  case Cruller
import Pastry.*

def printPastry (p: Pastry) =
  p match
    case Beignet => println("It's a Beignet")
    case Cruller => println("It's a Cruller")

def main(args:Array[String]) = 
  var ps = List(Beignet,  Cruller)
  for (p <- ps) do printPastry(p)
$ scala Pastry.scala 
It's a Beignet
It's a Cruller

Pastries: Java Visitor

interface PastryVisitor {
  public void visitBeignet(Beignet beignet); 
  public void visitCruller(Cruller cruller);
}
abstract class Pastry {
  public abstract void accept(PastryVisitor visitor);
}
class Beignet extends Pastry {
  @Override
  public void accept(PastryVisitor visitor) {
    visitor.visitBeignet(this);
  }
}
class Cruller extends Pastry {
  @Override
  public void accept(PastryVisitor visitor) {
    visitor.visitCruller(this);
  }
}

class PastryPrinter implements PastryVisitor {
  public void visitBeignet(Beignet beignet) {
    System.out.println ("It's a beignet");
  }
  public void visitCruller(Cruller cruller) {
    System.out.println ("It's a cruller");
  }
}
public class PastryTest {
  public static void main (String[] args) {
    var printer = new PastryPrinter ();
    var ps = new Pastry[] {new Beignet(), new Cruller()};
    for (var p : ps) {
      p.accept(printer);
    }
  }
}
$ javac PastryTest.java 
$ java PastryTest 
It's a beignet
It's a cruller

Expressions: Abstract Grammar

Simple language with two binary operators

expr    → expr    "-" expr                     [Subtraction]
expr    → expr    "÷" expr                     [Division]   
expr    → NUMBER 

Expressions: Scala match

enum Expr:
  case SubExpr (left: Expr, right: Expr)
  case DivExpr (left: Expr, right: Expr)
  case NumExpr (num: Int)

import Expr.*

def printExpr (e: Expr) : String =
  e match
    case SubExpr (left, right) => "( " + printExpr(left) + " - " + printExpr(right) + " )"
    case DivExpr (left, right) => "( " + printExpr(left) + " / " + printExpr(right) + " )"
    case NumExpr (num)         => num.toString

def evalExpr (e: Expr) : Int =
  e match
    case SubExpr (left, right) => evalExpr(left) - evalExpr(right)
    case DivExpr (left, right) => evalExpr(left) / evalExpr(right)
    case NumExpr (num)         => num

def main(args:Array[String]) = 
  val expr = SubExpr (NumExpr (9), DivExpr (NumExpr(6), NumExpr (3)));
  println (printExpr (expr))
  println (evalExpr (expr))
$ scala Expr.scala
( 9 - ( 6 / 3 ) )
7

Expressions: Java Visitor

interface ExprVisitor<T> {
  public T visitSubExpr(SubExpr expr); 
  public T visitDivExpr(DivExpr expr);
  public T visitNumExpr(NumExpr expr);
}
abstract class Expr {
  public abstract <T> T accept(ExprVisitor<T> visitor);
}
class SubExpr extends Expr {
  public Expr left;
  public Expr right;
  public SubExpr (Expr left, Expr right) {
    this.left = left;
    this.right = right;
  }
  @Override
  public <T> T accept(ExprVisitor<T> visitor) {
    return visitor.visitSubExpr(this);
  }
}
class DivExpr extends Expr {
  public Expr left;
  public Expr right;
  public DivExpr (Expr left, Expr right) {
    this.left = left;
    this.right = right;
  }
  @Override
  public <T> T accept(ExprVisitor<T> visitor) {
    return visitor.visitDivExpr(this);
  }
}
class NumExpr extends Expr {
  public int num;
  public NumExpr (int num) {
    this.num = num;
  }
  public <T> T accept(ExprVisitor<T> visitor) {
    return visitor.visitNumExpr(this);
  }
}

class ExprPrinter implements ExprVisitor<String> {
  public String visitSubExpr(SubExpr expr) {
    String left = expr.left.accept(this);
    String right = expr.right.accept(this);
    return "( " + left + " - " + right + " )";
  }
  public String visitDivExpr(DivExpr expr) {
    String left = expr.left.accept(this);
    String right = expr.right.accept(this);
    return "( " + left + " / " + right + " )";
  }
  public String visitNumExpr(NumExpr expr) {
    return Integer.toString(expr.num);
  }
}

class ExprEvaluator implements ExprVisitor<Integer> {
  public Integer visitSubExpr(SubExpr expr) {
    Integer left = expr.left.accept(this);
    Integer right = expr.right.accept(this);
    return left - right;
  }
  public Integer visitDivExpr(DivExpr expr) {
    Integer left = expr.left.accept(this);
    Integer right = expr.right.accept(this);
    return left / right;
  }
  public Integer visitNumExpr(NumExpr expr) {
    return expr.num;
  }
}

public class ExprTest2 {
  public static void main (String[] args) {
    var printer = new ExprPrinter ();
    var evaluator = new ExprEvaluator ();
    var expr = new SubExpr (new NumExpr (9), new DivExpr (new NumExpr(6), new NumExpr (3)));
    System.out.println (expr.accept(printer));
    System.out.println (expr.accept(evaluator));
  }
}
$ javac ExprTest2.java 
$ java ExprTest 
( 9 - ( 6 / 3 ) )
7

Abstract Grammar of Lox Expressions

expression     → literal
               | unary
               | binary
               | grouping 

literal        → NUMBER | STRING | "true" | "false" | "nil" 
grouping       → "(" expression ")" 
unary          → ( "-" | "!" ) expression 
binary         → expression operator expression 
operator       → "==" | "!=" | "<" | "<=" | ">" | ">="
               | "+"  | "-"  | "*" | "/"