content: package com.tahini.tool;
  
  import java.io.IOException;
  import java.io.PrintWriter;
  import java.util.Arrays;
  import java.util.List;
  
  public class GenerateAst {
  
      public static void main(String[] args) throws IOException {
          if (args.length != 1) {
              System.err.println("Usage: generate_ast <output directory>");
              System.exit(64);
          }
          String outputDir = args[0];
  
          defineAst(outputDir, "Expr", Arrays.asList(
                  "Assign             : Token name, Expr value",
                  "Binary             : Expr left, Token operator, Expr right",
                  "Call               : Expr callee, Token paren, List<Expr> arguments",
                  "ListAccess         : Expr list, Token paren, Expr index",
                  "ListSlice          : Expr list, Token paren, Expr start, Expr end",
                  "Grouping           : Expr expression",
                  "Literal            : Object value",
                  "Unary              : Token operator, Expr right",
                  "Ternary            : Expr condition, Expr left, Expr right",
                  "Variable           : Token name",
                  "NamespacedVariable : List<Token> nameParts",
                  "Logical            : Expr left, Token operator, Expr right",
                  "TahiniList         : List<Expr> elements",
                  "TahiniMap          : List<Expr> keys, List<Expr> values"
          ));
  
          defineAst(outputDir, "Stmt", Arrays.asList(
                  "Expression : Expr expression",
                  "Function   : Token name, List<Token> params, List<Stmt> body, List<Expr> preconditions, List<Expr> postconditions, Object premsg, Object postmsg",
                  "Test       : Token name, Stmt body",
                  "Print      : Expr expression",
                  "If         : Expr condition, Stmt thenBranch, Stmt elseBranch",
                  "Var        : Token name, Expr initializer",
                  "Block      : List<Stmt> statements",
                  "While      : Expr condition, Stmt body",
                  "Break",
                  "Return     : Token keyword, Expr value",
                  "Contract   : Token type, List<Expr> conditions, Object msg",
                  "Import     : Token path, Token name"
          ));
      }
  
      private static void defineAst(
              String outputDir, String baseName, List<String> types)
              throws IOException {
          String path = outputDir + "/" + baseName + ".java";
          PrintWriter writer = new PrintWriter(path, "UTF-8");
  
          writer.println("package com.tahini.lang;");
          writer.println();
          writer.println("import java.util.List;");
          writer.println();
          writer.println("abstract class " + baseName + " {");
  
          defineVisitor(writer, baseName, types);
  
          for (String type : types) {
              String className = type.split(":")[0].trim();
              String fields;
              if (type.split(":").length == 2) {
                  fields = type.split(":")[1].trim();
              } else {
                  fields = "";
              }
  
              defineType(writer, baseName, className, fields);
          }
  
          // The base accept() method.
          writer.println();
          writer.println("  abstract <R> R accept(Visitor<R> visitor);");
  
          writer.println("}");
          writer.close();
      }
  
      private static void defineVisitor(
              PrintWriter writer, String baseName, List<String> types) {
          writer.println("  interface Visitor<R> {");
  
          for (String type : types) {
              String typeName = type.split(":")[0].trim();
              writer.println("    R visit" + typeName + baseName + "("
                      + typeName + " " + baseName.toLowerCase() + ");");
          }
  
          writer.println("  }");
      }
  
      private static void defineType(
              PrintWriter writer, String baseName,
              String className, String fieldList) {
          writer.println("  static class " + className + " extends "
                  + baseName + " {");
  
          // Constructor.
          writer.println("    " + className + "(" + fieldList + ") {");
  
          // Store parameters in fields.
          String[] fields;
          if (fieldList.length() > 0) {
              fields = fieldList.split(", ");
          } else {
              fields = new String[0];
          }
          for (String field : fields) {
              String name = field.split(" ")[1];
              writer.println("      this." + name + " = " + name + ";");
          }
  
          writer.println("    }");
  
          // Visitor pattern.
          writer.println();
          writer.println("    @Override");
          writer.println("    <R> R accept(Visitor<R> visitor) {");
          writer.println("      return visitor.visit"
                  + className + baseName + "(this);");
          writer.println("    }");
  
          // Fields.
          writer.println();
          for (String field : fields) {
              writer.println("    final " + field + ";");
          }
  
          writer.println("  }");
      }
  }
  