Add java code and test cases
This commit is contained in:
23
compiler/pom.xml
Normal file
23
compiler/pom.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>antlr-compiler</artifactId>
|
||||
<groupId>org.example</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>compiler</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.example</groupId>
|
||||
<artifactId>parser</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,15 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
public enum DataType {
|
||||
INT("I"), STRING("Ljava/lang/String;");
|
||||
|
||||
public final String jvmType;
|
||||
|
||||
DataType(String jvmType) {
|
||||
this.jvmType = jvmType;
|
||||
}
|
||||
|
||||
public String getJvmType() {
|
||||
return jvmType;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import de.letsbuildacompiler.compiler.exceptions.FunctionAlreadyDefinedException;
|
||||
import de.letsbuildacompiler.parser.DemoBaseVisitor;
|
||||
import de.letsbuildacompiler.parser.DemoParser;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FunctionDefinitionFinder {
|
||||
|
||||
public static FunctionList findFunctions(ParseTree tree) {
|
||||
final FunctionList definedFunctions = new FunctionList();
|
||||
|
||||
new DemoBaseVisitor<Void>() {
|
||||
@Override
|
||||
public Void visitFunctionDefinition(DemoParser.FunctionDefinitionContext ctx) {
|
||||
String functionName = ctx.funcName.getText();
|
||||
int numberOfArguments = ctx.params.declarations.size();
|
||||
if (definedFunctions.contains(functionName, numberOfArguments)) {
|
||||
throw new FunctionAlreadyDefinedException(ctx.funcName);
|
||||
}
|
||||
definedFunctions.add(functionName, numberOfArguments);
|
||||
return null;
|
||||
}
|
||||
}.visit(tree);
|
||||
|
||||
return definedFunctions;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class FunctionList {
|
||||
|
||||
private Collection<FunctionDefinition> definitions;
|
||||
|
||||
FunctionList() {
|
||||
this.definitions = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean contains(String functionName, int parameterCount) {
|
||||
for (FunctionDefinition definition : definitions) {
|
||||
if (definition.functionName.equals(functionName) && definition.parameterCount == parameterCount) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void add (String functionName, int parameterCount) {
|
||||
definitions.add(new FunctionDefinition(functionName, parameterCount));
|
||||
}
|
||||
|
||||
private static final class FunctionDefinition {
|
||||
private final String functionName;
|
||||
private final int parameterCount;
|
||||
|
||||
private FunctionDefinition(String functionName, int parameterCount) {
|
||||
this.functionName = functionName;
|
||||
this.parameterCount = parameterCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,304 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import de.letsbuildacompiler.compiler.exceptions.UndeclaredVariableException;
|
||||
import de.letsbuildacompiler.compiler.exceptions.UndefinedFunctionException;
|
||||
import de.letsbuildacompiler.compiler.exceptions.VariableAlreadyDefinedException;
|
||||
import de.letsbuildacompiler.parser.DemoBaseVisitor;
|
||||
import de.letsbuildacompiler.parser.DemoParser;
|
||||
import de.letsbuildacompiler.parser.DemoParser.MainStatementContext;
|
||||
import de.letsbuildacompiler.parser.DemoParser.NumberContext;
|
||||
import de.letsbuildacompiler.parser.DemoParser.PlusContext;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JasminVisitor extends DemoBaseVisitor<String> {
|
||||
|
||||
// Symbol table
|
||||
private Map<String, Integer> variables = new HashMap<>();
|
||||
private JvmStack jvmStack = new JvmStack();
|
||||
private final FunctionList definedFunctions;
|
||||
private int branchCounter = 0;
|
||||
private int compareCount = 0;
|
||||
private int andCounter = 0;
|
||||
private int orCounter = 0;
|
||||
|
||||
JasminVisitor(FunctionList definedFunctions) {
|
||||
if (definedFunctions == null) {
|
||||
throw new NullPointerException("definedFunctions");
|
||||
}
|
||||
this.definedFunctions = definedFunctions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitNumber(NumberContext ctx) {
|
||||
jvmStack.push(DataType.INT);
|
||||
return "ldc " + ctx.number.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitString(DemoParser.StringContext ctx) {
|
||||
jvmStack.push(DataType.STRING);
|
||||
return "ldc " + ctx.txt.getText();
|
||||
}
|
||||
|
||||
private String visitBinaryOperation(String name, DemoParser.ExpressionContext ctx) {
|
||||
String instructions = visitChildren(ctx) + "\n" + name;
|
||||
jvmStack.pop();
|
||||
jvmStack.pop();
|
||||
jvmStack.push(DataType.INT);
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPlus(PlusContext ctx) {
|
||||
return visitBinaryOperation("iadd", ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitMinus(DemoParser.MinusContext ctx) {
|
||||
return visitBinaryOperation("isub", ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitDiv(DemoParser.DivContext ctx) {
|
||||
return visitBinaryOperation("idiv", ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitMult(DemoParser.MultContext ctx) {
|
||||
return visitBinaryOperation("imul", ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitRelational(DemoParser.RelationalContext ctx) {
|
||||
int compareNum = compareCount++;
|
||||
String jumpInstruction;
|
||||
|
||||
switch (ctx.operator.getText()) {
|
||||
case "<":
|
||||
jumpInstruction = "if_icmplt";
|
||||
break;
|
||||
case "<=":
|
||||
jumpInstruction = "if_icmple";
|
||||
break;
|
||||
case ">":
|
||||
jumpInstruction = "if_icmpgt";
|
||||
break;
|
||||
case ">=":
|
||||
jumpInstruction = "if_icmpge";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown operator : <" + ctx.operator.getText() + ">");
|
||||
}
|
||||
|
||||
final String instructions = visitChildren(ctx) + "\n" +
|
||||
jumpInstruction + " onTrue" + compareNum + "\n" +
|
||||
"ldc 0\n" +
|
||||
"goto onFalse" + compareNum + "\n" +
|
||||
"onTrue" + compareNum + ":\n" +
|
||||
"ldc 1\n" +
|
||||
"onFalse" + compareNum + ":";
|
||||
jvmStack.pop();
|
||||
jvmStack.pop();
|
||||
jvmStack.push(DataType.INT);
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitAnd(DemoParser.AndContext ctx) {
|
||||
String left = visit(ctx.left);
|
||||
String right = visit(ctx.right);
|
||||
int andNum = andCounter++;
|
||||
jvmStack.pop();
|
||||
jvmStack.pop();
|
||||
jvmStack.push(DataType.INT);
|
||||
|
||||
return left + "\n" +
|
||||
"ifeq onAndFalse" + andNum + "\n" +
|
||||
right + "\n" +
|
||||
"ifeq onAndFalse" + andNum + "\n" +
|
||||
"ldc 1\n" +
|
||||
"goto andEnd" + andNum + "\n" +
|
||||
"onAndFalse" + andNum + ":\n" +
|
||||
"ldc 0\n" +
|
||||
"andEnd" + andNum + ":";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitOr(DemoParser.OrContext ctx) {
|
||||
String left = visit(ctx.left);
|
||||
String right = visit(ctx.right);
|
||||
int orNum = orCounter++;
|
||||
jvmStack.pop();
|
||||
jvmStack.pop();
|
||||
jvmStack.push(DataType.INT);
|
||||
|
||||
return left + "\n" +
|
||||
"ifne onOrTrue" + orNum + "\n" +
|
||||
right + "\n" +
|
||||
"ifne onOrTrue" + orNum + "\n" +
|
||||
"ldc 0\n" +
|
||||
"goto orEnd" + orNum + "\n" +
|
||||
"onOrTrue" + orNum + ":\n" +
|
||||
"ldc 1\n" +
|
||||
"orEnd" + orNum + ":";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPrintln(DemoParser.PrintlnContext ctx) {
|
||||
String arg = visit(ctx.argument);
|
||||
DataType type = jvmStack.pop();
|
||||
return "getstatic java/lang/System/out Ljava/io/PrintStream;\n" +
|
||||
arg + "\n" +
|
||||
"invokevirtual java/io/PrintStream/println(" + type.getJvmType() + ")V\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitPrint(DemoParser.PrintContext ctx) {
|
||||
String arg = visit(ctx.argument);
|
||||
DataType type = jvmStack.pop();
|
||||
return "getstatic java/lang/System/out Ljava/io/PrintStream;\n" +
|
||||
arg + "\n" +
|
||||
"invokevirtual java/io/PrintStream/print(" + type.getJvmType() + ")V\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitVariable(DemoParser.VariableContext ctx) {
|
||||
jvmStack.push(DataType.INT);
|
||||
return "iload " + requireVariableIndex(ctx.varName);
|
||||
}
|
||||
|
||||
private int requireVariableIndex(Token varName) {
|
||||
Integer varIndex = variables.get(varName.getText());
|
||||
if (varIndex == null) {
|
||||
throw new UndeclaredVariableException(varName);
|
||||
}
|
||||
return varIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitVarDeclaration(DemoParser.VarDeclarationContext ctx) {
|
||||
String key = ctx.varName.getText();
|
||||
if (variables.containsKey(key)) {
|
||||
throw new VariableAlreadyDefinedException(ctx.varName);
|
||||
}
|
||||
variables.put(key, variables.size());
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitBranch(DemoParser.BranchContext ctx) {
|
||||
String condition = visit(ctx.condition);
|
||||
jvmStack.pop();
|
||||
String onTrue = visit(ctx.onTrue);
|
||||
String onFalse = visit(ctx.onFalse);
|
||||
int branchNum = branchCounter++;
|
||||
|
||||
return condition + "\n" +
|
||||
"ifne ifTrue" + branchNum + "\n" +
|
||||
onFalse + "\n" +
|
||||
"goto endIf" + branchNum + "\n" +
|
||||
"ifTrue" + branchNum + ":\n" +
|
||||
onTrue + "\n" +
|
||||
"endIf" + branchNum + ":\n";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitAssignment(DemoParser.AssignmentContext ctx) {
|
||||
final String instructions = visit(ctx.expr) + "\n"
|
||||
+ "istore " + requireVariableIndex(ctx.varName) + "\n";
|
||||
jvmStack.pop();
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitFunctionDefinition(DemoParser.FunctionDefinitionContext ctx) {
|
||||
Map<String, Integer> oldVars = this.variables;
|
||||
JvmStack oldJvmStack = jvmStack;
|
||||
jvmStack = new JvmStack();
|
||||
variables = new HashMap<>();
|
||||
visit(ctx.params);
|
||||
String statements = visit(ctx.statements);
|
||||
String instructions = ".method public static " + ctx.funcName.getText() + "(";
|
||||
int numberOfParams = ctx.params.declarations.size();
|
||||
instructions += stringRepeat("I", numberOfParams);
|
||||
instructions += ")I\n" +
|
||||
".limit locals 100\n" +
|
||||
".limit stack 100\n" +
|
||||
(statements == null ? "" : statements + "\n") +
|
||||
visit(ctx.retValue) + "\n" +
|
||||
"ireturn\n" +
|
||||
".end method\n";
|
||||
jvmStack.pop();
|
||||
variables = oldVars;
|
||||
jvmStack = oldJvmStack;
|
||||
return instructions;
|
||||
}
|
||||
|
||||
private String stringRepeat(String string, int numberOfParams) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < numberOfParams; i++) {
|
||||
result.append(string);
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitFunctionCall(DemoParser.FunctionCallContext ctx) {
|
||||
if (!definedFunctions.contains(ctx.funcName.getText(), ctx.args.expressions.size() )) {
|
||||
throw new UndefinedFunctionException(ctx.funcName);
|
||||
}
|
||||
|
||||
String instructions = "";
|
||||
String args = visit(ctx.args);
|
||||
if (args != null) {
|
||||
instructions += args + "\n";
|
||||
}
|
||||
instructions += "invokestatic HelloWorld/" + ctx.funcName.getText() + "(";
|
||||
int numberOfParams = ctx.args.expressions.size();
|
||||
instructions += stringRepeat("I", numberOfParams);
|
||||
instructions +=")I\n";
|
||||
for (int i = 0; i < numberOfParams; i++) {
|
||||
jvmStack.pop();
|
||||
}
|
||||
jvmStack.push(DataType.INT);
|
||||
return instructions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String visitProgram(DemoParser.ProgramContext ctx) {
|
||||
StringBuilder mainCode = new StringBuilder();
|
||||
StringBuilder functions = new StringBuilder();
|
||||
|
||||
for (ParseTree child : ctx.children) {
|
||||
final String instructions = visit(child);
|
||||
if (child instanceof MainStatementContext) {
|
||||
mainCode.append(instructions);
|
||||
} else {
|
||||
functions.append(instructions);
|
||||
}
|
||||
}
|
||||
final String instructions = ".method public static main([Ljava/lang/String;)V\n" +
|
||||
".limit stack 100\n" +
|
||||
".limit locals 100\n" +
|
||||
mainCode + "\n" +
|
||||
"return\n" +
|
||||
".end method\n";
|
||||
|
||||
return functions.append(instructions).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String aggregateResult(String aggregate, String nextResult) {
|
||||
if (aggregate == null) {
|
||||
return nextResult;
|
||||
}
|
||||
if (nextResult == null) {
|
||||
return aggregate;
|
||||
}
|
||||
return aggregate + "\n" + nextResult;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class JvmStack {
|
||||
|
||||
private Deque<DataType> typesOnStack = new LinkedList<>();
|
||||
|
||||
public void push(DataType type) {
|
||||
typesOnStack.push(type);
|
||||
}
|
||||
|
||||
public DataType pop() {
|
||||
return typesOnStack.pop();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import de.letsbuildacompiler.parser.DemoLexer;
|
||||
import de.letsbuildacompiler.parser.DemoParser;
|
||||
import org.antlr.v4.runtime.CharStream;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.antlr.v4.runtime.CodePointCharStream;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
CodePointCharStream input = CharStreams.fromString("print(2 < 2);");
|
||||
System.out.println(compile(input));
|
||||
}
|
||||
|
||||
public static String compile(CharStream input) {
|
||||
DemoLexer lexer = new DemoLexer(input);
|
||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||
DemoParser parser = new DemoParser(tokens);
|
||||
|
||||
ParseTree tree = parser.program();
|
||||
FunctionList definedFunctions = FunctionDefinitionFinder.findFunctions(tree);
|
||||
return createJasminFile(new JasminVisitor(definedFunctions).visit(tree));
|
||||
}
|
||||
|
||||
private static String createJasminFile(String instructions) {
|
||||
return ".class public HelloWorld\n" +
|
||||
".super java/lang/Object\n" +
|
||||
"\n" +
|
||||
instructions;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package de.letsbuildacompiler.compiler.exceptions;
|
||||
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
class CompileException extends RuntimeException {
|
||||
int line;
|
||||
int col;
|
||||
|
||||
CompileException(Token token) {
|
||||
line = token.getLine();
|
||||
col = token.getCharPositionInLine();
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package de.letsbuildacompiler.compiler.exceptions;
|
||||
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class FunctionAlreadyDefinedException extends CompileException{
|
||||
|
||||
private String funcName;
|
||||
|
||||
public FunctionAlreadyDefinedException(Token token) {
|
||||
super(token);
|
||||
funcName = token.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return line + ":" + col + " function already defined : <" + funcName + ">";
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package de.letsbuildacompiler.compiler.exceptions;
|
||||
|
||||
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class UndeclaredVariableException extends CompileException {
|
||||
|
||||
private final String varName;
|
||||
|
||||
public UndeclaredVariableException(Token token) {
|
||||
super(token);
|
||||
varName = token.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return line + ":" + col + " undeclared variable <" + varName + ">";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package de.letsbuildacompiler.compiler.exceptions;
|
||||
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class UndefinedFunctionException extends CompileException {
|
||||
|
||||
private String funcName;
|
||||
|
||||
public UndefinedFunctionException(Token token) {
|
||||
super(token);
|
||||
funcName = token.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return line + ":" + col + " call to undefined function : <" + funcName + ">";
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package de.letsbuildacompiler.compiler.exceptions;
|
||||
|
||||
import org.antlr.v4.runtime.Token;
|
||||
|
||||
public class VariableAlreadyDefinedException extends CompileException {
|
||||
|
||||
private String varName;
|
||||
|
||||
public VariableAlreadyDefinedException(Token token) {
|
||||
super(token);
|
||||
varName = token.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return line + ":" + col + " variable <" + varName + "> already defined";
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
package de.letsbuildacompiler.compiler;
|
||||
|
||||
import de.letsbuildacompiler.compiler.exceptions.FunctionAlreadyDefinedException;
|
||||
import de.letsbuildacompiler.compiler.exceptions.UndeclaredVariableException;
|
||||
import de.letsbuildacompiler.compiler.exceptions.UndefinedFunctionException;
|
||||
import de.letsbuildacompiler.compiler.exceptions.VariableAlreadyDefinedException;
|
||||
import jasmin.ClassFile;
|
||||
import org.antlr.v4.runtime.CharStreams;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
import java.util.Scanner;
|
||||
|
||||
import static org.testng.AssertJUnit.assertEquals;
|
||||
|
||||
public class CompilerTest {
|
||||
|
||||
private Path tempDir;
|
||||
|
||||
@BeforeMethod
|
||||
public void createTempDir() throws IOException {
|
||||
tempDir = Files.createTempDirectory("compilerTest");
|
||||
}
|
||||
|
||||
@AfterMethod
|
||||
public void deleteTempDir() {
|
||||
deleteRecursive(tempDir.toFile());
|
||||
}
|
||||
|
||||
private void deleteRecursive(File file) {
|
||||
if (file.isDirectory()) {
|
||||
for (File f : Objects.requireNonNull(file.listFiles())) {
|
||||
deleteRecursive(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.delete()) {
|
||||
throw new Error("Could not delete file <" + file + ">");
|
||||
}
|
||||
}
|
||||
|
||||
@Test(dataProvider = "provider")
|
||||
@SuppressWarnings("unused")
|
||||
public void runningCode_outputsExpectedText(String description, String code, String expectedText) throws Exception {
|
||||
String actual = compileAndRun(code);
|
||||
assertEquals(expectedText, actual);
|
||||
}
|
||||
|
||||
@DataProvider(name = "provider")
|
||||
public Object[][] provide_code_expectedText() {
|
||||
return new Object[][] {
|
||||
{"plus", "println(1+2);", "3" + System.lineSeparator()},
|
||||
{"chained plus", "println(1+4+42);", "47" + System.lineSeparator()},
|
||||
{"multiple statements", "println(1); println(2);", "1" + System.lineSeparator() + "2" + System.lineSeparator()},
|
||||
{"minus", "println(3-2);", "1" + System.lineSeparator()},
|
||||
{"times", "println(3*3);", "9" + System.lineSeparator()},
|
||||
{"division", "println(6/3);", "2" + System.lineSeparator()},
|
||||
{"truncated division", "println(7/2);", "3" + System.lineSeparator()},
|
||||
{"operator precedence times and divide", "println(8/2*4);", "16" + System.lineSeparator()},
|
||||
{"operator precedence times and plus", "println(2+3*3);", "11" + System.lineSeparator()},
|
||||
{"operator precedence times and minus", "println(9-2*3);", "3" + System.lineSeparator()},
|
||||
{"operator precedence minus and plus", "println(8-2+5);", "11" + System.lineSeparator()},
|
||||
{"int variable", "int foo; foo = 42; println(foo);", "42" + System.lineSeparator()},
|
||||
{"add variable and constant parameters",
|
||||
"int foo; foo = 42; println(foo+2);", "44" + System.lineSeparator()},
|
||||
{"add two variables parameters",
|
||||
"int foo; int bar; foo = 42; bar = 43; println(foo+bar);", "85" + System.lineSeparator()},
|
||||
|
||||
example("functions/simple_function", "4" + System.lineSeparator()),
|
||||
example("functions/scopes", "4" + System.lineSeparator() + "42" + System.lineSeparator()),
|
||||
example("functions/int_parameters", "13" + System.lineSeparator()),
|
||||
example("branch/if_int_false", "42" + System.lineSeparator()),
|
||||
example("branch/if_int_true", "81" + System.lineSeparator()),
|
||||
|
||||
{"lower than true", "println(1 < 2);", "1" + System.lineSeparator()},
|
||||
{"lower than false", "println(2 < 2);", "0" + System.lineSeparator()},
|
||||
{"lower or equal true", "println(1 <= 1);", "1" + System.lineSeparator()},
|
||||
{"lower or equal false", "println(3 <= 2);", "0" + System.lineSeparator()},
|
||||
|
||||
{"greater than true", "println(3 > 2);", "1" + System.lineSeparator()},
|
||||
{"greater than false", "println(3 > 3);", "0" + System.lineSeparator()},
|
||||
{"greater or equal true", "println(3 >= 3);", "1" + System.lineSeparator()},
|
||||
{"greater or equal false", "println(2 >= 3);", "0" + System.lineSeparator()},
|
||||
|
||||
{"and true", "println(1 && 1);", "1" + System.lineSeparator()},
|
||||
{"and left false", "println(0 && 1);", "0" + System.lineSeparator()},
|
||||
{"and right false", "println(1 && 0);", "0" + System.lineSeparator()},
|
||||
example("operators/and-skip-right", "0" + System.lineSeparator() + "0" + System.lineSeparator()),
|
||||
|
||||
{"or false", "println(0 || 0);", "0" + System.lineSeparator()},
|
||||
{"or left true", "println(1 || 0);", "1" + System.lineSeparator()},
|
||||
{"or right true", "println(0 || 1);", "1" + System.lineSeparator()},
|
||||
example("operators/or-skip-right", "1" + System.lineSeparator() + "1" + System.lineSeparator()),
|
||||
|
||||
{"print", "print(42);", "42"},
|
||||
{"print string litteral", "print(\"Hello world\");", "Hello world"},
|
||||
};
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UndeclaredVariableException.class,
|
||||
expectedExceptionsMessageRegExp = "1:8 undeclared variable <x>")
|
||||
public void compilingCode_throwsUndeclaredVariableException_ifReadingUndefinedVariable() throws Exception {
|
||||
compileAndRun("println(x);");
|
||||
// evaluation performed by expected exception.
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UndeclaredVariableException.class,
|
||||
expectedExceptionsMessageRegExp = "1:0 undeclared variable <x>")
|
||||
public void compilingCode_throwsUndeclaredVariableException_ifWritingUndefinedVariable() throws Exception {
|
||||
compileAndRun("x = 5;");
|
||||
// evaluation performed by expected exception.
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = VariableAlreadyDefinedException.class,
|
||||
expectedExceptionsMessageRegExp = "2:4 variable <x> already defined")
|
||||
public void compilingCode_throwsAlreadyDefinedException_whenDefiningAlreadyDefinedVariable() throws Exception {
|
||||
compileAndRun("int x;" + System.lineSeparator() +
|
||||
"int x;");
|
||||
// evaluation performed by expected exception.
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = UndefinedFunctionException.class,
|
||||
expectedExceptionsMessageRegExp = "1:8 call to undefined function : <undefinedFunction>")
|
||||
public void compilingCode_throwsUndefinedFunctionException_whenCallingUndefinedFunction() throws Exception {
|
||||
compileAndRun("println(undefinedFunction());");
|
||||
// evaluation performed by expected exception.
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = FunctionAlreadyDefinedException.class,
|
||||
expectedExceptionsMessageRegExp = "2:4 function already defined : <x>")
|
||||
public void compilingCode_throwsAlreadyDefinedFunctionException_whenDefiningFunctionTwice() throws Exception {
|
||||
compileAndRun("int x() { return 42; }\n" +
|
||||
"int x() { return 45; }");
|
||||
// evaluation performed by expected exception.
|
||||
}
|
||||
|
||||
private String compileAndRun(String code) throws Exception {
|
||||
code = Main.compile(CharStreams.fromString(code));
|
||||
ClassFile classFile = new ClassFile();
|
||||
classFile.readJasmin(new StringReader(code), "", false);
|
||||
Path outputPath = tempDir.resolve(classFile.getClassName() + ".class");
|
||||
try (OutputStream outputStream = Files.newOutputStream(outputPath)) {
|
||||
classFile.write(outputStream);
|
||||
}
|
||||
return runJavaClass(tempDir, classFile.getClassName());
|
||||
}
|
||||
|
||||
private String runJavaClass(Path dir, String className) throws IOException {
|
||||
Process process = Runtime.getRuntime().exec(new String[]{"java", "-cp", dir.toString(), className});
|
||||
try (InputStream in = process.getInputStream()) {
|
||||
return new Scanner(in).useDelimiter("\\A").next();
|
||||
}
|
||||
}
|
||||
|
||||
private String[] example(String name, String expectedResult) throws IllegalArgumentException {
|
||||
try (InputStream in = CompilerTest.class.getClassLoader().getResourceAsStream("examples/" + name)) {
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException("No such example " + name);
|
||||
}
|
||||
String code = new Scanner(in, "UTF-8").useDelimiter("\\A").next();
|
||||
|
||||
return new String[] {name, code, expectedResult};
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("No such example " + name);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
if (0) {
|
||||
println(81);
|
||||
} else {
|
||||
println(42);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
if(1){
|
||||
println(81);
|
||||
} else{
|
||||
println(42);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
println(add(5, 8));
|
@ -0,0 +1,9 @@
|
||||
int i;
|
||||
i = 42;
|
||||
int randomNumber() {
|
||||
int i;
|
||||
i = 4;
|
||||
return i;
|
||||
}
|
||||
println(randomNumber());
|
||||
println(i);
|
@ -0,0 +1,5 @@
|
||||
int number() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
println(number());
|
@ -0,0 +1,6 @@
|
||||
int x(int i) {
|
||||
println(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
println(x(0) && x(1));
|
@ -0,0 +1,6 @@
|
||||
int x(int i) {
|
||||
println(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
println(x(1) || x(0));
|
@ -0,0 +1,5 @@
|
||||
if (0) {
|
||||
println(81);
|
||||
} else {
|
||||
println(42);
|
||||
}
|
5
compiler/target/test-classes/examples/branch/if_int_true
Normal file
5
compiler/target/test-classes/examples/branch/if_int_true
Normal file
@ -0,0 +1,5 @@
|
||||
if(1){
|
||||
println(81);
|
||||
} else{
|
||||
println(42);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
int add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
println(add(5, 8));
|
9
compiler/target/test-classes/examples/functions/scopes
Normal file
9
compiler/target/test-classes/examples/functions/scopes
Normal file
@ -0,0 +1,9 @@
|
||||
int i;
|
||||
i = 42;
|
||||
int randomNumber() {
|
||||
int i;
|
||||
i = 4;
|
||||
return i;
|
||||
}
|
||||
println(randomNumber());
|
||||
println(i);
|
@ -0,0 +1,5 @@
|
||||
int number() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
println(number());
|
@ -0,0 +1,6 @@
|
||||
int x(int i) {
|
||||
println(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
println(x(0) && x(1));
|
@ -0,0 +1,6 @@
|
||||
int x(int i) {
|
||||
println(i);
|
||||
return i;
|
||||
}
|
||||
|
||||
println(x(1) || x(0));
|
Reference in New Issue
Block a user