Skip to content

Commit

Permalink
Assignment 1 CLI and README complete
Browse files Browse the repository at this point in the history
  • Loading branch information
ToucheSir committed Jan 28, 2018
1 parent 8c8508a commit 8e04c9c
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 56 deletions.
4 changes: 3 additions & 1 deletion README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ The Unnamed Language

# Building:
Run `make` to generate the grammar, compile the source and bundle an executable jar (unnamed_language.jar).
Run `make test` to kick off the automated JUnit test suite.

# Running:
Either run `unnamed-language.jar` or use the provided `ulc` script
e.g. java -jar unnamed-language.jar <filename>
e.g. java -jar unnamed-language.jar test.ul
Passing the `-astgraph` flag will generate a DOT graph representation of the AST that can be passed to graphviz.

# Compiler Internals:
The compiler is written in a mix of Java and Kotlin.
Expand Down
65 changes: 42 additions & 23 deletions src/main/java/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,44 @@
import ast.DotPrinter;
import ast.PrettyPrinter;
import org.antlr.runtime.*;
import org.apache.commons.cli.*;

import java.io.*;
import java.util.Scanner;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

public class Compiler {
public static void main(String[] args) throws Exception {
ANTLRInputStream input;
Options options = new Options();
options.addOption("astgraph", "Generate a DOT-formatted AST tree for the input file");

if (args.length == 0) {
System.out.println("Usage: Test filename.ul");
return;
CommandLineParser cliParser = new DefaultParser();
try {
CommandLine cmd = cliParser.parse(options, args);
boolean dumpAst = cmd.hasOption("astgraph");
List<String> restArgs = cmd.getArgList();

if (restArgs.size() < 1) {
printUsage(options);
return;
}
String fileName = restArgs.get(0);
compileFile(fileName, dumpAst);
} catch (UnrecognizedOptionException e) {
System.err.printf("ulc: invalid option: %s%n", e.getOption());
printUsage(options);
}
}

String fileName = args[0];
input = new ANTLRInputStream(new FileInputStream(fileName));
private static void compileFile(String fileName, boolean dumpAst) throws IOException {
Path filePath = Paths.get(fileName);
if (!Files.exists(filePath)) {
System.err.printf("file %s does not exist%n", fileName);
return;
}
ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(fileName));

// The name of the grammar here is "UnnamedLanguage",
// so ANTLR generates UnnamedLanguageLexer and UnnamedLanguageParser
Expand All @@ -32,33 +55,29 @@ public static void main(String[] args) throws Exception {
UnnamedLanguageParser parser = new UnnamedLanguageParser(tokens);

try {
// TODO don't pretty-print by default after we get proper type-checking and/or evaluation
ASTNode program = parser.program();
PrettyPrinter fmt = new PrettyPrinter(System.out);
fmt.process(program);

final Process p = Runtime.getRuntime().exec("dot -Tpng -o ast.png");
try (OutputStream out = p.getOutputStream()) {
DotPrinter graph = new DotPrinter(new PrintStream(out));
if (dumpAst) {
File graphFile = new File(filePath.getFileName() + ".dot");
DotPrinter graph = new DotPrinter(new PrintStream(graphFile));
graph.process(program);
}
new Thread(new Runnable() {
public void run() {
InputStreamReader reader = new InputStreamReader(p.getErrorStream());
Scanner scan = new Scanner(reader);
while (scan.hasNextLine()) {
System.err.println(scan.nextLine());
}
}
}).start();
p.waitFor();
} catch (RecognitionException e) {
// A lexical or parsing error occured.
// A lexical or parsing error occurred.
// ANTLR will have already printed information on the
// console due to code added to the grammar. So there is
// nothing to do here.
// console due to code added to the grammar,
// so there is nothing to do here.
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}

private static void printUsage(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("ulc <options> filename.ul", "Options:", options, "");
}
}
70 changes: 39 additions & 31 deletions src/main/java/ast/DotPrinter.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package ast

import org.apache.commons.text.StringEscapeUtils.escapeJava
import java.io.PrintStream

private fun escapeStringLiteral(s: String) = s
.replace("\\", "\\\\")
.replace("\"", "\\\"")

private data class TreeNode(val id: Int, val type: String, var value: String? = null)

class DotPrinter(private val out: PrintStream, private val graphName: String) : ASTConsumer<Unit> {
private var counter = 0

Expand All @@ -14,23 +21,25 @@ class DotPrinter(private val out: PrintStream, private val graphName: String) :
out.println("}")
}

private fun createNode(label: String, parentId: Int? = null, action: ((Int) -> Unit)? = null) {
val id = allocateNode(label)
parentId?.let { out.println("$parentId -> $id") }
return action?.invoke(id) ?: Unit
private fun createNode(
label: String,
parentId: Int? = null,
action: ((TreeNode) -> Unit)? = null
) {
val node = allocateNode(label)
action?.invoke(node)
val escapedType = escapeStringLiteral(node.type)
out.print("${node.id} [shape=\"record\",label=\"{$escapedType")
node.value?.let { out.print("|${escapeStringLiteral(it)}") }
out.println("}\"]")
parentId?.let { out.println("$parentId -> ${node.id}") }
}

private fun allocateNode(label: String): Int {
val id = counter++
val escapedLabel = label
.replace("\\", "\\\\")
.replace("\"", "\\\"")
out.println("$id [shape=box,label=\"$escapedLabel\"]")
return id
}
private fun allocateNode(type: String) = TreeNode(counter++, type)

private fun ASTNode.printTree(parentId: Int? = null) {
createNode(javaClass.simpleName, parentId) { id ->
createNode(javaClass.simpleName, parentId) { node ->
val id = node.id
run {
when (this) {
is Program -> forEach { it.printTree(id) }
Expand All @@ -47,12 +56,8 @@ class DotPrinter(private val out: PrintStream, private val graphName: String) :
type.printTree(id)
name.printTree(id)
}
is TypeNode -> {
createNode(type.name, id)
}
is Identifier -> {
createNode(name, id)
}
is TypeNode -> node.value = type.name
is Identifier -> node.value = name
is FormalParameterList -> forEach { it.printTree(id) }
is FormalParameter -> {
type.printTree(id)
Expand All @@ -66,7 +71,7 @@ class DotPrinter(private val out: PrintStream, private val graphName: String) :
is StatementList -> forEach { it.printTree(id) }
is Statement -> printChildren(id)
is Block -> forEach { it.printTree(id) }
is Expression -> printChildren(id)
is Expression -> printChildren(node)
is ExpressionList -> forEach { it.printTree(id) }
}
}
Expand Down Expand Up @@ -104,23 +109,26 @@ class DotPrinter(private val out: PrintStream, private val graphName: String) :
}
}

private fun Expression.printChildren(id: Int) = when (this) {
private fun Expression.printChildren(node: TreeNode) = when (this) {
is BinaryExpression -> {
lhs.printTree(id)
rhs.printTree(id)
lhs.printTree(node.id)
rhs.printTree(node.id)
}
is ArrayReference -> {
name.printTree(id)
index.printTree(id)
name.printTree(node.id)
index.printTree(node.id)
}
is FunctionCall -> {
name.printTree(id)
args.printTree(id)
name.printTree(node.id)
args.printTree(node.id)
}
is ParenExpression -> {
inner.printTree(id)
is ParenExpression -> inner.printTree(node.id)
is IdentifierValue -> this.id.printTree(node.id)
is StringLiteral -> node.value = "\"${escapeJava(value)}\""
is CharacterLiteral -> {
val escaped = escapeJava(value.toString())
node.value = "'${escaped.removeSurrounding("\"")}'"
}
is IdentifierValue -> this.id.printTree(id)
is Literal<*> -> createNode(value as? String ?: value.toString(), id)
is Literal<*> -> node.value = value.toString()
}
}
3 changes: 2 additions & 1 deletion src/main/java/ast/PrettyPrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,9 @@ class PrettyPrinter(private val out: PrintStream) : ASTConsumer<Unit> {
emit('"')
}
is CharacterLiteral -> {
val escaped = escapeJava(expr.value.toString())
emit('\'')
emit(expr.value)
emit(escaped.removeSurrounding("\""))
emit('\'')
}
is Literal<*> -> emit(expr.value, nestingLevel)
Expand Down

0 comments on commit 8e04c9c

Please sign in to comment.