commit e854da56aa7ea0ca8f166f2c2d099facdb43a4cb Author: quirinecker Date: Sat Nov 8 15:33:53 2025 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1fcb152 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +out diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/exercise05.iml b/.idea/exercise05.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/exercise05.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/lib.xml b/.idea/libraries/lib.xml new file mode 100644 index 0000000..0277ad2 --- /dev/null +++ b/.idea/libraries/lib.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/lib2.xml b/.idea/libraries/lib2.xml new file mode 100644 index 0000000..48a1569 --- /dev/null +++ b/.idea/libraries/lib2.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6f29fee --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bec10bb --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/MicroJava Compiler/MicroJava Compiler.iml b/MicroJava Compiler/MicroJava Compiler.iml new file mode 100644 index 0000000..228f0a9 --- /dev/null +++ b/MicroJava Compiler/MicroJava Compiler.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/MicroJava Compiler/Prims.mj b/MicroJava Compiler/Prims.mj new file mode 100644 index 0000000..fd9ea30 --- /dev/null +++ b/MicroJava Compiler/Prims.mj @@ -0,0 +1,27 @@ +program Prims +{ + + void print_prims (int n) + int[] numbers; + int i, j; + { + numbers = new int[n]; + i = 0; + while(i < n) { + if(1 < i && numbers[i] == 0) { + print(i); + print('\n'); + j = i; + while(j < n) { + numbers[j]++; + j += i; + } + } + i++; + } + } + + void main () { + print_prims(1000); + } +} diff --git a/MicroJava Compiler/Prims.obj b/MicroJava Compiler/Prims.obj new file mode 100644 index 0000000..2b79bad Binary files /dev/null and b/MicroJava Compiler/Prims.obj differ diff --git a/MicroJava Compiler/StudentList.mj b/MicroJava Compiler/StudentList.mj new file mode 100644 index 0000000..56f13d7 --- /dev/null +++ b/MicroJava Compiler/StudentList.mj @@ -0,0 +1,87 @@ +program StudentList + final int MAXLEN = 20; + + class Student { + int matrNr; + char[] name; + } + + Student[] list; + int stCnt; + +{ + void printString (char[] str) + int i; + { + if (str != null) { + i = 0; + while (i < len(str)) { + print(str[i]); + i++; + } + } + } + + void init () { + list = new Student[MAXLEN]; + stCnt = 0; + } + + void add (Student s) + int i; + { + /* insert sorted by matrNr */ + i = stCnt - 1; + while (i >= 0 && s.matrNr < list[i].matrNr) { + list[i+1] = list[i]; + i--; + } + list[i+1] = s; + stCnt++; + } + + int find (int matrNr) + int l, r, x; + + { + /* binary search */ + l = 0; r = stCnt-1; + while (l <= r && matrNr != list[x].matrNr) { + x = (l+r)/2; + if (matrNr < list[x].matrNr) r = x-1; + else l = x+1; + } + if (matrNr == list[x].matrNr) return x; + + return -1; + } + + void printStudent (int i) { + print('m'); print('['); print(i); print(']'); print('='); + print(list[i].matrNr); print(','); printString(list[i].name); + print('\n'); + } + + void main() + Student s; + { + init(); + s = new Student; s.matrNr = 1234567; s.name = new char[3]; + s.name[0] = 'X'; s.name[1] = '\\'; s.name[2] = 'Y'; + add(s); + s = new Student; s.matrNr = 9876543; s.name = new char[4]; + s.name[0] = 'M'; s.name[1] = 'r'; s.name[2] = '.'; s.name[3] = 'X'; + add(s); + s = new Student; s.matrNr = 9090900; s.name = new char[2]; + s.name[0] = 'A'; s.name[1] = 'l'; + add(s); + + printStudent(0); + printStudent(1); + printStudent(2); + + print(9876543); print(' '); printString(list[find(9876543)].name); print('\n'); + print(1234567); print(' '); printString(list[find(1234567)].name); print('\n'); + print(9090900); print(' '); printString(list[find(9090900)].name); print('\n'); + } +} diff --git a/MicroJava Compiler/StudentList.obj b/MicroJava Compiler/StudentList.obj new file mode 100644 index 0000000..4f7a3b3 Binary files /dev/null and b/MicroJava Compiler/StudentList.obj differ diff --git a/MicroJava Compiler/StudentListOutput.txt b/MicroJava Compiler/StudentListOutput.txt new file mode 100644 index 0000000..806a761 --- /dev/null +++ b/MicroJava Compiler/StudentListOutput.txt @@ -0,0 +1,6 @@ +m[0]=1234567,X\Y +m[1]=9090900,Al +m[2]=9876543,Mr.X +9876543 Mr.X +1234567 X\Y +9090900 Al diff --git a/MicroJava Compiler/TestProgram.mj b/MicroJava Compiler/TestProgram.mj new file mode 100644 index 0000000..466d690 --- /dev/null +++ b/MicroJava Compiler/TestProgram.mj @@ -0,0 +1,75 @@ +program Test + + class Inner { + int i; + char[] a; + char ch; + } + + int ii, jj; + int[] arr; + char a, b; + char[] arr2; + + final int const1 = 3; + final char const2 = 'w'; +{ + int Func (int i) { return i*2; } + + void Math () + int i, j, k, l, m; + Inner in; + Inner[] arr; + { + arr = new Inner[12]; + arr[7] = new Inner; + arr[7].i = 17; + + in = new Inner; + in.i = 5; + i = 2; j=3; k=4; + l = i*j*k*4*Func(2)*Func(k); + m = l+i-k+(const1%ord(const2))*in.i; + i = m+arr[7].i; + i += ii; + if (i == 3119) print(1); + } + + void Structs () + int i; + { + while (i<17) { + if (i>10) ii++; + else if (i<5) ii--; + else ii+=8; + i++; + } + ii++; + if (ii == 67) print(2); + i = 3; + while (i<15) { + ii*=2; + if (i == 10) break; + i++; + } + if(ii == 17152) print(3); + } + + int Call(int val) + int i; + { + if (val<10) val = Call(val+1)-1; + else val = -2; + return val; + } + + void main () { + ii = 17; + Math(); + Structs(); + ii = 1; + ii = Call(ii); + if (ii == -11) print(4); + print('\n'); + } +} diff --git a/MicroJava Compiler/TestProgram.obj b/MicroJava Compiler/TestProgram.obj new file mode 100644 index 0000000..70b1118 Binary files /dev/null and b/MicroJava Compiler/TestProgram.obj differ diff --git a/MicroJava Compiler/Trap.mj b/MicroJava Compiler/Trap.mj new file mode 100644 index 0000000..e735ccf --- /dev/null +++ b/MicroJava Compiler/Trap.mj @@ -0,0 +1,11 @@ +program Test +{ + + int Trap() + { + } + + void main () { + print(Trap()); + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Compiler.java b/MicroJava Compiler/src/ssw/mj/Compiler.java new file mode 100644 index 0000000..141a4a1 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Compiler.java @@ -0,0 +1,56 @@ +package ssw.mj; + +import ssw.mj.impl.Parser; +import ssw.mj.impl.Scanner; + +import java.io.*; + +/** + * Compiler is the driver for the MicroJava-Compiler. + *

+ * Execute
+ * java ssw.mj.Compiler <MJ-Source-Filename>
+ * to start compilation. + */ +public class Compiler { + + private static String objFileName(String s) { + int i = s.lastIndexOf('.'); + if (i < 0) { + return s + ".obj"; + } + return s.substring(0, i) + ".obj"; + } + + public static void main(String[] args) { + // --- get the filename + if (args.length != 1) { + System.out.println("usage: java Compiler filename.mj"); + return; + } + String inFilename = args[0]; + String outFilename = objFileName(inFilename); + + try { + Scanner scanner = new Scanner(new BufferedReader(new FileReader(inFilename))); + + System.out.println("-----------------------------------"); + System.out.println("Parsing file " + inFilename); + + Parser parser = new Parser(scanner); + parser.parse(); + if (scanner.errors.numErrors() == 0) { + parser.code.write(new BufferedOutputStream(new FileOutputStream(outFilename))); + } + + if (scanner.errors.numErrors() > 0) { + System.out.println(scanner.errors.dump()); + System.out.println(scanner.errors.numErrors() + " errors."); + } else { + System.out.println("No errors."); + } + } catch (IOException ex) { + System.out.println("I/O Error: " + ex.getMessage()); + } + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Errors.java b/MicroJava Compiler/src/ssw/mj/Errors.java new file mode 100644 index 0000000..6a1c08a --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Errors.java @@ -0,0 +1,140 @@ +package ssw.mj; + +import java.io.Serial; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +public class Errors { + public static class PanicMode extends Error { + @Serial + private static final long serialVersionUID = 1L; + // Nothing to implement here. + } + + public enum Message { + // ----- error messages first used in ScannerTest + EMPTY_CHARCONST("empty character constant"), + UNDEFINED_ESCAPE("undefined escape character sequence ''\\{0}''"), + MISSING_QUOTE("missing '' at end of character constant"), + INVALID_CHAR("invalid character {0}"), + BIG_NUM("{0} too big for integer constant"), + EOF_IN_COMMENT("unexpected end of file in comment"), + EOF_IN_CHAR("unexpected end of file in char"), + ILLEGAL_LINE_END("illegal line end in character constant"), + + // ----- error messages first used in ParserTest + INVALID_ADD_OP("unexpected token. + or - expected"), // cannot occur in current grammar version, but still must be placed correctly (in default case of respective switch) + INVALID_ASSIGN_OP("unexpected token. =, +=, -=, *=, /=, %= expected"), // cannot occur in current grammar version, but still must be placed correctly (in default case of respective switch) + INVALID_MUL_OP("unexpected token. *, /, % expected"), // cannot occur in current grammar version, but still must be placed correctly (in default case of respective switch) + INVALID_METHOD_DECL("invalid start of method decl: type name or void expected"), // cannot occur in current grammar version, but still must be placed correctly (in default case of respective switch) + INVALID_STATEMENT("unexpected token. identifier, if, while, break, return, read, print, '{' or ; expected"), // cannot occur in current grammar version, but still must be placed correctly (in default case of respective switch) + INVALID_DESIGNATOR_STATEMENT("unexpected token. assignment token (=, +=, -=, *=, /=, %=), method call (\"(\"), increment (++) or decrement (--) expected"), + INVALID_CONST_TYPE("number or character constant expected"), + INVALID_FACTOR("unexpected token. identifier, number, character constant, new or \"(\" expected"), + INVALID_REL_OP("unexpected token. ==, !=, >, >=, <, <= expected"), + TOKEN_EXPECTED("{0} expected"), + + // ----- error messages first used in RecoverTest + DECLARATION_RECOVERY("start or follow of declaration expected"), + METHOD_DECL_RECOVERY("start or follow of method declaration expected"), + STATEMENT_RECOVERY("start or follow of statement expected"), + + // ----- error messages first used in SymbolTableTest + INCOMPATIBLE_TYPES("incompatible types"), // mainly used in SimpleCodeGenerationTest, but also in SymbolTableTest + DUPLICATE_NAME_IN_SCOPE("{0} already declared in current scope"), + MAIN_WITH_PARAMS("main method must not have any parameters"), + MAIN_NOT_VOID("main method must return void"), + FIELD_NOT_FOUND("{0} is not a field"), + TYPE_EXPECTED("type expected"), + NAME_NOT_FOUND("{0} not found"), + TOO_MANY_FIELDS("too many fields"), + TOO_MANY_GLOBALS("too many global variables"), + TOO_MANY_LOCALS("too many local variables"), + CLASS_TYPE_EXPECTED("can only instantiate new object for a class"), + + // ----- error messages first used in SimpleCodeGenerationTest + ARRAY_INDEX_EXPECTS_INT("array index must be an integer"), + ARRAY_SIZE_EXPECTS_INT("array size must be an integer"), + CANNOT_STORE_TO_READONLY("cannot store to readonly operand of kind {0}"), + VOID_CALL_IN_EXPRESSION("cannot use void method as part of expression"), + MAIN_NOT_FOUND("mainPC is -1, main not found (did you forget to set code.mainPC? ;))"), + INDEXED_ACCESS_TO_NON_ARRAY("indexed object is not an array"), + FIELD_ACCESS_TO_NON_CLASS("accessed object is not of kind class"), + ILLEGAL_OPERAND_KIND("cannot create operand symbol table object of type {0}"), + CANNOT_LOAD_OPERAND("already loaded (stack) or loadable operand (const, local, static, field, array element) expected"), + ILLEGAL_PRINT_ARGUMENT("can only print int or char values"), + ILLEGAL_READ_ARGUMENT("can only read int or char values"), + INC_DEC_EXPECTS_INT("increment and decrement only allowed for int"), + UNARY_MINUS_EXPECTS_INT("unary minus only allowed for int"), + + // ----- error messages first used in CodeGenerationTest + ILLEGAL_REFERENCE_COMPARISON("only equality and unequality checks are allowed for reference types"), + ILLEGAL_METHOD_RETURN_TYPE("methods may only return int or char"), + WRONG_ARGUMENT_COUNT("number of arguments and formal parameters does not match"), + BREAK_OUTSIDE_LOOP("break is not within a loop"), + CALL_TO_NON_METHOD("called object is not a method"), + ARGUMENT_TYPE_MISMATCH("argument type does not match formal parameter type"), + MISSING_RETURN_VALUE("return expression required in non-void method"), + UNEXPECTED_RETURN_VALUE("no return expression allowed in void method"), + RETURN_TYPE_MISMATCH("return type must match method type"); + + private final String msg; + + Message(String msg) { + this.msg = msg; + } + + public String format(Object... params) { + int expectedParams = 0; + while (msg.contains("{" + expectedParams + "}")) { + expectedParams++; + } + if (params.length != expectedParams) { + throw new Error("incorrect number of error message parameters. Expected %d but got %d".formatted(expectedParams, params.length)); + } + return MessageFormat.format(msg, params); + } + } + + /** + * List of error messages. + */ + private final List errors; + + /** + * Initialization (must be called before compilation). + */ + public Errors() { + errors = new ArrayList<>(); + } + + /** + * Add a new error message to the list of errors. + */ + public void error(int line, int col, Message msg, Object... msgParams) { + errors.add("-- line " + line + " col " + col + ": " + msg.format(msgParams)); + } + + /** + * Returns the number of errors. + */ + public int numErrors() { + return errors.size(); + } + + /** + * String representation for JUnit test cases. + */ + public String dump() { + StringBuilder sb = new StringBuilder(); + for (String error : errors) { + sb.append(error).append("\n"); + } + return sb.toString(); + } + + public List getErrors() { + return errors; + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Interpreter.java b/MicroJava Compiler/src/ssw/mj/Interpreter.java new file mode 100644 index 0000000..1ae825c --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Interpreter.java @@ -0,0 +1,517 @@ +// MicroJava Virtual Machine +// ------------------------- +// Syntax: java ssw.mj.Run fileName [-debug] +// =========================================================================== +// by Hanspeter Moessenboeck, 2002-10-28 +// edited by Albrecht Woess, 2002-10-30 +package ssw.mj; + +import ssw.mj.impl.Code; +import ssw.mj.impl.Code.OpCode; + +import java.io.IOException; + +public class Interpreter { + + private final boolean debug; // debug output on or off + private final byte[] code; // code array + private final int[] data; // global data + private final int[] heap; // dynamic heap + private final int[] stack; // expression stack + private final int[] local; // method stack + private final int startPC; // address of main() method + private int pc; // program counter + private int fp, sp; // frame pointer, stack pointer on method stack + private int esp; // expression stack pointer + private int free; // next free heap address + private static final int heapSize = 100000, // size of the heap in words + mStackSize = 4000, // size of the method stack in words + eStackSize = 30; // size of the expression stack in words + + private void write(String s, int len) { + for (int i = 0; i < len; i++) { + io.write(' '); + } + for (int i = 0; i < s.length(); i++) { + io.write(s.charAt(i)); + } + } + + public static class BufferIO implements IO { + + private final StringBuffer output; + private final String input; + + private int inputPos; + + public BufferIO(String input) { + output = new StringBuffer(); + this.input = input; + } + + @Override + public char read() { + if (inputPos >= input.length()) { + return 0; + } + + return input.charAt(inputPos++); + } + + @Override + public void write(char c) { + output.append(c); + } + + public String getOutput() { + return output.toString(); + } + } + + public static final IO ConsoleIO = new IO() { + + @Override + public char read() { + try { + int i = System.in.read(); + if (i == -1) { + return 0; + } + return (char) i; + } catch (IOException ex) { + return 0; + } + } + + @Override + public void write(char c) { + System.out.print(c); + } + }; + + public interface IO { + char read(); + + void write(char c); + } + + private final IO io; + + public Interpreter(byte[] code, int startPC, int dataSize, IO io, boolean debug) { + this.code = code; + this.startPC = startPC; + this.io = io; + this.debug = debug; + heap = new int[heapSize]; // fixed sized heap + data = new int[dataSize]; // global data as specified in + // classfile + stack = new int[eStackSize]; // expression stack + local = new int[mStackSize]; // method stack + fp = 0; + sp = 0; + esp = 0; + free = 1; // no block should start at address 0 + } + + // ----- expression stack + private void push(int val) throws IllegalStateException { + if (esp == eStackSize) { + throw new IllegalStateException("expression stack overflow"); + } + stack[esp++] = val; + } + + private int pop() throws IllegalStateException { + if (esp == 0) { + throw new IllegalStateException("expression stack underflow"); + } + return stack[--esp]; + } + + // ----- method stack + private void PUSH(int val) throws IllegalStateException { + if (sp == mStackSize) { + throw new IllegalStateException("method stack overflow"); + } + local[sp++] = val; + } + + private int POP() throws IllegalStateException { + if (sp == 0) { + throw new IllegalStateException("method stack underflow"); + } + return local[--sp]; + } + + // ----- instruction fetch + private byte next(boolean dbgPrint) { + byte b = code[pc++]; + if (debug && dbgPrint) { + System.out.print(b + " "); + } + return b; + } + + private short next2(boolean dbgPrint) { + short s = (short) (((next(false) << 8) + + (next(false) & 0xff)) << 16 >> 16); + if (debug && dbgPrint) { + System.out.print(s + " "); + } + return s; + } + + private int next4() { + return next4(true); + } + + private int next4(boolean dbgPrint) { + int n = (next2(false) << 16) + (next2(false) & 0xffff); + if (debug && dbgPrint) { + System.out.print(n + " "); + } + return n; + } + + /** + * Allocate heap block of size bytes + */ + private int alloc(int size) throws IllegalStateException { + int adr = free; + free += ((size + 3) >> 2); // skip to next free adr + // (>> 2 to convert byte to word) + if (free > heapSize) { + throw new IllegalStateException("heap overflow"); + } + return adr; + } + + /** + * Retrieve byte n from val. Byte 0 is MSB + */ + private static byte getByte(int val, int n) { + return (byte) (val << (8 * n) >>> 24); + } + + /** + * Replace byte n in val by b + */ + private static int setByte(int val, int n, byte b) { + int delta = (3 - n) * 8; + int mask = ~(255 << delta); // mask all 1 except on chosen byte + int by = (b & 255) << delta; + return (val & mask) ^ by; + } + + /** + * Read int from standard input stream + */ + private int readInt() { + int val = 0; + int prev = ' '; + int b = io.read(); + while (b < '0' || b > '9') { + prev = b; + b = io.read(); + } + while (b >= '0' && b <= '9') { + val = 10 * val + b - '0'; + b = io.read(); + } + if (prev == '-') { + val = -val; + } + return val; + } + + private void printInstr() { + int op = code[pc - 1]; + OpCode opCode = Code.OpCode.get(op); + String instr = (opCode != null) ? opCode.cleanName() : "???"; + System.out.printf("%5d: %s ", pc - 1, instr); + } + + private void printStack() { + for (int i = 0; i < esp; i++) { + System.out.print(stack[i] + " "); + } + System.out.println(); + } + + // ----- actual interpretation + public void run() throws IllegalStateException { + Code.OpCode op; + int adr, val, val2, off, idx, len, i; + pc = startPC; + + if (debug) { // header for debug output + System.out.println(); + System.out.println(" pos: instruction operands"); + System.out.println(" | expressionstack"); + System.out.println("-----------------------------"); + } + + for (; ; ) { // terminated by return instruction + op = Code.OpCode.get(next(false)); + if (debug) { + printInstr(); + } + + switch (op) { + // load/store local variables + case load -> push(local[fp + next(true)]); + case load_0, load_1, load_2, load_3 -> push(local[fp + op.code() - OpCode.load_0.code()]); // mapping + + // on + // range + // 0..3 + case store -> local[fp + next(true)] = pop(); + case store_0, store_1, store_2, store_3 -> local[fp + op.code() - OpCode.store_0.code()] = pop(); // mapping + + // on + // range + // 0..3 + // load/store global variables + case getstatic -> push(data[next2(true)]); + case putstatic -> data[next2(true)] = pop(); + + + // load/store object fields + case getfield -> { + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + push(heap[adr + next2(true)]); + } + case putfield -> { + val = pop(); + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + heap[adr + next2(true)] = val; + } + + // load constants + case const_0, const_1, const_2, const_3, const_4, const_5 -> + push(op.code() - OpCode.const_0.code()); // map opcode to + + // 0..5 + case const_m1 -> push(-1); + case const_ -> push(next4()); + + + // arithmetic operations + case add -> push(pop() + pop()); + case sub -> push(-pop() + pop()); + case mul -> push(pop() * pop()); + case div -> { + val = pop(); + if (val == 0) { + throw new IllegalStateException("division by zero"); + } + push(pop() / val); + } + case rem -> { + val = pop(); + if (val == 0) { + throw new IllegalStateException("division by zero"); + } + push(pop() % val); + } + case neg -> push(-pop()); + case shl -> { + val = pop(); + push(pop() << val); + } + case shr -> { + val = pop(); + push(pop() >> val); + } + case inc -> { + off = fp + next(true); + local[off] += next(true); + } + + // object creation + case new_ -> push(alloc(next2(true) * 4)); + case newarray -> { + val = next(true); + len = pop(); + if (val == 0) { + adr = alloc(len + 4); + } else { + adr = alloc(len * 4 + 4); + } + heap[adr] = len; + push(adr + 1); // skip length field of array + } + + // array access + case aload -> { + idx = pop(); + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + len = heap[adr - 1]; + if (idx < 0 || idx >= len) { + throw new IllegalStateException("index out of bounds"); + } + push(heap[adr + idx]); + } + case astore -> { + val = pop(); + idx = pop(); + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + len = heap[adr - 1]; + if (debug) { + System.out.println("\nArraylength = " + len); + System.out.println("Address = " + adr); + System.out.println("Index = " + idx); + System.out.println("Value = " + val); + } + if (idx < 0 || idx >= len) { + throw new IllegalStateException("index out of bounds"); + } + heap[adr + idx] = val; + } + case baload -> { + idx = pop(); + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + len = heap[adr - 1]; + if (idx < 0 || idx >= len) { + throw new IllegalStateException("index out of bounds"); + } + push(getByte(heap[adr + idx / 4], idx % 4)); + } + case bastore -> { + val = pop(); + idx = pop(); + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + len = heap[adr - 1]; + if (idx < 0 || idx >= len) { + throw new IllegalStateException("index out of bounds"); + } + heap[adr + idx / 4] = setByte(heap[adr + idx / 4], idx % 4, + (byte) val); + } + case arraylength -> { + adr = pop(); + if (adr == 0) { + throw new IllegalStateException("null reference used"); + } + push(heap[adr - 1]); + } + + // stack manipulation + case pop -> pop(); + case dup -> { + val = pop(); + push(val); + push(val); + } + case dup2 -> { + val = pop(); + val2 = pop(); + push(val2); + push(val); + push(val2); + push(val); + } + + // jumps + case jmp -> { + off = next2(true); + pc += off - 3; + } + case jeq, jne, jlt, jle, jgt, jge -> { + off = next2(true); + val2 = pop(); + val = pop(); + boolean cond = false; + switch (op) { + case jeq -> cond = val == val2; + case jne -> cond = val != val2; + case jlt -> cond = val < val2; + case jle -> cond = val <= val2; + case jgt -> cond = val > val2; + case jge -> cond = val >= val2; + default -> { + assert false; + } + } + if (cond) { + pc += off - 3; + } + } + + // method calls + case call -> { + off = next2(true); + PUSH(pc); + pc += off - 3; + } + case return_ -> { + if (sp == 0) { + return; + } + pc = POP(); + } + case enter -> { + int psize = next(true); + int lsize = next(true); + PUSH(fp); + fp = sp; + for (i = 0; i < lsize; i++) { + PUSH(0); + } + assert sp == (fp + lsize); + for (i = psize - 1; i >= 0; i--) { + local[fp + i] = pop(); + } + } + case exit -> { + sp = fp; + fp = POP(); + } + + // I/O + case read -> push(readInt()); + case print -> { + len = pop(); + val = pop(); + String s = String.valueOf(val); + len = len - s.length(); + write(s, len); + } + case bread -> push(io.read()); + case bprint -> { + len = pop() - 1; + val = pop(); + write(Character.toString((char) val), len); + } + case nop -> { + } + // nothing to do + case trap -> throw new IllegalStateException("trap(" + next(true) + ")"); + default -> throw new IllegalStateException("wrong opcode " + op); + } + if (debug) { + System.out.println(); + System.out.print(" | "); + printStack(); + } + } + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Recorder.java b/MicroJava Compiler/src/ssw/mj/Recorder.java new file mode 100644 index 0000000..7b30cd4 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Recorder.java @@ -0,0 +1,158 @@ +package ssw.mj; + +import com.google.gson.Gson; +import ssw.mj.scanner.Token; + +import java.util.*; + +public class Recorder { + /** + * Result class for Gson serialization + */ + public static class RecorderTraceEntry { + public final String in; + public final String name; + public final Map params; + public final TraceEntryType type; + public final TraceEntryOperation operation; + public final Token laToken; + public final Token token; + public final transient RecorderTraceEntry parent; // transient <=> do not serialize + + public RecorderTraceEntry(TraceEntryType type, TraceEntryOperation operation, String in, String name, Map params, Token token, Token laToken, RecorderTraceEntry parent) { + this.type = type; + this.operation = operation; + this.in = in; + this.name = name; + this.params = params; + this.token = token; + this.laToken = laToken; + this.parent = parent; + } + } + + public static final Gson gson = new Gson(); + + // Type of operation executed + public enum TraceEntryOperation { + enter, exit + } + + // Type of trace + public enum TraceEntryType { + check, error, scan, custom, recover + } + + public Recorder() { + this.trace = new ArrayList<>(); + } + + private final List trace; + private RecorderTraceEntry currentEntry = null; + + private final Map idIndex = new HashMap<>(); + + /** + * Record a check operation + * + * @param laToken current token + * @param expected expected token kind + */ + public void checkEnter(Token token, Token laToken, Token.Kind expected) { + enter(TraceEntryType.check, "check", token, laToken, Map.of("expected", expected.toString())); + } + + /** + * Record a scan operation + * + * @param laToken current token + */ + public void scanEnter(Token token, Token laToken) { + enter(TraceEntryType.scan, "scan", token, laToken, Map.of()); + } + + /** + * Record an error operation + * + * @param laToken current token + * @param error error message + * @param msgParams parameters for error message + */ + public void errorEnter(Token token, Token laToken, Errors.Message error, Object[] msgParams) { + enter(TraceEntryType.error, "error", token, laToken, Map.of("type", error.toString(), "message", msgParams.length == 0 ? error.format() : error.format(msgParams))); + } + + /** + * Record a recover operation, i.e., a method repeatedly scanning to recover from an error + * + * @param laToken current token + * @param name name of function + */ + public void recoverEnter(Token token, Token laToken, String name) { + enter(TraceEntryType.recover, name, token, laToken, Map.of()); + } + + /** + * Record an enter operation for a custom function, i.e., a grammar rule + * + * @param laToken current token + * @param name name of function + */ + public void customEnter(Token token, Token laToken, String name) { + enter(TraceEntryType.custom, name, token, laToken, Map.of()); + } + + /** + * Record an enter trace for an arbitrary operation + * + * @param type type of operation + * @param name name of function + * @param laToken current token + * @param params parameters of function + */ + private void enter(TraceEntryType type, String name, Token token, Token laToken, Map params) { + String in = currentEntry == null ? null : currentEntry.name; + + currentEntry = new RecorderTraceEntry(type, TraceEntryOperation.enter, in, getName(name), params, token, laToken, currentEntry); + + trace.add(currentEntry); + } + + /** + * Record an exit trace for the current operation + * + * @param laToken current token + */ + public void exit(Token token, Token laToken) { + if (currentEntry == null) { + throw new IllegalStateException("Cannot exit trace, no trace is active"); + } + + trace.add(new RecorderTraceEntry(currentEntry.type, TraceEntryOperation.exit, currentEntry.in, currentEntry.name, currentEntry.params, token, laToken, null)); + + currentEntry = currentEntry.parent; + } + + /** + * Reset the recorder + */ + public void reset() { + trace.clear(); + currentEntry = null; + idIndex.clear(); + } + + public List getTrace() { + return trace; + } + + private String getName(String name) { + if (idIndex.containsKey(name)) { + idIndex.put(name, idIndex.get(name) + 1); + } else { + idIndex.put(name, 1); + } + + return name + "$" + idIndex.get(name); + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Run.java b/MicroJava Compiler/src/ssw/mj/Run.java new file mode 100644 index 0000000..0804d57 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Run.java @@ -0,0 +1,84 @@ +// MicroJava Virtual Machine +// ------------------------- +// Syntax: java ssw.mj.Run fileName [-debug] +// =========================================================================== +// by Hanspeter Moessenboeck, 2002-10-28 +// edited by Albrecht Woess, 2002-10-30 +package ssw.mj; + +import java.io.*; + +public class Run { + + // ----- VM internals + static Interpreter load(String name, boolean debug) throws IOException { + int codeSize; + byte[] sig = new byte[2]; + DataInputStream in = new DataInputStream(new FileInputStream(name)); + in.read(sig, 0, 2); + if (sig[0] != 'M' || sig[1] != 'J') { + in.close(); + throw new FormatException("wrong marker"); + } + codeSize = in.readInt(); + if (codeSize <= 0) { + in.close(); + throw new FormatException("codeSize <= 0"); + } + int dataSize = in.readInt(); + if (dataSize < 0) { + in.close(); + throw new FormatException("dataSize < 0"); + } + int startPC = in.readInt(); + if (startPC < 0 || startPC >= codeSize) { + in.close(); + throw new FormatException("startPC not in code area"); + } + byte[] code = new byte[codeSize]; + in.read(code, 0, codeSize); + in.close(); + + return new Interpreter(code, startPC, dataSize, Interpreter.ConsoleIO, debug); + } + + public static void main(String[] args) { + String fileName = null; + boolean debug = false; + for (String arg : args) { + if (arg.equals("-debug")) { + debug = true; + } else { + fileName = arg; + } + } + if (fileName == null) { + System.out.println("Syntax: java ssw.mj.Run filename [-debug]"); + return; + } + try { + Interpreter r = load(fileName, debug); + + long startTime = System.currentTimeMillis(); + r.run(); + + System.out.print("\nCompletion took " + (System.currentTimeMillis() - startTime) + " ms"); + } catch (FileNotFoundException e) { + System.out.println("-- file " + fileName + " not found"); + } catch (FormatException e) { + System.out.println("-- corrupted object file " + fileName + ": " + e.getMessage()); + } catch (IOException e) { + System.out.println("-- error reading file " + fileName); + } + } +} + +class FormatException extends IOException { + + @Serial + private static final long serialVersionUID = 1L; + + FormatException(String s) { + super(s); + } +} \ No newline at end of file diff --git a/MicroJava Compiler/src/ssw/mj/TracingClassLoader.java b/MicroJava Compiler/src/ssw/mj/TracingClassLoader.java new file mode 100644 index 0000000..320d513 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/TracingClassLoader.java @@ -0,0 +1,173 @@ +package ssw.mj; + +import javassist.*; +import javassist.expr.ExprEditor; +import javassist.expr.MethodCall; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +public class TracingClassLoader extends ClassLoader { + private ClassPool pool; + + public TracingClassLoader(ClassLoader parent) { + super(parent); + + // The class pool is used by Javassist to hold the classes that are being manipulated + // We create it once and it stores the CtClass objects of all loaded classes + pool = ClassPool.getDefault(); + // We need to add the class path to the ClassPool + // otherwise, Javassist will not be able to find classes on the class path + addClassPathToPool(); + } + + private void addClassPathToPool() { + // Hopefully, this is the correct way to get the class path + String classpath = System.getProperty("java.class.path"); + String[] classpathEntries = classpath.split(File.pathSeparator); + for (String cp : classpathEntries) { + try { + pool.appendClassPath(cp); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + } + + /* + We do not rely on the parent class loader to load the class. Instead, we use Javassist to + manipulate the class as needed and then define the class using the byte code generated by Javassist. + + If we followed the default delegation model for loading classes, we would override the findClass method. + But this method would not be called if the parent class loader can find the class. Therefore, we override + the loadClass method instead. + */ + @Override + public Class loadClass(String name) throws ClassNotFoundException { + // Check if the class has already been loaded by this class loader + Class foundClazz = findLoadedClass(name); + if (foundClazz != null) { + // otherwise we get java.lang.LinkageError: loader ssw.mj.TracingClassLoader @ attempted duplicate class definition for + return foundClazz; + } + /* if (name.startsWith("java.") + || name.startsWith("javax.") + || name.startsWith("jdk.internal.") + || name.startsWith("sun.") + || name.startsWith("com.sun.") + || name.startsWith("org.w3c.") + || name.startsWith("org.xml.") + ) { */ + if (!name.startsWith("ssw")) { + // class library classes should be loaded by the bootstrap class loader + // there are three class loaders: bootstrap, extension, and system class loader + // - the bootstrap class loader is implemented in native code and is responsible for loading the core Java classes + // - the extension class loader (also called platform class loader) is implemented in Java and is responsible for loading the classes in the extension directories (i.e., jre/lib/ext) + // - the system class loader (also sometimes called the app class loader) is implemented in Java and is responsible for loading the classes in the class path + // --- The TracingClassLoader is meant to be used as the system class loader + return super.loadClass(name); + } + CtClass cc = null; + try { + cc = pool.get(name); + if (!cc.isFrozen() && cc.getName().equals("ssw.mj.impl.Parser")) { + cc.addField(CtField.make("public static ssw.mj.Recorder __recorder__ = new ssw.mj.Recorder();", cc)); + + // Difference between getMethods and getDeclaredMethods: https://stackoverflow.com/a/73069812/2938364 + CtMethod[] methods = cc.getDeclaredMethods(); + for (CtMethod method : methods) { + // System.out.println("Instrumenting: " + method.getName()); + + // TODO: Check if EBNF contains rule for method.getName() (ignoreCase) + // e.g., we want to instrument program or ProGram if EBNF has a production for Program = ... . + // We could use an array/set with all production names for this. + if (Character.isUpperCase(method.getName().charAt(0))) { + method.insertBefore("__recorder__.customEnter(t, la, \"%s\");".formatted(method.getName())); + method.insertAfter("__recorder__.exit(t, la);"); + } else if (method.getName().equals("parse")) { + // We need to reset the recorder once we perform a new parse + // since it's declared as a static field in the parser + method.insertBefore("__recorder__.reset();"); + } else if (method.getName().equals("check")) { + method.insertBefore("__recorder__.checkEnter(t, la, $1);"); + method.insertAfter("__recorder__.exit(t, la);"); + } else if (method.getName().equals("scan")) { + method.insertBefore("__recorder__.scanEnter(t, la);"); + method.insertAfter("__recorder__.exit(t, la);"); + } else if (method.getName().equals("error")) { + method.instrument(new ExprEditor() { + @Override + public void edit(MethodCall m) throws CannotCompileException { + if (m.getClassName().equals("ssw.mj.Errors") && m.getMethodName().equals("error")) { + // We record enter and exit directly one after another, but we do not need more details than that + method.insertAt(m.getLineNumber(), "__recorder__.errorEnter(t, la, $1, $2); __recorder__.exit(t, la);"); + } + super.edit(m); + } + }); + } else if (method.getName().startsWith("recover")) { + method.insertBefore("__recorder__.recoverEnter(t, la, \"%s\");".formatted(method.getName())); + method.insertAfter("__recorder__.exit(t, la);"); + } + } + } + + cc.debugWriteFile(); + + byte[] byteCode = cc.toBytecode(); + Class newClazz = defineClass(name, byteCode, 0, byteCode.length); + return newClazz; + } catch (NotFoundException | CannotCompileException | IOException e) { + e.printStackTrace(); + System.err.println("Could not load " + name + "\n" + e); + throw new ClassNotFoundException("Could not load " + name, e); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + return super.findClass(name); + } + + /** + * See ClassLoaders.AppClassLoader + * Called by the VM to support dynamic additions to the class path + * + * @see java.lang.instrument.Instrumentation#appendToSystemClassLoaderSearch + */ + void appendToClassPathForInstrumentation(String path) { + try { + System.out.println("appendToClassPathForInstrumentation:" + path); + pool.appendClassPath(path); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public URL getResource(String name) { + if (name.contains("JUnitStarter")) { + System.out.println("JUnitStarter"); + } + return super.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + if (name.contains("JUnitStarter")) { + System.out.println("JUnitStarter"); + } + return super.getResources(name); + } + + @Override + public InputStream getResourceAsStream(String name) { + if (name.contains("JUnitStarter")) { + System.out.println("JUnitStarter"); + } + return super.getResourceAsStream(name); + } +} diff --git a/MicroJava Compiler/src/ssw/mj/Visualizer.java b/MicroJava Compiler/src/ssw/mj/Visualizer.java new file mode 100644 index 0000000..2dd3cce --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/Visualizer.java @@ -0,0 +1,218 @@ +package ssw.mj; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import ssw.mj.scanner.Token; + +import java.awt.*; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.util.List; + +public class Visualizer { + // Config Flags + private static final boolean debug = false; + private static final String visualizerUrl = "https://ssw.jku.at/General/Staff/Weninger/Teaching/CB/Visualization/v1_0"; + // private static final String visualizerUrl = "http://localhost:5173/General/Staff/Weninger/Teaching/CB/Visualization/v1_0"; + + public static final Gson gson = debug ? new GsonBuilder().setPrettyPrinting().create() : new Gson(); + + // Remember when the browser was last opened, to ensure that we + // don't open a huge number of tabs + private static LocalDateTime browserLastOpened; + + public static class VisualizerException extends RuntimeException { + public VisualizerException(String message) { + super("[Recorder]: " + message); + } + } + + private interface WithDescription { + String getDescription(); + } + + public enum VisualizationType implements WithDescription { + SCANNER_V1 { + @Override + public String getDescription() { + return "Scanner Visualization"; + } + }, + PARSER_V1 { + @Override + public String getDescription() { + return "Parser Visualization"; + } + } + } + + private record ScannerPayload(List receivedTokens, List expectedTokens) { + public String toJson() { + return gson.toJson(this); + } + } + + private record ParserPayload(List traces) { + public String toJson() { + return gson.toJson(this); + } + } + + /** + * Transforms scanner tokens to our visualizer format and ingests them to the desired method + * + * @param receivedTokens The tokens received from the student's scanner + * @param expectedTokens The tokens expected by the test case + */ + public static void createScannerVisualization(String sourcecode, + List receivedTokens, + List expectedTokens, + boolean openInBrowserAfterwards) { + String payload = new ScannerPayload(receivedTokens, expectedTokens).toJson(); + VisualizerIngestData visData = new VisualizerIngestData(VisualizationType.SCANNER_V1, sourcecode, payload); + + Path path = ingestFile(visData); + if (openInBrowserAfterwards) { + openBrowser(path.toUri().toString()); + } + } + + /** + * Get the secret recorder from the parser and ingest the tokens to the visualizer + */ + public static void createParserVisualization(String sourcecode, boolean openInBrowserAfterwards) { + Recorder recorder = getRecorderFromParserUsingReflection(); + + // If we don't have a recorder, we can't create a visualization + if (recorder == null) return; + + String payload = new ParserPayload(recorder.getTrace()).toJson(); + + VisualizerIngestData visData = new VisualizerIngestData(VisualizationType.PARSER_V1, sourcecode, payload); + + Path path = ingestFile(visData); + if (openInBrowserAfterwards) { + openBrowser(path.toUri().toString()); + } + } + + // Data to be sent to the ingest endpoint + private record VisualizerIngestData(VisualizationType type, String sourcecode, String payload) { + public String toJson() { + return gson.toJson(this); + } + } + + /** + * Create a visualization file + * + * @param data The visualization data + * @return The path at which the visualization file has been created + */ + private static Path ingestFile(VisualizerIngestData data) { + String html = "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " Compiler Visualization\n" + + " \n" + + " \n" + + "

\n" + + "\n" + + " \n" + + "\n" + + " \n" + + " \n" + + ""; + + try { + // Get a temporary file to write the visualization to + Path path = Files.createTempFile("microjava-visualization", ".html"); + Files.writeString(path, html); + + System.out.println(data.type.getDescription() + " created successfully. View it at: " + path.toUri().toString()); + return path; + } catch (IOException e) { + throw new VisualizerException("Failed to create visualization: " + e.getMessage()); + } + } + + /** + * Try to open the browser if possible and not rate limited + * + * @param url The url to open + */ + private static void openBrowser(String url) { + if (browserLastOpened != null && browserLastOpened.plusSeconds(5).isAfter(LocalDateTime.now())) { + return; + } + + if (Desktop.isDesktopSupported()) { + try { + Desktop.getDesktop().browse(new URI(url)); + } catch (Exception e) { + // Ignore + } + } + } + + /** + * Use reflection to get the secret recorder from the parser + * + * @return The recorder + */ + private static Recorder getRecorderFromParserUsingReflection() { + try { + // Get the parser class + Class parserClass = Class.forName("ssw.mj.impl.Parser"); + + // __recorder__ is a static field that has secretly been added in the Parser class by the TracingClassLoader + // during a run of the application / the unit tests with the CLI setting + // -Djava.system.class.loader=ssw.mj.TracingClassLoader + Field recorderField = parserClass.getField("__recorder__"); + + // Get the value of the field + Recorder recorder = (Recorder) recorderField.get(null); + + return recorder; + } catch (Exception e) { + // throw new VisualizerException("Failed to get recorder from parser: " + e.getMessage()); + + // We don't want to throw an exception here, since we might not have a recorder + // when the custom class loader is not used + return null; + } + } +} diff --git a/MicroJava Compiler/src/ssw/mj/codegen/Decoder.java b/MicroJava Compiler/src/ssw/mj/codegen/Decoder.java new file mode 100644 index 0000000..51ea042 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/codegen/Decoder.java @@ -0,0 +1,97 @@ +package ssw.mj.codegen; + +import ssw.mj.impl.Code; +import ssw.mj.impl.Code.OpCode; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.FileInputStream; +import java.io.IOException; + +public class Decoder { + private byte[] codeBuf; // code buffer + private int cur; // address of next byte to decode + private int adr; // address of currently decoded instruction + + private int getAndMove() { + return codeBuf[cur++]; + } + + private int getAndMove2() { + return (getAndMove() << 8) + (getAndMove() & 0xFF); + } + + private int getAndMove4() { + return (getAndMove2() << 16) + (getAndMove2() & 0xFFFF); + } + + private String jumpDist() { + int dist = getAndMove2(); + int pos = adr + dist; + return dist + " (=" + pos + ")"; + } + + public String decode(Code code) { + return decode(code.buf, 0, code.pc); + } + + public String decode(byte[] buf, int off, int len) { + StringBuilder sb = new StringBuilder(); + codeBuf = buf; + cur = off; + adr = cur; + while (cur < len) { + sb.append(adr); + sb.append(": "); + sb.append(decode(OpCode.get(getAndMove()))); + sb.append("\n"); + adr = cur; + } + return sb.toString(); + } + + private String decode(OpCode opCode) { + if (opCode == null) { + return "--error, unknown opcode--"; + } + + return switch (opCode) { + // Operations without parameters in the code buffer + case load_0, load_1, load_2, load_3, store_0, store_1, store_2, store_3, const_0, const_1, const_2, const_3, const_4, const_5, const_m1, add, sub, mul, div, rem, neg, shl, shr, aload, astore, baload, bastore, arraylength, pop, dup, dup2, exit, return_, read, print, bread, bprint -> + opCode.cleanName(); + // Operations with one 1 byte parameter in the code buffer + case load, store, newarray, trap -> opCode.cleanName() + " " + getAndMove(); + // Operations with one 2 byte parameter in the code buffer + case getstatic, putstatic, getfield, putfield, new_ -> opCode.cleanName() + " " + getAndMove2(); + // Operations with one 4 byte parameter in the code buffer + case const_ -> opCode.cleanName() + " " + getAndMove4(); + // Operations with two 1 byte parameters in the code buffer + case inc, enter -> opCode.cleanName() + " " + getAndMove() + ", " + getAndMove(); + // Operations with a jump distance as a parameter in the code buffer + case jmp, jeq, jne, jlt, jle, jgt, jge, call -> opCode.cleanName() + " " + jumpDist(); + default -> "--error--"; + }; + } + + public void decodeFile(String filename) throws IOException { + DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(filename))); + byte[] sig = new byte[2]; + in.read(sig, 0, 2); + System.out.println("" + (char) sig[0] + (char) sig[1]); + int codeSize = in.readInt(); + System.out.println("codesize = " + codeSize); + System.out.println("datasize = " + in.readInt()); + System.out.println("startPC = " + in.readInt()); + byte[] code = new byte[codeSize]; + in.read(code); + System.out.println(decode(code, 0, codeSize)); + in.close(); + } + + public static void main(String[] args) throws IOException { + if (args.length > 0) { + Decoder dec = new Decoder(); + dec.decodeFile(args[0]); + } + } +} diff --git a/MicroJava Compiler/src/ssw/mj/codegen/Label.java b/MicroJava Compiler/src/ssw/mj/codegen/Label.java new file mode 100644 index 0000000..a47c053 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/codegen/Label.java @@ -0,0 +1,65 @@ +package ssw.mj.codegen; + +import ssw.mj.impl.Code; + +import java.util.ArrayList; +import java.util.List; + +public final class Label { + + /** + * Jump destination address. + */ + private int adr; + + /** + * List of unresolved forward jumps + */ + private List fixupList; + + /** + * The code buffer this Label belongs to. + */ + private final Code code; + + public Label(Code code) { + this.code = code; + fixupList = new ArrayList<>(); + } + + /** + * Generates code for a jump to this label. + */ + public void put() { + if (isDefined()) { + // jump destination already known + code.put2(adr - (code.pc - 1)); + } else { + // remember address to patch + fixupList.add(code.pc); + // insert place holder + code.put2(0); + } + } + + /** + * Defines this label to be at the current pc position + */ + public void here() { + if (isDefined()) { + // should never happen + throw new IllegalStateException("label defined twice"); + } + + for (int pos : fixupList) { + code.put2(pos, code.pc - (pos - 1)); + } + + fixupList = null; + adr = code.pc; + } + + private boolean isDefined() { + return fixupList == null; + } +} diff --git a/MicroJava Compiler/src/ssw/mj/codegen/Operand.java b/MicroJava Compiler/src/ssw/mj/codegen/Operand.java new file mode 100644 index 0000000..a931bab --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/codegen/Operand.java @@ -0,0 +1,152 @@ +package ssw.mj.codegen; + +import ssw.mj.impl.Code; +import ssw.mj.impl.Code.CompOp; +import ssw.mj.impl.Parser; +import ssw.mj.impl.Tab; +import ssw.mj.symtab.Obj; +import ssw.mj.symtab.Struct; + +import static ssw.mj.Errors.Message.ILLEGAL_OPERAND_KIND; + +public class Operand { + /** + * Possible operands. + */ + public enum Kind { + Con(true), + Local(false), + Static(false), + Stack(true), + Fld(false), + Elem(false), + Meth(true), + Cond(true), + None(true); + + public final boolean isReadOnly; + + Kind(boolean isReadOnly) { + this.isReadOnly = isReadOnly; + } + } + + /** + * Kind of the operand. + */ + public Kind kind; + /** + * The type of the operand (reference to symbol table). + */ + public Struct type; + /** + * Only for Con: Value of the constant. + */ + public int val; + /** + * Only for Local, Static, Fld, Meth: Offset of the element. + */ + public int adr; + /** + * Only for Cond: Relational operator. + */ + public CompOp op; + /** + * Only for Meth: Method object from the symbol table. + */ + public Obj obj; + /** + * Only for Cond: Target for true jumps. + */ + public Label tLabel; + /** + * Only for Cond: Target for false jumps. + */ + public Label fLabel; + + /** + * Constructor for named objects: constants, variables, methods + */ + public Operand(Obj o, Parser parser) { + type = o.type; + val = o.val; + adr = o.adr; + switch (o.kind) { + case Con -> kind = Kind.Con; + case Var -> { + if (o.level == 0) { + kind = Kind.Static; + } else { + kind = Kind.Local; + } + } + case Meth -> { + kind = Kind.Meth; + obj = o; + } + default -> { + kind = Kind.None; + parser.error(ILLEGAL_OPERAND_KIND, o.kind); + } + } + } + + /** + * Constructor for compare operations + */ + public Operand(CompOp op, Code code) { + this(code); + this.kind = Kind.Cond; + this.op = op; + } + + public Operand(Code code) { + tLabel = new Label(code); + fLabel = new Label(code); + } + + /** + * Constructor for stack operands + */ + public Operand(Struct type) { + this.kind = Kind.Stack; + this.type = type; + } + + /** + * Constructor for integer constants + */ + public Operand(int x) { + kind = Kind.Con; + type = Tab.intType; + val = x; + } + + public boolean isReadOnly() { + return kind.isReadOnly; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Op["); + switch (kind) { + case Con -> { + sb.append(type).append(' '); + sb.append(val); + } + case Local, Static, Fld -> { + sb.append(kind).append(' '); + sb.append(type).append(' '); + sb.append(adr); + } + case Cond -> sb.append(op); + case Meth -> sb.append(obj); + case Elem, Stack -> { + sb.append(kind).append(' '); + sb.append(type); + } + } + return sb.append(']').toString(); + } + +} diff --git a/MicroJava Compiler/src/ssw/mj/impl/Code.java b/MicroJava Compiler/src/ssw/mj/impl/Code.java new file mode 100644 index 0000000..cf69cb7 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/impl/Code.java @@ -0,0 +1,294 @@ +package ssw.mj.impl; + +import ssw.mj.codegen.Label; +import ssw.mj.codegen.Operand; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; + +public final class Code { + + public enum OpCode { + load, + load_0, + load_1, + load_2, + load_3, + store, + store_0, + store_1, + store_2, + store_3, + getstatic, + putstatic, + getfield, + putfield, + const_0, + const_1, + const_2, + const_3, + const_4, + const_5, + const_m1, + const_, + add, + sub, + mul, + div, + rem, + neg, + shl, + shr, + inc, + new_, + newarray, + aload, + astore, + baload, + bastore, + arraylength, + pop, + dup, + dup2, + jmp, + jeq, + jne, + jlt, + jle, + jgt, + jge, + call, + return_, + enter, + exit, + read, + print, + bread, + bprint, + trap, + nop; + + public int code() { + return ordinal() + 1; + } + + public String cleanName() { + String name = name(); + if (name.endsWith("_")) { + name = name.substring(0, name.length() - 1); + } + return name; + } + + public static OpCode get(int code) { + if (code < 1 || code > values().length) { + return null; + } + return values()[code - 1]; + } + } + + public enum CompOp { + eq, ne, lt, le, gt, ge; + + public static CompOp invert(CompOp op) { + if (op == null) { + throw new IllegalArgumentException("Compare operator must not be null!"); + } + return switch (op) { + case eq -> ne; + case ne -> eq; + case lt -> ge; + case le -> gt; + case gt -> le; + case ge -> lt; + default -> + // Cannot happen, we covered all six compare operations as well as null parameter + // This is purely to prevent the compiler from complaining about a missing return statement + throw new IllegalArgumentException("Impossible compare operator"); + }; + } + + public static OpCode toOpCode(CompOp op) { + return switch (op) { + case eq -> OpCode.jeq; + case ge -> OpCode.jge; + case gt -> OpCode.jgt; + case le -> OpCode.jle; + case lt -> OpCode.jlt; + case ne -> OpCode.jne; + }; + } + } + + /** + * Code buffer + */ + public byte[] buf; + + /** + * Program counter. Indicates next free byte in code buffer. + */ + public int pc; + + /** + * PC of main method (set by parser). + */ + public int mainpc; + + /** + * Length of static data in words (set by parser). + */ + public int dataSize; + + /** + * According parser. + */ + private final Parser parser; + + // ----- initialization + + public Code(Parser p) { + parser = p; + buf = new byte[100]; + pc = 0; + mainpc = -1; + dataSize = 0; + } + + // ----- code storage management + + public void put(OpCode code) { + put(code.code()); + } + + public void put(int x) { + if (pc == buf.length) { + buf = Arrays.copyOf(buf, buf.length * 2); + } + buf[pc++] = (byte) x; + } + + public void put2(int x) { + put(x >> 8); + put(x); + } + + public void put4(int x) { + put2(x >> 16); + put2(x); + } + + public void put2(int pos, int x) { + int oldpc = pc; + pc = pos; + put2(x); + pc = oldpc; + } + + /** + * Write the code buffer to the output stream. + */ + public void write(OutputStream os) throws IOException { + int codeSize = pc; + + ByteArrayOutputStream header = new ByteArrayOutputStream(); + DataOutputStream headerWriter = new DataOutputStream(header); + headerWriter.writeByte('M'); + headerWriter.writeByte('J'); + headerWriter.writeInt(codeSize); + headerWriter.writeInt(dataSize); + headerWriter.writeInt(mainpc); + headerWriter.close(); + + os.write(header.toByteArray()); + + os.write(buf, 0, codeSize); + os.flush(); + os.close(); + } + + // ====================================================== + // TODO Exercise UE-P-5-6: implementation of code generation + // ====================================================== + + // TODO Exercise UE-P-5: Various code generation methods such as load or assign + + /** + * Load the operand x onto the expression stack. + */ + public void load(Operand x) { + // TODO Exercise UE-P-5 + } + + /** + * Load an integer constant onto the expression stack. + */ + public void loadConst(int n) { + // TODO Exercise UE-P-5 + } + + /** + * Generate an assignment x = y. + */ + public void assign(Operand x, Operand y) { + // TODO Exercise UE-P-5 + } + + /** + * Generate an increment instruction that increments x by n. + */ + public void inc(Operand x, int n) { + // TODO Exercise UE-P-5 + } + + /** + * Prepares the left-hand side of a compound assignment. + */ + public void prepareLhsOfCompoundAssignment(Operand x) { + Operand.Kind kindBeforeLoad = x.kind; + // TODO Exercise UE-P-5 + // TODO: Field accesses (such as x.y) or array accesses (such as arr[2]) on the left-hand side of + // an compound assignment (e.g., arr[2] += 4) need to correctly use dup or dup2 before load. Implement here. + + + // Do not switch kind to Stack after loading x. + // We still need its kind later on during the assign(). + x.kind = kindBeforeLoad; + } + + // -------------------- + + public void methodCall(Operand x) { + // TODO Exercise UE-P-6 + } + + /** + * Unconditional jump. + */ + public void jump(Label lab) { + // TODO Exercise UE-P-6 + } + + /** + * True Jump. Generates conditional jump instruction and links it to true + * jump chain. + */ + public void tJump(CompOp op, Label to) { + // TODO Exercise UE-P-6 + } + + /** + * False Jump. Generates conditional jump instruction and links it to false + * jump chain. + */ + public void fJump(CompOp op, Label to) { + // TODO Exercise UE-P-6 + } + + // ================================================= + // ================================================= +} diff --git a/MicroJava Compiler/src/ssw/mj/impl/Parser.java b/MicroJava Compiler/src/ssw/mj/impl/Parser.java new file mode 100644 index 0000000..4bf19b9 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/impl/Parser.java @@ -0,0 +1,509 @@ +package ssw.mj.impl; + +import javassist.bytecode.analysis.ControlFlow; +import javassist.compiler.ast.MethodDecl; +import ssw.mj.Errors; +import ssw.mj.Errors.Message; +import ssw.mj.scanner.Token; + +import javax.xml.stream.FactoryConfigurationError; +import java.util.EnumSet; + +import static ssw.mj.Errors.Message.TOKEN_EXPECTED; +import static ssw.mj.Errors.Message.UNDEFINED_ESCAPE; +import static ssw.mj.scanner.Token.Kind.*; + +public final class Parser { + + /** + * Maximum number of global variables per program + */ + private static final int MAX_GLOBALS = 32767; + + /** + * Maximum number of fields per class + */ + private static final int MAX_FIELDS = 32767; + + /** + * Maximum number of local variables per method + */ + private static final int MAX_LOCALS = 127; + + /** + * Last recognized token; + */ + private Token t; + + /** + * Lookahead token (not recognized).) + */ + private Token la; + + /** + * Shortcut to kind attribute of lookahead token (la). + */ + private Token.Kind sym; + + /** + * According scanner + */ + public final Scanner scanner; + + /** + * According code buffer + */ + public final Code code; + + /** + * According symbol table + */ + public final Tab tab; + + public Parser(Scanner scanner) { + this.scanner = scanner; + tab = new Tab(this); + code = new Code(this); + // Pseudo token to avoid crash when 1st symbol has scanner error. + la = new Token(none, 1, 1); + } + + + /** + * Reads ahead one symbol. + */ + private void scan() { + t = la; + la = scanner.next(); + sym = la.kind; + } + + /** + * Verifies symbol and reads ahead. + */ + private void check(Token.Kind expected) { + if (sym == expected) { + scan(); + } else { + error(TOKEN_EXPECTED, expected); + } + } + + /** + * Adds error message to the list of errors. + */ + public void error(Message msg, Object... msgParams) { + // TODO Exercise UE-P-3: Replace panic mode with error recovery (i.e., keep track of error distance) + // TODO Exercise UE-P-3: Hint: Replacing panic mode also affects scan() method + scanner.errors.error(la.line, la.col, msg, msgParams); + throw new Errors.PanicMode(); + } + + /** + * Starts the analysis. + */ + public void parse() { + scan(); // scan first symbol, initializes look-ahead + Program(); // start analysis + check(eof); + } + + + // =============================================== + // TODO Exercise UE-P-2: Implementation of parser + // TODO Exercise UE-P-3: Error recovery methods + // TODO Exercise UE-P-4: Symbol table handling + // TODO Exercise UE-P-5-6: Code generation + // =============================================== + + // TODO Exercise UE-P-3: Error distance + + // TODO Exercise UE-P-2 + Exercise 3: Sets to handle certain first, follow, and recover sets + + // Initialize first and follow sets. + static final EnumSet firstConstDecl = EnumSet.of(Token.Kind.final_); + static final EnumSet firstVarDecl = EnumSet.of(Token.Kind.ident); + static final EnumSet firstClassDecl = EnumSet.of(Token.Kind.class_); + static final EnumSet firstStatement = EnumSet.of(ident, if_, while_, break_, return_, read, print, lbrace, semicolon); + static final EnumSet firstAsignop = EnumSet.of(assign, plusas, minusas, timesas, slashas, remas); + static final EnumSet firstFactor = EnumSet.of(ident, number, charConst, new_, lpar); + static final EnumSet firstMulop = EnumSet.of(times, slash, rem); + + static > EnumSet enumUnion(EnumSet first, EnumSet ...sets) { + final var copy = EnumSet.copyOf(first); + for (final var set : sets) { + copy.addAll(set); + } + + return copy; + } + + // --------------------------------- + + // TODO Exercise UE-P-2: One top-down parsing method per production + + /** + * Program =
+ * "program" ident
+ * { ConstDecl | VarDecl | ClassDecl }
+ * "{" { MethodDecl } "}" . + */ + private void Program() { + // TODO Exercise UE-P-2 + + check(Token.Kind.program); + check(Token.Kind.ident); + + final var firstDeclarationUnion = enumUnion(firstConstDecl, firstVarDecl, firstClassDecl); + + while (firstDeclarationUnion.contains(sym)) { + if (firstVarDecl.contains(sym)) { + VarDecl(); + } else if (firstClassDecl.contains(sym)) { + ClassDecl(); + } else { + ConstDecl(); + } + } + + check(lbrace); + + while (sym == ident || sym == void_) { + MethodDecl(); + } + + check(rbrace); + } + + private void ConstDecl() { + check(Token.Kind.final_); + Type(); + check(Token.Kind.ident); + check(Token.Kind.assign); + + if (sym == Token.Kind.number) { + scan(); + } else if (sym == Token.Kind.charConst) { + scan(); + } else { + error(TOKEN_EXPECTED, "number or character constant"); + } + + check(Token.Kind.semicolon); + } + + private void VarDecl() { + Type(); + check(ident); + + while (sym == Token.Kind.comma) { + scan(); + check(ident); + } + + check(semicolon); + } + + private void ClassDecl() { + check(class_); + check(ident); + check(lbrace); + + while (firstVarDecl.contains(sym)) { + VarDecl(); + } + + check(rbrace); + } + + private void Type() { + check(ident); + + if (sym == lbrack) { + scan(); + check(rbrack); + } + } + + private void MethodDecl() { + if (sym == ident) { + Type(); + } else if (sym == void_) { + scan(); + } + + check(ident); + check(lpar); + + if (sym == ident) { + FormPars(); + } + + check(rpar); + + while (firstVarDecl.contains(sym)) { + VarDecl(); + } + + Block(); + } + + private void FormPars() { + Type(); + check(ident); + + while (sym == comma) { + scan(); + Type(); + check(ident); + } + } + + private void Block() { + check(lbrace); + Statement(); + check(rbrace); + } + + private void Statement() { + while (firstStatement.contains(sym)) { + switch (sym) { + case ident -> { + Designator(); + + if (firstAsignop.contains(sym)) { + AssignOp(); + Expr(); + } else if (sym == lpar) { + ActPars(); + } else if (sym == pplus) { + scan(); + } else if (sym == mminus) { + scan(); + } else { + error(TOKEN_EXPECTED, "unexpected token. assignment token (=, +=, -=, *=, /=, %=), method call (\"(\"), increment (++) or decrement (--)"); + } + + check(semicolon); + } + case if_ -> { + scan(); + check(lpar); + Condition(); + check(rpar); + Statement(); + + if (sym == else_) { + scan(); + Statement(); + } + } + case while_ -> { + scan(); + check(lpar); + Condition(); + check(rpar); + Statement(); + } + case break_-> { + scan(); + check(semicolon); + } + case return_ -> { + scan(); + + if (sym == minus || firstFactor.contains(sym)) { + Expr(); + } + + check(semicolon); + } + case read -> { + scan(); + check(lpar); + Designator(); + check(rpar); + check(semicolon); + } + case print -> { + scan(); + check(lpar); + Expr(); + + if (sym == comma) { + scan(); + check(number); + } + + check(rpar); + check(semicolon); + } + case lbrace -> Block(); + case semicolon -> scan(); + }; + } + } + + private void Designator() { + check(ident); + + while (sym == period || sym == lbrack) { + if (sym == period) { + scan(); + check(ident); + } else { + scan(); + + if (sym == tilde) { + scan(); + } + + Expr(); + + check(rbrack); + } + } + + } + + private void AssignOp() { + switch (sym) { + case assign -> scan(); + case plusas -> scan(); + case minusas -> scan(); + case timesas -> scan(); + case slashas -> scan(); + case remas -> scan(); + default -> error(TOKEN_EXPECTED, "unexpected token. assignment token (=, +=, -=, *=, /=, %=), method call (\"(\"), increment (++) or decrement (--)"); + } + } + + private void Expr() { + if (sym == minus) { + scan(); + } + + Term(); + + while (sym == plus || sym == minus) { + Addop(); + Term(); + } + + } + + private void ActPars() { + + check(lpar); + + if (sym == minus || firstFactor.contains(sym)) { + Expr(); + + while (sym == comma) { + scan(); + Expr(); + } + } + + check(rpar); + } + + private void Term() { + Factor(); + + while (firstMulop.contains(sym)) { + Mulop(); + Factor(); + } + } + + private void Addop() { + if (sym == plus) { + scan(); + } else if (sym == minus) { + scan(); + } else { + error(TOKEN_EXPECTED, "minus or plus expected"); + } + } + + private void Factor() { + switch(sym) { + case ident -> Designator(); + case number -> scan(); + case charConst -> scan(); + case new_ -> { + scan(); + check(ident); + if (sym == lbrack) { + scan(); + Expr(); + check(rbrack); + } + } + case lpar -> { + scan(); + Expr(); + check(rpar); + } + default -> error(TOKEN_EXPECTED, "unexpected token. identifier, number, character constant, new or \"(\""); + } + } + + private void Mulop() { + if (sym == times) { + scan(); + } else if (sym == slash) { + scan(); + } else if (sym == rem) { + scan(); + } else { + error(TOKEN_EXPECTED, "expected *, /, %"); + } + } + + private void Condition() { + CondTerm(); + + while (sym == or) { + scan(); + CondTerm(); + } + } + + private void CondTerm() { + CondFact(); + + while (sym == and) { + scan(); + CondFact(); + } + } + + private void CondFact() { + Expr(); + Relop(); + Expr(); + } + + private void Relop() { + switch (sym) { + case eql -> scan(); + case neq -> scan(); + case gtr -> scan(); + case lss -> scan(); + case leq -> scan(); + case geq -> scan(); + default -> error(TOKEN_EXPECTED, "unexpected token. ==, !=, >, >=, <, <="); + } + } + + // ... + + // ------------------------------------ + + // TODO Exercise UE-P-3: Error recovery methods: recoverDecl, recoverMethodDecl and recoverStat (+ TODO Exercise UE-P-5: Check idents for Type kind) + + // ==================================== + // ==================================== +} diff --git a/MicroJava Compiler/src/ssw/mj/impl/Scanner.java b/MicroJava Compiler/src/ssw/mj/impl/Scanner.java new file mode 100644 index 0000000..898bd3e --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/impl/Scanner.java @@ -0,0 +1,434 @@ +package ssw.mj.impl; + +import ssw.mj.Errors; +import ssw.mj.Interpreter; +import ssw.mj.scanner.Token; + +import java.io.IOException; +import java.io.Reader; +import java.util.Map; + +import static ssw.mj.scanner.Token.Kind.*; +import static ssw.mj.scanner.Token.Kind.number; + +public class Scanner { + + // Scanner Skeleton - do not rename fields / methods ! + private static final char EOF = (char) -1; + private static final char LF = '\n'; + + /** + * Input data to read from. + */ + private final Reader in; + + /** + * Lookahead character. (= next (unhandled) character in the input stream) + */ + private char ch; + + /** + * Current line in input stream. + */ + private int line; + + /** + * Current column in input stream. + */ + private int col; + + /** + * According errors object. + */ + public final Errors errors; + + public Scanner(Reader r) { + // store reader + in = r; + + // initialize error handling support + errors = new Errors(); + + line = 1; + col = 0; + nextCh(); // read 1st char into ch, incr col to 1 + } + + /** + * Adds error message to the list of errors. + */ + public final void error(Token t, Errors.Message msg, Object... msgParams) { + errors.error(t.line, t.col, msg, msgParams); + + // reset token content (consistent JUnit tests) + t.numVal = 0; + t.val = null; + } + + + // ================================================ + // TODO Exercise UE-P-1: Implement Scanner (next() + private helper methods) + // ================================================ + + // TODO Exercise UE-P-1: Keywords + /** + * Mapping from keyword names to appropriate token codes. + */ + private static final Map keywords; + + static { + keywords = Map.ofEntries( + Map.entry("void", void_), + Map.entry("class", class_), + Map.entry("program", program), + Map.entry("if", if_), + Map.entry("else", else_), + Map.entry("while", while_), + Map.entry("read", read), + Map.entry("print", print), + Map.entry("return", return_), + Map.entry("break", break_), + Map.entry("final", final_), + Map.entry("new", new_) + ); + } + + /** + * Returns next token. To be used by parser. + */ + public Token next() { + // TODO Exercise UE-P-1: implementation of next method + while (Character.isWhitespace(ch)) { + nextCh(); + } + + final var ogLine = line; + final var ogCol = col; + + if (isLetter(ch)) { + final var name = readName(); + + return getTokenByName(name, ogLine, ogCol); + } + + if (isDigit(ch)) { + final var stringNumber = readNumber(); + + try { + final var number = Integer.parseInt(stringNumber); + + var token = new Token(Token.Kind.number, ogLine, ogCol); + token.numVal = number; + token.val = stringNumber; + + return token; + } catch (NumberFormatException exception) { + final var token = new Token(number, ogLine, ogCol); + error(token, Errors.Message.BIG_NUM, stringNumber); + return token; + } + } + + if (ch == '\'') { + nextCh(); + + if (ch == '\r') { + nextCh(); + } + + var charContent = ch; + var lfEscaped = false; + var tickEscaped = false; + + if (ch == '\\') { + nextCh(); + charContent = switch (ch) { + case 'n' -> '\n'; + case 'r' -> '\r'; + case 't' -> '\t'; + case '\\' -> '\\'; + case '\'' -> '\''; + default -> '\0'; + }; + + if (charContent == '\n') { + lfEscaped = true; + } + + if (charContent == '\'') { + tickEscaped = true; + } + + if (charContent == '\0') { + final var token = new Token(charConst, ogLine, ogCol); + error(token, Errors.Message.UNDEFINED_ESCAPE, ch); + } + } + + if (charContent == LF && !lfEscaped || charContent == EOF || charContent == '\'' && !tickEscaped) { + final var token = new Token(charConst, ogLine, ogCol); + final var message = switch (charContent) { + case LF -> Errors.Message.ILLEGAL_LINE_END; + case EOF -> Errors.Message.EOF_IN_CHAR; + case '\'' -> Errors.Message.EMPTY_CHARCONST; + default -> null; + }; + + error(token, message); + token.val = '\0' + ""; + token.numVal = 0; + + if (charContent == '\'') { + nextCh(); + } + + return token; + } + + nextCh(); + + if (ch != '\'') { + final var token = new Token(charConst, ogLine, ogCol); + error(token, Errors.Message.MISSING_QUOTE); + token.val = '\0' + ""; + token.numVal = 0; + return token; + } + + nextCh(); + + final var token = new Token(charConst, ogLine, ogCol); + token.val = charContent + ""; + token.numVal = charContent; + return token; + } + + final var ogChar = ch; + nextCh(); + return switch(ogChar) { + case ';' -> new Token(Token.Kind.semicolon, ogLine, ogCol); + case ',' -> new Token(Token.Kind.comma, ogLine, ogCol); + case '.' -> new Token(Token.Kind.period, ogLine, ogCol); + case '{' -> new Token(Token.Kind.lbrace, ogLine, ogCol); + case '}' -> new Token(Token.Kind.rbrace, ogLine, ogCol); + case '[' -> new Token(Token.Kind.lbrack, ogLine, ogCol); + case ']' -> new Token(Token.Kind.rbrack, ogLine, ogCol); + case '(' -> new Token(Token.Kind.lpar, ogLine, ogCol); + case ')' -> new Token(Token.Kind.rpar, ogLine, ogCol); + case (char) -1 -> new Token(Token.Kind.eof, ogLine, ogCol); + case '~' -> new Token(Token.Kind.tilde, ogLine, ogCol); + case '=' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.eql, ogLine, ogCol); + } + + yield new Token(Token.Kind.assign, ogLine, ogCol); + } + case '+' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.plusas, ogLine, ogCol); + } + + if (ch == '+') { + nextCh(); + yield new Token(Token.Kind.pplus, ogLine, ogCol); + } + + yield new Token(Token.Kind.plus, ogLine, ogCol); + } + case '-' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.minusas, ogLine, ogCol); + } + + if (ch == '-') { + nextCh(); + yield new Token(Token.Kind.mminus, ogLine, ogCol); + } + + yield new Token(Token.Kind.minus, ogLine, ogCol); + } + case '*' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.timesas, ogLine, ogCol); + } + + yield new Token(Token.Kind.times, ogLine, ogCol); + } + case '/' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.slashas, ogLine, ogCol); + } + + if (ch == '*') { + nextCh(); + final var success = skipComment(); + if (success) { + yield next(); + } else { + final var token = new Token(eof, ogLine, ogCol); + error(token, Errors.Message.EOF_IN_COMMENT); + yield next(); + } + } + + yield new Token(Token.Kind.slash, ogLine, ogCol); + } + case '%' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.remas, ogLine, ogCol); + } + + yield new Token(Token.Kind.rem, ogLine, ogCol); + } + case '!' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.neq, ogLine, ogCol); + } + + final var token = new Token(none, ogLine, ogCol); + error(token, Errors.Message.INVALID_CHAR, ogChar); + yield token; + } + case '>' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.geq, ogLine, ogCol); + } + + yield new Token(Token.Kind.gtr, ogLine, ogCol); + } + case '<' -> { + if (ch == '=') { + nextCh(); + yield new Token(Token.Kind.leq, ogLine, ogCol); + } + + yield new Token(Token.Kind.lss, ogLine, ogCol); + } + case '&' -> { + if (ch == '&') { + nextCh(); + yield new Token(Token.Kind.and, ogLine, ogCol); + } + + final var token = new Token(none, ogLine, ogCol); + error(token, Errors.Message.INVALID_CHAR, ogChar); + yield token; + } + case '|' -> { + if (ch == '|') { + nextCh(); + yield new Token(Token.Kind.or, ogLine, ogCol); + } + + final var token = new Token(none, ogLine, ogCol); + error(token, Errors.Message.INVALID_CHAR, ogChar); + yield token; + } + default -> { + final var token = new Token(none, ogLine, ogCol); + error(token, Errors.Message.INVALID_CHAR, ogChar); + yield token; + } + }; + } + + private void nextCh() { + // TODO Exercise UE-P-1: implementation of nextCh method and other private helper methods + try { + final var intChar = in.read(); + final var nextChar = (char) intChar; + + if (ch == '\n') { + line++; + col = 0; + } + + col++; + ch = nextChar; + } catch (IOException e) { + System.err.println("Could not read Stream"); + } + } + + // TODO Exercise UE-P-1: private helper methods used by next(), as discussed in the exercise + + // ----------------------------------------------- + + private boolean isLetter(char c) { + return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'; + } + + private boolean isDigit(char c) { + return '0' <= c && c <= '9'; + } + + public String readName() { + final var builder = new StringBuilder(); + + while (isLetter(ch) || ch == '_' || isDigit(ch)) { + builder.append(ch); + nextCh(); + } + + return builder.toString(); + } + + public String readNumber() { + final var builder = new StringBuilder(); + + while (isDigit(ch)) { + builder.append(ch); + nextCh(); + } + + return builder.toString(); + } + + private Token getTokenByName(String name, int line, int col) { + final var kind = keywords.getOrDefault(name, Token.Kind.ident); + + var token = new Token(kind, line, col); + + if (kind == Token.Kind.ident) { + token.val = name; + } + + return token; + } + + private boolean skipComment() { + var commentCount = 1; + while (commentCount != 0) { + final var lastCh = ch; + nextCh(); + + if (lastCh == '/' && ch == '*') { + commentCount++; + nextCh(); + } + + if (lastCh == '*' && ch == '/') { + commentCount--; + nextCh(); + } + + if (ch == EOF && commentCount != 0) { + return false; + } + } + + return true; + } + + // ================================================ + // ================================================ +} diff --git a/MicroJava Compiler/src/ssw/mj/impl/Tab.java b/MicroJava Compiler/src/ssw/mj/impl/Tab.java new file mode 100644 index 0000000..8e7ad71 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/impl/Tab.java @@ -0,0 +1,100 @@ +package ssw.mj.impl; + +import ssw.mj.symtab.Obj; +import ssw.mj.symtab.Scope; +import ssw.mj.symtab.Struct; + +public final class Tab { + + // Universe + public static final Struct noType = new Struct(Struct.Kind.None); + public static final Struct intType = new Struct(Struct.Kind.Int); + public static final Struct charType = new Struct(Struct.Kind.Char); + public static final Struct nullType = new Struct(Struct.Kind.Class); + + public final Obj noObj, chrObj; + public Obj ordObj, lenObj; + + /** + * Only used for reporting errors. + */ + private final Parser parser; + /** + * The current top scope. + */ + public Scope curScope = null; + // First scope opening (universe) will increase this to -1 + /** + * Nesting level of current scope. + */ + private int curLevel = -2; + + public Tab(Parser p) { + parser = p; + + // setting up "universe" (= predefined names) + // opening scope (curLevel goes to -1, which is the universe level) + openScope(); + + noObj = new Obj(Obj.Kind.Var, "noObj", noType); + + insert(Obj.Kind.Type, "int", intType); + insert(Obj.Kind.Type, "char", charType); + insert(Obj.Kind.Con, "null", nullType); + + chrObj = insert(Obj.Kind.Meth, "chr", charType); + openScope(); + Obj iVarObj = insert(Obj.Kind.Var, "i", intType); + iVarObj.level = 1; + chrObj.nPars = curScope.nVars(); + chrObj.locals = curScope.locals(); + closeScope(); + + // TODO Exercise UE-P-4: build "ord" universe method and store in ordObj + + // TODO Exercise UE-P-4: build "len" universe method and store in lenObj + + // still on level -1 + // now that the universe is constructed, the next node that will be added is the Program itself + // (which will open its own scope with level 0) + } + + // =============================================== + // TODO Exercise UE-P-4: implementation of symbol table + // =============================================== + + public void openScope() { + curScope = new Scope(curScope); + curLevel++; + } + + public void closeScope() { + curScope = curScope.outer(); + curLevel--; + } + + public Obj insert(Obj.Kind kind, String name, Struct type) { + // TODO Exercise UE-P-4 + return noObj; + } + + /** + * Retrieves the object with name from the innermost scope. + */ + public Obj find(String name) { + // TODO Exercise UE-P-4 + return noObj; + } + + /** + * Retrieves the field name from the fields of + * type. + */ + public Obj findField(String name, Struct type) { + // TODO Exercise UE-P-4 + return noObj; + } + + // =============================================== + // =============================================== +} diff --git a/MicroJava Compiler/src/ssw/mj/scanner/Token.java b/MicroJava Compiler/src/ssw/mj/scanner/Token.java new file mode 100644 index 0000000..43d5c51 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/scanner/Token.java @@ -0,0 +1,126 @@ +package ssw.mj.scanner; + +/** + * A Token represents a terminal symbol. Tokens are provided by the + * scanner for the parser. They hold additional information about the symbol. + */ +public class Token { + public enum Kind { + + // @formatter:off + none("none"), + ident("identifier"), + number("number"), + charConst("character constant"), + plus("+"), + minus("-"), + times("*"), + slash("/"), + rem("%"), + eql("=="), + neq("!="), + lss("<"), + leq("<="), + gtr(">"), + geq(">="), + and("&&"), + or("||"), + assign("="), + plusas("+="), + minusas("-="), + timesas("*="), + slashas("/="), + remas("%="), + pplus("++"), + mminus("--"), + semicolon(";"), + comma(","), + period("."), + lpar("("), + rpar(")"), + lbrack("["), + rbrack("]"), + lbrace("{"), + rbrace("}"), + tilde("~"), + break_("break"), + class_("class"), + else_("else"), + final_("final"), + if_("if"), + new_("new"), + print("print"), + program("program"), + read("read"), + return_("return"), + void_("void"), + while_("while"), + eof("end of file"); + // @formatter:on + + private final String label; + + Kind(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + + public String label() { + return label; + } + } + + /** + * Token class (none, ident, ...). + */ + public Kind kind; + + /** + * Line number of this token. + */ + public final int line; + + /** + * Column number of this token. + */ + public final int col; + + /** + * Value of this token (for numbers or character constants). + */ + public int numVal; + + /** + * String representation of this token. + */ + public String val; + + /** + * Constructor that sets the fields required for all tokens. + */ + public Token(Kind kind, int line, int col) { + this.kind = kind; + this.val = kind.label(); // Set val to kind text by default. Will be overwritten by terminal classes (ident, number, charConst) + this.line = line; + this.col = col; + } + + /** + * Returns a string representation of this Token object. This only includes + * the relevant attributes of the token. + */ + @Override + public String toString() { + String result = "line " + line + ", col " + col + ", kind " + kind; + if (kind == Kind.ident) { + result = result + ", val " + val; + } else if (kind == Kind.number || kind == Kind.charConst) { + result = result + ", val " + val + ", numVal " + numVal; + } + return result; + } +} diff --git a/MicroJava Compiler/src/ssw/mj/symtab/Obj.java b/MicroJava Compiler/src/ssw/mj/symtab/Obj.java new file mode 100644 index 0000000..2eadedd --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/symtab/Obj.java @@ -0,0 +1,144 @@ +package ssw.mj.symtab; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; + +/** + * MicroJava Symbol Table Objects: Every named object in a program is stored in + * an Obj node. Every scope has a list of objects declared within + * it. + */ +public class Obj { + /** + * Possible codes for object kinds. + */ + public enum Kind { + Con, Var, Type, Meth, Prog + } + + /** + * Kind of the object node. + */ + public final Kind kind; + + /** + * Name of the object node. + */ + public final String name; + + /** + * Type of the object node. + */ + public final Struct type; + + /** + * Only for Con: Value of the constant. + */ + public int val; + + /** + * Only for Var, Meth: Offset of the element. + */ + public int adr; + + /** + * Only for Var: Declaration level (0..global, 1..local) + */ + public int level; + + /** + * Only for Meth: Number of parameters. + */ + public int nPars; + + // This is a Collections.emptyMap() on purpose, do not change this line + // If you finished reading the locals of a method, use meth.locals = curScope.locals() and close the scope afterward + /** + * Only for Meth / Prog: List of local variables / global declarations. + */ + public Map locals = Collections.emptyMap(); + + public Obj(Kind kind, String name, Struct type) { + this.kind = kind; + this.name = name; + this.type = type; + } + + @Override + public String toString() { + StringBuilder sb; + switch (kind) { + case Prog -> { + sb = new StringBuilder(); + sb.append(kind).append(' '); + sb.append(name); + return sb.toString(); + } + case Con -> { + sb = new StringBuilder(); + sb.append("const "); + sb.append(type).append(' '); + sb.append(name).append('='); + sb.append(renderValue()); + return sb.toString(); + } + case Var -> { + sb = new StringBuilder(); + sb.append(renderVarPrefix()).append(' '); + sb.append(type).append(' '); + sb.append(name).append('@'); + renderAddress(sb); + return sb.toString(); + } + case Meth -> { + sb = new StringBuilder(type.toString()).append(' '); + sb.append(name).append('('); + boolean first = true; + Iterator> it = locals.entrySet().iterator(); + for (int i = 0; i < nPars; i++) { + if (!first) { + sb.append(", "); + } + Entry e = it.next(); + sb.append(e.getValue().type).append(' ').append(e.getKey()); + first = false; + } + sb.append(')').append('@'); + renderAddress(sb); + return sb.toString(); + } + case Type -> { + sb = new StringBuilder(); + sb.append("Type "); + sb.append(type).append(' '); + sb.append(name); + return sb.toString(); + } + } + throw new RuntimeException("Unknown Obj " + kind); + } + + private void renderAddress(StringBuilder sb) { + if (kind == Kind.Meth || level == 0) { + sb.append("0x").append(Integer.toHexString(adr)); + } else { + sb.append(adr); + } + } + + private String renderVarPrefix() { + if (level == 0) { + return "global"; + } + return "local"; + } + + private String renderValue() { + if (type.kind == Struct.Kind.Char) { + return Character.toString((char) val); + } + return Integer.toString(val); + } +} diff --git a/MicroJava Compiler/src/ssw/mj/symtab/Scope.java b/MicroJava Compiler/src/ssw/mj/symtab/Scope.java new file mode 100644 index 0000000..6524fb9 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/symtab/Scope.java @@ -0,0 +1,58 @@ +package ssw.mj.symtab; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * MicroJava Symbol Table Scopes + */ +public final class Scope { + /** + * Reference to enclosing scope. + */ + private final Scope outer; + /** + * Declarations of this scope. + */ + private final Map locals = new LinkedHashMap<>(); + /** + * Number of variables in this scope. + */ + private int nVars; + + public Scope(Scope outer) { + this.outer = outer; + } + + public int nVars() { + return nVars; + } + + public Obj findGlobal(String name) { + Obj res = findLocal(name); + if (res == null && outer != null) { + res = outer.findGlobal(name); + } + return res; + } + + public Obj findLocal(String name) { + return locals.get(name); + } + + public void insert(Obj o) { + locals.put(o.name, o); + if (o.kind == Obj.Kind.Var) { + nVars++; + } + } + + public Scope outer() { + return outer; + } + + public Map locals() { + return Collections.unmodifiableMap(locals); + } +} diff --git a/MicroJava Compiler/src/ssw/mj/symtab/Struct.java b/MicroJava Compiler/src/ssw/mj/symtab/Struct.java new file mode 100644 index 0000000..7a9c4c4 --- /dev/null +++ b/MicroJava Compiler/src/ssw/mj/symtab/Struct.java @@ -0,0 +1,116 @@ +package ssw.mj.symtab; + +import ssw.mj.impl.Tab; + +import java.util.Collections; +import java.util.Map; + +public final class Struct { + + /** + * Possible codes for structure kinds. + */ + public enum Kind { + None, Int, Char, Arr, Class + } + + /** + * Kind of the structure node. + */ + public final Kind kind; + /** + * Only for Arr: Type of the array elements. + */ + public final Struct elemType; + + /** + * Only for Class: First element of the linked list of local variables. + *
+ * This is a Collections.emptyMap() (which is immutable) on purpose, do not change this line. + * When you finished reading the fields of a class, use clazz.fields = curScope.locals() and close the scope afterward + */ + public Map fields = Collections.emptyMap(); + + public Struct(Kind kind) { + this.kind = kind; + this.elemType = null; + } + + /** + * Creates a new array structure with a specified element type. + */ + public Struct(Struct elemType) { + this.kind = Kind.Arr; + this.elemType = elemType; + } + + /** + * Retrieves the field name. + */ + public Obj findField(String name) { + return fields.get(name); + } + + /** + * Only for Class: Number of fields. + */ + public int nrFields() { + return fields.size(); + } + + @Override + public String toString() { + if (this == Tab.nullType) { + return "null"; + } + switch (kind) { + case Int, Char, None -> { + return kind.toString(); + } + case Arr -> { + return elemType + "[]"; + } + case Class -> { + StringBuilder sb = new StringBuilder(); + sb.append("Class{"); + boolean first = true; + for (Map.Entry e : fields.entrySet()) { + String fieldName = e.getKey(); + Obj field = e.getValue(); + if (!first) { + sb.append(", "); + } + sb.append(fieldName).append('=').append(field.type); + first = false; + } + sb.append('}'); + return sb.toString(); + } + } + throw new RuntimeException("Unknown Struct " + kind); + } + + public boolean isRefType() { + return kind == Kind.Class || kind == Kind.Arr; + } + + public boolean isEqual(Struct other) { + if (kind == Kind.Arr) { + return other.kind == Kind.Arr && elemType.isEqual(other.elemType); + } + return this == other; + } + + public boolean compatibleWith(Struct other) { + return this.isEqual(other) + || (this == Tab.nullType && other.isRefType()) + || (other == Tab.nullType && this.isRefType()); + } + + public boolean assignableTo(Struct dest) { + return this.isEqual(dest) || (this == Tab.nullType && dest.isRefType()) + // this is necessary for the standard function len + || (this.kind == Kind.Arr && dest.kind == Kind.Arr + && dest.elemType == Tab.noType); + } +} diff --git a/MicroJava Tests/MicroJava Tests2.iml b/MicroJava Tests/MicroJava Tests2.iml new file mode 100644 index 0000000..9d2e8a7 --- /dev/null +++ b/MicroJava Tests/MicroJava Tests2.iml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MicroJava Tests/resources/animals.mj b/MicroJava Tests/resources/animals.mj new file mode 100644 index 0000000..2e7c9cf --- /dev/null +++ b/MicroJava Tests/resources/animals.mj @@ -0,0 +1,54 @@ +program Animals + class Animal { + char[] name; + } +{ + void setName(Animal a, char[] name) { + a.name = name; + } + void aPrint(Animal a) + int i, l; + char c; + { + l = len(a.name); + i = 0; + while (i < l) { + print(a.name[i]); + i += 1; + } + } + void main() + int a; + Animal[] animals; + char[] cat; + char[] dog; + char[] octopus; + { + cat = new char[3]; + cat[0] = 'c'; + cat[1] = 'a'; + cat[2] = 't'; + dog = new char[3]; + dog[0] = 'd'; + dog[1] = 'o'; + dog[2] = 'g'; + octopus = new char[7]; + octopus[0] = 'o'; + octopus[1] = 'c'; + octopus[2] = 't'; + octopus[3] = 'o'; + octopus[4] = 'p'; + octopus[5] = 'u'; + octopus[6] = 's'; + + animals = new Animal[3]; + animals[0] = new Animal; + animals[1] = new Animal; + animals[2] = new Animal; + setName(animals[0],cat); + setName(animals[1],dog); + setName(animals[2],octopus); + read(a); + aPrint(animals[a]); + } +} diff --git a/MicroJava Tests/resources/bytecodes.txt b/MicroJava Tests/resources/bytecodes.txt new file mode 100644 index 0000000..c0964b8 --- /dev/null +++ b/MicroJava Tests/resources/bytecodes.txt @@ -0,0 +1,1842 @@ +#SimpleCodeGenerationTest.testLocalVarsIncDec() +0: enter 0, 2 +3: const_2 +4: store_0 +5: const_5 +6: store_1 +7: inc 0, 1 +10: inc 1, -1 +13: load_0 +14: load_1 +15: add +16: const_0 +17: print +18: exit +19: return +#SimpleCodeGenerationTest.dynamicArrayAccessFromEnd() +0: enter 0, 2 +3: read +4: store_1 +5: load_1 +6: newarray 1 +8: store_0 +9: load_0 +10: dup +11: arraylength +12: load_1 +13: sub +14: const_1 +15: astore +16: load_0 +17: dup +18: arraylength +19: load_1 +20: const_1 +21: sub +22: sub +23: const_2 +24: astore +25: load_0 +26: dup +27: arraylength +28: load_1 +29: const_2 +30: sub +31: sub +32: const_3 +33: astore +34: load_0 +35: dup +36: arraylength +37: load_1 +38: sub +39: aload +40: store_1 +41: load_1 +42: const_0 +43: print +44: exit +45: return +#SimpleCodeGenerationTest.negativeOutOfBoundsArrayAccessFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const -20 +15: sub +16: aload +17: store_1 +18: load_1 +19: const_0 +20: print +21: exit +22: return +#SimpleCodeGenerationTest.testReadAndPrint() +0: enter 0, 1 +3: const_0 +4: store_0 +5: read +6: store_0 +7: load_0 +8: const_0 +9: print +10: exit +11: return +#SimpleCodeGenerationTest.bsp01a() +0: enter 0, 3 +3: const_m1 +4: const_2 +5: add +6: store_2 +7: load_2 +8: const_0 +9: print +10: exit +11: return +#SimpleCodeGenerationTest.testPrint() +0: enter 0, 0 +3: const 97 +8: const_0 +9: bprint +10: const 98 +15: const_1 +16: bprint +17: const 99 +22: const_2 +23: bprint +24: const 100 +29: const_3 +30: bprint +31: const 101 +36: const_4 +37: bprint +38: exit +39: return +#SimpleCodeGenerationTest.negativeArrayIndex() +0: enter 0, 2 +3: const_1 +4: newarray 1 +6: store_0 +7: load_0 +8: const_m1 +9: aload +10: store_1 +11: load_1 +12: const_0 +13: print +14: exit +15: return +#SimpleCodeGenerationTest.testArrayAndDesignator() +0: enter 0, 3 +3: const_5 +4: newarray 1 +6: store_2 +7: load_2 +8: const_0 +9: new 1 +12: astore +13: load_2 +14: const_0 +15: aload +16: new 1 +19: putfield 0 +22: load_2 +23: const_0 +24: aload +25: getfield 0 +28: new 1 +31: putfield 0 +34: load_2 +35: const_0 +36: aload +37: getfield 0 +40: getfield 0 +43: const 10 +48: newarray 1 +50: putfield 0 +53: load_2 +54: const_3 +55: new 1 +58: astore +59: load_2 +60: const_3 +61: aload +62: new 1 +65: putfield 0 +68: load_2 +69: const_3 +70: aload +71: getfield 0 +74: new 1 +77: putfield 0 +80: load_2 +81: const_3 +82: aload +83: getfield 0 +86: getfield 0 +89: const 30 +94: newarray 1 +96: putfield 0 +99: load_2 +100: const_0 +101: aload +102: getfield 0 +105: getfield 0 +108: getfield 0 +111: const_0 +112: dup2 +113: aload +114: const_m1 +115: add +116: astore +117: load_2 +118: const_0 +119: aload +120: getfield 0 +123: getfield 0 +126: getfield 0 +129: const 8 +134: dup2 +135: aload +136: const_1 +137: add +138: astore +139: load_2 +140: const_3 +141: aload +142: getfield 0 +145: getfield 0 +148: getfield 0 +151: const_2 +152: dup2 +153: aload +154: const_1 +155: add +156: astore +157: load_2 +158: const_3 +159: aload +160: getfield 0 +163: getfield 0 +166: getfield 0 +169: const_2 +170: dup2 +171: aload +172: const_3 +173: mul +174: astore +175: load_2 +176: const_0 +177: aload +178: getfield 0 +181: getfield 0 +184: getfield 0 +187: const 8 +192: dup2 +193: aload +194: const 50 +199: load_2 +200: const_3 +201: aload +202: getfield 0 +205: getfield 0 +208: getfield 0 +211: const_2 +212: aload +213: load_2 +214: const_3 +215: aload +216: getfield 0 +219: getfield 0 +222: getfield 0 +225: const_2 +226: aload +227: mul +228: load_2 +229: const_0 +230: aload +231: getfield 0 +234: getfield 0 +237: getfield 0 +240: const_0 +241: aload +242: mul +243: add +244: add +245: astore +246: load_2 +247: const_0 +248: aload +249: getfield 0 +252: getfield 0 +255: getfield 0 +258: const 8 +263: aload +264: const_0 +265: print +266: exit +267: return +#SimpleCodeGenerationTest.computedArrayAccessFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const_2 +11: const 6 +16: mul +17: const 11 +22: sub +23: sub +24: const_3 +25: astore +26: load_0 +27: dup +28: arraylength +29: const 9 +34: const 11 +39: sub +40: const_4 +41: add +42: sub +43: const_2 +44: astore +45: load_0 +46: dup +47: arraylength +48: const_3 +49: sub +50: const_1 +51: astore +52: load_0 +53: dup +54: arraylength +55: load_0 +56: const_0 +57: aload +58: const_2 +59: const 6 +64: mul +65: add +66: const 11 +71: sub +72: sub +73: aload +74: store_1 +75: load_1 +76: const_0 +77: print +78: exit +79: return +#SimpleCodeGenerationTest.testDesignator() +0: enter 0, 3 +3: new 1 +6: store_2 +7: load_2 +8: new 1 +11: putfield 0 +14: load_2 +15: getfield 0 +18: new 1 +21: putfield 0 +24: load_2 +25: getfield 0 +28: getfield 0 +31: dup +32: getfield 0 +35: const_1 +36: add +37: putfield 0 +40: load_2 +41: getfield 0 +44: getfield 0 +47: getfield 0 +50: const_0 +51: print +52: exit +53: return +#SimpleCodeGenerationTest.arrayFromEndIncrement() +0: enter 0, 1 +3: const_1 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const_1 +11: sub +12: dup2 +13: aload +14: const_1 +15: add +16: astore +17: load_0 +18: const_0 +19: aload +20: const_0 +21: print +22: exit +23: return +#SimpleCodeGenerationTest.testArrayAndDesignatorAndAssign() +0: enter 0, 3 +3: const_5 +4: newarray 1 +6: store_2 +7: load_2 +8: const_0 +9: new 1 +12: astore +13: load_2 +14: const_0 +15: aload +16: new 1 +19: putfield 0 +22: load_2 +23: const_0 +24: aload +25: getfield 0 +28: new 1 +31: putfield 0 +34: load_2 +35: const_0 +36: aload +37: getfield 0 +40: getfield 0 +43: const_2 +44: newarray 1 +46: putfield 0 +49: load_2 +50: const_3 +51: new 1 +54: astore +55: load_2 +56: const_3 +57: aload +58: new 1 +61: putfield 0 +64: load_2 +65: const_3 +66: aload +67: getfield 0 +70: new 1 +73: putfield 0 +76: load_2 +77: const_3 +78: aload +79: getfield 0 +82: getfield 0 +85: const_3 +86: newarray 1 +88: putfield 0 +91: load_2 +92: const_0 +93: aload +94: getfield 0 +97: getfield 0 +100: getfield 0 +103: const_1 +104: dup2 +105: aload +106: const_1 +107: add +108: astore +109: load_2 +110: const_0 +111: aload +112: getfield 0 +115: getfield 0 +118: getfield 0 +121: const_1 +122: dup2 +123: aload +124: const 256 +129: mul +130: astore +131: load_2 +132: const_0 +133: aload +134: getfield 0 +137: getfield 0 +140: getfield 0 +143: const_1 +144: dup2 +145: aload +146: const_2 +147: div +148: astore +149: load_2 +150: const_0 +151: aload +152: getfield 0 +155: getfield 0 +158: getfield 0 +161: const_1 +162: dup2 +163: aload +164: const_m1 +165: add +166: astore +167: load_2 +168: const_0 +169: aload +170: getfield 0 +173: getfield 0 +176: getfield 0 +179: const_1 +180: dup2 +181: aload +182: const 64 +187: rem +188: astore +189: load_2 +190: const_3 +191: aload +192: getfield 0 +195: getfield 0 +198: getfield 0 +201: const_2 +202: dup2 +203: aload +204: const_1 +205: add +206: astore +207: load_2 +208: const_3 +209: aload +210: getfield 0 +213: getfield 0 +216: getfield 0 +219: const_2 +220: dup2 +221: aload +222: const 21 +227: mul +228: astore +229: load_2 +230: const_0 +231: aload +232: getfield 0 +235: getfield 0 +238: getfield 0 +241: const_1 +242: dup2 +243: aload +244: load_2 +245: const_3 +246: aload +247: getfield 0 +250: getfield 0 +253: getfield 0 +256: const_2 +257: aload +258: sub +259: astore +260: load_2 +261: const_0 +262: aload +263: getfield 0 +266: getfield 0 +269: getfield 0 +272: const_1 +273: aload +274: const_0 +275: print +276: exit +277: return +#SimpleCodeGenerationTest.bsp01() +0: enter 0, 3 +3: const_3 +4: store_2 +5: load_2 +6: const_0 +7: print +8: exit +9: return +#SimpleCodeGenerationTest.bsp02() +0: enter 0, 3 +3: const 10 +8: putstatic 1 +11: getstatic 1 +14: const_0 +15: print +16: exit +17: return +#SimpleCodeGenerationTest.bsp03() +0: enter 0, 3 +3: const_1 +4: putstatic 1 +7: const_3 +8: getstatic 1 +11: add +12: store_2 +13: load_2 +14: const_0 +15: print +16: exit +17: return +#SimpleCodeGenerationTest.bsp04() +0: enter 0, 3 +3: const_1 +4: putstatic 1 +7: const_3 +8: getstatic 1 +11: const 12 +16: mul +17: add +18: load_2 +19: sub +20: store_2 +21: load_2 +22: const_0 +23: print +24: exit +25: return +#SimpleCodeGenerationTest.bsp05() +0: enter 0, 3 +3: const 10 +8: newarray 1 +10: store_0 +11: load_0 +12: const_5 +13: const 10 +18: astore +19: load_0 +20: const_0 +21: aload +22: const_0 +23: print +24: load_0 +25: const_5 +26: aload +27: const_0 +28: print +29: exit +30: return +#SimpleCodeGenerationTest.bsp06() +0: enter 0, 3 +3: const 10 +8: newarray 1 +10: store_0 +11: load_0 +12: const_5 +13: const 10 +18: astore +19: new 2 +22: store_1 +23: load_1 +24: load_0 +25: const_5 +26: aload +27: const_3 +28: mul +29: putfield 1 +32: load_1 +33: getfield 1 +36: const_0 +37: print +38: exit +39: return +#SimpleCodeGenerationTest.bsp07() +0: enter 0, 3 +3: inc 2, -1 +6: load_2 +7: const_0 +8: print +9: exit +10: return +#SimpleCodeGenerationTest.bsp08() +0: enter 0, 3 +3: getstatic 1 +6: const_m1 +7: add +8: putstatic 1 +11: getstatic 1 +14: const_0 +15: print +16: exit +17: return +#SimpleCodeGenerationTest.bsp09() +0: enter 0, 3 +3: new 2 +6: store_1 +7: load_1 +8: dup +9: getfield 1 +12: const_m1 +13: add +14: putfield 1 +17: load_1 +18: getfield 1 +21: const_0 +22: print +23: exit +24: return +#SimpleCodeGenerationTest.bsp10() +0: enter 0, 3 +3: const 10 +8: newarray 1 +10: store_0 +11: load_0 +12: const_0 +13: dup2 +14: aload +15: const_m1 +16: add +17: astore +18: load_0 +19: const_0 +20: aload +21: const_0 +22: print +23: exit +24: return +#SimpleCodeGenerationTest.arrayAccessFromZeroEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const_0 +11: sub +12: aload +13: store_1 +14: load_1 +15: const_0 +16: print +17: exit +18: return +#SimpleCodeGenerationTest.testConstDecl() +0: enter 0, 0 +3: const 100 +8: const_0 +9: print +10: const 65 +15: const_0 +16: bprint +17: exit +18: return +#SimpleCodeGenerationTest.positiveOutOfBoundsArrayAccessFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const 1000 +15: sub +16: aload +17: store_1 +18: load_1 +19: const_0 +20: print +21: exit +22: return +#SimpleCodeGenerationTest.constantArrayAccessFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const_1 +11: sub +12: const_3 +13: astore +14: load_0 +15: dup +16: arraylength +17: const_2 +18: sub +19: const_2 +20: astore +21: load_0 +22: dup +23: arraylength +24: const_3 +25: sub +26: const_1 +27: astore +28: load_0 +29: dup +30: arraylength +31: const_1 +32: sub +33: aload +34: store_1 +35: load_1 +36: const_0 +37: print +38: exit +39: return +#SimpleCodeGenerationTest.twoMethods() +0: enter 0, 1 +3: const_0 +4: store_0 +5: exit +6: return +7: enter 0, 0 +10: const_2 +11: const_0 +12: print +13: exit +14: return +#SimpleCodeGenerationTest.bspEmpty() +0: enter 0, 3 +3: exit +4: return +#SimpleCodeGenerationTest.testFields() +0: enter 0, 2 +3: new 1 +6: store_0 +7: new 1 +10: store_1 +11: load_0 +12: const 20 +17: putfield 0 +20: load_0 +21: dup +22: getfield 0 +25: const_1 +26: add +27: putfield 0 +30: load_0 +31: dup +32: getfield 0 +35: const 7 +40: div +41: putfield 0 +44: load_0 +45: dup +46: getfield 0 +49: load_0 +50: getfield 0 +53: mul +54: putfield 0 +57: load_0 +58: dup +59: getfield 0 +62: load_0 +63: getfield 0 +66: const_5 +67: sub +68: rem +69: putfield 0 +72: load_1 +73: new 1 +76: putfield 0 +79: load_1 +80: getfield 0 +83: const -12 +88: putfield 0 +91: load_1 +92: getfield 0 +95: dup +96: getfield 0 +99: load_0 +100: getfield 0 +103: sub +104: putfield 0 +107: load_1 +108: getfield 0 +111: dup +112: getfield 0 +115: load_0 +116: getfield 0 +119: neg +120: mul +121: putfield 0 +124: load_1 +125: getfield 0 +128: dup +129: getfield 0 +132: const_5 +133: rem +134: putfield 0 +137: load_1 +138: getfield 0 +141: dup +142: getfield 0 +145: load_0 +146: getfield 0 +149: const_2 +150: const_3 +151: mul +152: add +153: mul +154: putfield 0 +157: load_1 +158: getfield 0 +161: getfield 0 +164: const_0 +165: print +166: exit +167: return +#SimpleCodeGenerationTest.testMulops() +0: enter 0, 2 +3: const 42 +8: store_0 +9: const_3 +10: store_1 +11: load_0 +12: load_1 +13: div +14: store_0 +15: load_0 +16: load_1 +17: load_1 +18: mul +19: rem +20: store_0 +21: load_0 +22: const_0 +23: print +24: exit +25: return +#SimpleCodeGenerationTest.testArrayIndexExpression() +0: enter 0, 1 +3: const 10 +8: newarray 1 +10: store_0 +11: load_0 +12: const_1 +13: const_2 +14: add +15: const_3 +16: mul +17: const_4 +18: astore +19: load_0 +20: const_4 +21: const_2 +22: const_2 +23: mul +24: sub +25: const_2 +26: astore +27: load_0 +28: const 90 +33: const 10 +38: div +39: aload +40: const_0 +41: print +42: load_0 +43: const 6 +48: const_3 +49: const_2 +50: mul +51: sub +52: aload +53: const_0 +54: print +55: exit +56: return +#SimpleCodeGenerationTest.arrayFromEndCompoundAssignment() +0: enter 0, 1 +3: const_2 +4: newarray 1 +6: store_0 +7: load_0 +8: const_0 +9: const_1 +10: astore +11: load_0 +12: const_1 +13: const_1 +14: astore +15: load_0 +16: dup +17: arraylength +18: const_1 +19: sub +20: dup2 +21: aload +22: const_1 +23: add +24: astore +25: load_0 +26: dup +27: arraylength +28: const_2 +29: sub +30: dup2 +31: aload +32: const_m1 +33: add +34: astore +35: load_0 +36: const_0 +37: aload +38: const_0 +39: print +40: load_0 +41: const_1 +42: aload +43: const_0 +44: print +45: exit +46: return +#SimpleCodeGenerationTest.globalArrayAccessFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: dup +9: arraylength +10: const_3 +11: sub +12: const_1 +13: astore +14: load_0 +15: dup +16: arraylength +17: const_3 +18: const_1 +19: sub +20: sub +21: const_2 +22: astore +23: load_0 +24: dup +25: arraylength +26: const_3 +27: const_2 +28: sub +29: sub +30: const_3 +31: astore +32: load_0 +33: dup +34: arraylength +35: const_3 +36: sub +37: aload +38: store_1 +39: load_1 +40: const_0 +41: print +42: exit +43: return +#CodeGenerationTest.testSimpleBreak() +0: enter 0, 0 +3: const 42 +8: const_0 +9: jle 9 (=18) +12: jmp 6 (=18) +15: jmp -12 (=3) +18: exit +19: return +#CodeGenerationTest.coverUniverseMethod() +0: enter 1, 1 +3: load_0 +4: exit +5: return +6: trap 1 +8: enter 1, 1 +11: load_0 +12: call -12 (=0) +15: const 30 +20: sub +21: exit +22: return +23: trap 1 +25: enter 0, 0 +28: const 65 +33: call -25 (=8) +36: const_0 +37: bprint +38: exit +39: return +#CodeGenerationTest.fibDyn() +0: enter 1, 2 +3: load_0 +4: const_1 +5: jgt 6 (=11) +8: const_1 +9: exit +10: return +11: getstatic 0 +14: load_0 +15: aload +16: const_0 +17: jeq 10 (=27) +20: getstatic 0 +23: load_0 +24: aload +25: exit +26: return +27: load_0 +28: const_1 +29: sub +30: call -30 (=0) +33: load_0 +34: const_2 +35: sub +36: call -36 (=0) +39: add +40: store_1 +41: getstatic 0 +44: load_0 +45: load_1 +46: astore +47: load_1 +48: exit +49: return +50: trap 1 +52: enter 0, 1 +55: const 1000 +60: newarray 1 +62: putstatic 0 +65: read +66: store_0 +67: load_0 +68: call -68 (=0) +71: const_0 +72: print +73: exit +74: return +#CodeGenerationTest.testAnimals() +0: enter 2, 2 +3: load_0 +4: load_1 +5: putfield 0 +8: exit +9: return +10: enter 1, 4 +13: load_0 +14: getfield 0 +17: arraylength +18: store_2 +19: const_0 +20: store_1 +21: load_1 +22: load_2 +23: jge 18 (=41) +26: load_0 +27: getfield 0 +30: load_1 +31: baload +32: const_0 +33: bprint +34: load_1 +35: const_1 +36: add +37: store_1 +38: jmp -17 (=21) +41: exit +42: return +43: enter 0, 5 +46: const_3 +47: newarray 0 +49: store_2 +50: load_2 +51: const_0 +52: const 99 +57: bastore +58: load_2 +59: const_1 +60: const 97 +65: bastore +66: load_2 +67: const_2 +68: const 116 +73: bastore +74: const_3 +75: newarray 0 +77: store_3 +78: load_3 +79: const_0 +80: const 100 +85: bastore +86: load_3 +87: const_1 +88: const 111 +93: bastore +94: load_3 +95: const_2 +96: const 103 +101: bastore +102: const 7 +107: newarray 0 +109: store 4 +111: load 4 +113: const_0 +114: const 111 +119: bastore +120: load 4 +122: const_1 +123: const 99 +128: bastore +129: load 4 +131: const_2 +132: const 116 +137: bastore +138: load 4 +140: const_3 +141: const 111 +146: bastore +147: load 4 +149: const_4 +150: const 112 +155: bastore +156: load 4 +158: const_5 +159: const 117 +164: bastore +165: load 4 +167: const 6 +172: const 115 +177: bastore +178: const_3 +179: newarray 1 +181: store_1 +182: load_1 +183: const_0 +184: new 1 +187: astore +188: load_1 +189: const_1 +190: new 1 +193: astore +194: load_1 +195: const_2 +196: new 1 +199: astore +200: load_1 +201: const_0 +202: aload +203: load_2 +204: call -204 (=0) +207: load_1 +208: const_1 +209: aload +210: load_3 +211: call -211 (=0) +214: load_1 +215: const_2 +216: aload +217: load 4 +219: call -219 (=0) +222: read +223: store_0 +224: load_1 +225: load_0 +226: aload +227: call -217 (=10) +230: exit +231: return +#CodeGenerationTest.testBreak() +0: enter 0, 1 +3: read +4: store_0 +5: getstatic 0 +8: load_0 +9: jgt 40 (=49) +12: const_1 +13: const_2 +14: jge 14 (=28) +17: const_1 +18: const_1 +19: jne 6 (=25) +22: jmp 6 (=28) +25: jmp -13 (=12) +28: getstatic 0 +31: const_5 +32: jne 6 (=38) +35: jmp 14 (=49) +38: getstatic 0 +41: const_1 +42: add +43: putstatic 0 +46: jmp -41 (=5) +49: getstatic 0 +52: const_0 +53: print +54: exit +55: return +#CodeGenerationTest.iterateArrayFromEnd() +0: enter 0, 2 +3: const_3 +4: newarray 1 +6: store_0 +7: load_0 +8: const_0 +9: const_1 +10: astore +11: load_0 +12: const_1 +13: const_2 +14: astore +15: load_0 +16: const_2 +17: const_3 +18: astore +19: const_1 +20: store_1 +21: load_1 +22: const_3 +23: jgt 17 (=40) +26: load_0 +27: dup +28: arraylength +29: load_1 +30: sub +31: aload +32: const_0 +33: print +34: inc 1, 1 +37: jmp -16 (=21) +40: exit +41: return +#CodeGenerationTest.compareNeg() +0: enter 0, 1 +3: const -42 +8: store_0 +9: load_0 +10: const -42 +15: jne 13 (=28) +18: const 42 +23: const_0 +24: print +25: jmp 6 (=31) +28: load_0 +29: const_0 +30: print +31: exit +32: return +#CodeGenerationTest.methodCall() +0: enter 0, 0 +3: const 98 +8: const_0 +9: bprint +10: const 97 +15: const_0 +16: bprint +17: const 114 +22: const_0 +23: bprint +24: exit +25: return +26: enter 0, 0 +29: const 102 +34: const_0 +35: bprint +36: const 111 +41: const_0 +42: bprint +43: const 111 +48: const_0 +49: bprint +50: exit +51: return +52: enter 0, 0 +55: call -29 (=26) +58: exit +59: return +#CodeGenerationTest.createPalindrom() +0: enter 2, 4 +3: load_0 +4: arraylength +5: store_3 +6: const_0 +7: store_2 +8: load_2 +9: load_3 +10: jge 26 (=36) +13: load_1 +14: load_2 +15: load_0 +16: load_2 +17: baload +18: bastore +19: load_1 +20: dup +21: arraylength +22: load_2 +23: const_1 +24: add +25: sub +26: load_0 +27: load_2 +28: baload +29: bastore +30: inc 2, 1 +33: jmp -25 (=8) +36: exit +37: return +38: enter 1, 2 +41: const_0 +42: store_1 +43: load_1 +44: load_0 +45: arraylength +46: jge 14 (=60) +49: load_0 +50: load_1 +51: baload +52: const_0 +53: bprint +54: inc 1, 1 +57: jmp -14 (=43) +60: exit +61: return +62: enter 0, 3 +65: const_5 +66: newarray 0 +68: store_0 +69: load_0 +70: const_0 +71: const 108 +76: bastore +77: load_0 +78: const_1 +79: const 97 +84: bastore +85: load_0 +86: const_2 +87: const 103 +92: bastore +93: load_0 +94: const_3 +95: const 101 +100: bastore +101: load_0 +102: const_4 +103: const 114 +108: bastore +109: const 10 +114: newarray 0 +116: store_1 +117: load_0 +118: load_1 +119: call -119 (=0) +122: load_1 +123: call -85 (=38) +126: const_2 +127: newarray 0 +129: store_0 +130: load_0 +131: const_0 +132: const 111 +137: bastore +138: load_0 +139: const_1 +140: const 116 +145: bastore +146: const_4 +147: newarray 0 +149: store_1 +150: load_0 +151: load_1 +152: call -152 (=0) +155: load_1 +156: call -118 (=38) +159: exit +160: return +#CodeGenerationTest.fib() +0: enter 1, 1 +3: load_0 +4: const_1 +5: jgt 6 (=11) +8: const_1 +9: exit +10: return +11: load_0 +12: const_1 +13: sub +14: call -14 (=0) +17: load_0 +18: const_2 +19: sub +20: call -20 (=0) +23: add +24: exit +25: return +26: trap 1 +28: enter 0, 1 +31: read +32: store_0 +33: load_0 +34: call -34 (=0) +37: const_0 +38: print +39: exit +40: return +#CodeGenerationTest.unusedReturnVal() +0: enter 0, 0 +3: const 351 +8: exit +9: return +10: trap 1 +12: enter 0, 0 +15: call -15 (=0) +18: pop +19: const 42 +24: exit +25: return +26: trap 1 +28: enter 0, 0 +31: const 932 +36: call -24 (=12) +39: add +40: const_0 +41: print +42: exit +43: return +#CodeGenerationTest.lenTest() +0: enter 0, 1 +3: const_5 +4: newarray 1 +6: store_0 +7: load_0 +8: arraylength +9: const_0 +10: print +11: exit +12: return +#CodeGenerationTest.bsp11() +0: enter 0, 3 +3: read +4: putstatic 1 +7: getstatic 1 +10: load_2 +11: jgt 5 (=16) +14: const_1 +15: store_2 +16: load_2 +17: const_0 +18: print +19: exit +20: return +#CodeGenerationTest.bsp12() +0: enter 0, 3 +3: read +4: putstatic 1 +7: const_1 +8: store_2 +9: getstatic 1 +12: load_2 +13: jgt 10 (=23) +16: load_2 +17: const_0 +18: jge 5 (=23) +21: const_2 +22: store_2 +23: load_2 +24: const_0 +25: print +26: exit +27: return +#CodeGenerationTest.bsp13() +0: enter 0, 3 +3: read +4: putstatic 1 +7: const_1 +8: store_2 +9: getstatic 1 +12: load_2 +13: jle 14 (=27) +16: getstatic 1 +19: const 10 +24: jge 5 (=29) +27: const_2 +28: store_2 +29: load_2 +30: const_0 +31: print +32: exit +33: return +#CodeGenerationTest.bsp14() +0: enter 0, 3 +3: read +4: putstatic 1 +7: const_1 +8: store_2 +9: getstatic 1 +12: load_2 +13: jle 21 (=34) +16: getstatic 1 +19: const 10 +24: jge 12 (=36) +27: getstatic 1 +30: const_5 +31: jle 5 (=36) +34: const_2 +35: store_2 +36: load_2 +37: const_0 +38: print +39: exit +40: return +#CodeGenerationTest.bsp15() +0: enter 0, 3 +3: read +4: store_2 +5: getstatic 1 +8: load_2 +9: jgt 14 (=23) +12: getstatic 1 +15: const_1 +16: add +17: putstatic 1 +20: jmp -15 (=5) +23: getstatic 1 +26: const_0 +27: print +28: exit +29: return +#CodeGenerationTest.bsp16() +0: enter 0, 3 +3: read +4: putstatic 1 +7: getstatic 1 +10: const 12 +15: jgt 8 (=23) +18: const_1 +19: store_2 +20: jmp 5 (=25) +23: const_2 +24: store_2 +25: load_2 +26: const_0 +27: print +28: exit +29: return +#CodeGenerationTest.bsp17() +0: enter 0, 4 +3: read +4: store_2 +5: const_0 +6: store_3 +7: getstatic 1 +10: load_2 +11: jgt 20 (=31) +14: load_3 +15: getstatic 1 +18: add +19: store_3 +20: getstatic 1 +23: const_1 +24: add +25: putstatic 1 +28: jmp -21 (=7) +31: load_3 +32: const_0 +33: print +34: exit +35: return +#CodeGenerationTest.bsp18() +0: enter 0, 4 +3: read +4: store_2 +5: const_0 +6: store_3 +7: const_2 +8: putstatic 1 +11: getstatic 1 +14: load_2 +15: jgt 20 (=35) +18: load_3 +19: getstatic 1 +22: add +23: store_3 +24: getstatic 1 +27: const_1 +28: add +29: putstatic 1 +32: jmp -21 (=11) +35: load_3 +36: const_0 +37: print +38: exit +39: return +#CodeGenerationTest.trappingOrdChrTest() +0: enter 0, 0 +3: const 7 +8: const 7 +13: mul +14: const_0 +15: print +16: trap 1 +18: enter 0, 2 +21: const 33 +26: pop +27: const 42 +32: pop +33: const 33 +38: store_0 +39: const 42 +44: store_1 +45: exit +46: return +#CodeGenerationTest.arrayFromEndWithFunctionCall() +0: enter 0, 0 +3: const_1 +4: exit +5: return +6: trap 1 +8: enter 0, 1 +11: const_2 +12: newarray 1 +14: store_0 +15: load_0 +16: const_0 +17: const 13 +22: astore +23: load_0 +24: dup +25: arraylength +26: call -26 (=0) +29: sub +30: const 42 +35: astore +36: load_0 +37: dup +38: arraylength +39: call -39 (=0) +42: sub +43: aload +44: const_0 +45: print +46: exit +47: return +#CodeGenerationTest.testElseIf() +0: enter 0, 1 +3: read +4: store_0 +5: load_0 +6: const_1 +7: jne 13 (=20) +10: const 9 +15: const_0 +16: print +17: jmp 25 (=42) +20: load_0 +21: const_2 +22: jne 13 (=35) +25: const 8 +30: const_0 +31: print +32: jmp 10 (=42) +35: const 7 +40: const_0 +41: print +42: exit +43: return +#CodeGenerationTest.testRelops() +0: enter 0, 1 +3: read +4: store_0 +5: load_0 +6: const_1 +7: jne 24 (=31) +10: const 61 +15: const_0 +16: bprint +17: const 61 +22: const_0 +23: bprint +24: const 44 +29: const_0 +30: bprint +31: load_0 +32: const_1 +33: jeq 24 (=57) +36: const 33 +41: const_0 +42: bprint +43: const 61 +48: const_0 +49: bprint +50: const 44 +55: const_0 +56: bprint +57: load_0 +58: const_1 +59: jge 17 (=76) +62: const 60 +67: const_0 +68: bprint +69: const 44 +74: const_0 +75: bprint +76: load_0 +77: const_1 +78: jgt 24 (=102) +81: const 60 +86: const_0 +87: bprint +88: const 61 +93: const_0 +94: bprint +95: const 44 +100: const_0 +101: bprint +102: load_0 +103: const_1 +104: jle 17 (=121) +107: const 62 +112: const_0 +113: bprint +114: const 44 +119: const_0 +120: bprint +121: load_0 +122: const_1 +123: jlt 24 (=147) +126: const 62 +131: const_0 +132: bprint +133: const 61 +138: const_0 +139: bprint +140: const 44 +145: const_0 +146: bprint +147: exit +148: return +#CodeGenerationTest.basicOrdChrTest() +0: enter 0, 2 +3: const 65 +8: store_0 +9: load_0 +10: const_0 +11: print +12: const 42 +17: store_0 +18: load_0 +19: const_0 +20: print +21: const 49 +26: store_1 +27: load_1 +28: const_0 +29: bprint +30: exit +31: return +#CodeGenerationTest.testNestedBreak() +0: enter 0, 2 +3: const 21 +8: store_1 +9: const 83 +14: const 84 +19: jge 28 (=47) +22: const 167 +27: const 168 +32: jge 9 (=41) +35: jmp 6 (=41) +38: jmp -16 (=22) +41: jmp 6 (=47) +44: jmp -35 (=9) +47: exit +48: return \ No newline at end of file diff --git a/MicroJava Tests/resources/relops.mj b/MicroJava Tests/resources/relops.mj new file mode 100644 index 0000000..593df68 --- /dev/null +++ b/MicroJava Tests/resources/relops.mj @@ -0,0 +1,14 @@ +program Test +{ + void main() + int a; + { + read(a); + if (a == 1) { print('='); print('='); print(','); } + if (a != 1) { print('!'); print('='); print(','); } + if (a < 1) { print('<'); print(','); } + if (a <= 1) { print('<'); print('='); print(','); } + if (a > 1) { print('>'); print(','); } + if (a >= 1) { print('>'); print('='); print(','); } + } +} \ No newline at end of file diff --git a/MicroJava Tests/tests/ssw/mj/test/CodeGenerationTest.java b/MicroJava Tests/tests/ssw/mj/test/CodeGenerationTest.java new file mode 100644 index 0000000..33d86f0 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/CodeGenerationTest.java @@ -0,0 +1,880 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static ssw.mj.Errors.Message.*; + +public class CodeGenerationTest extends BaseCompilerTestCase { + + /** + * Symbol table for most examples of this test class. + */ + private void expectExampleSymTab() { + expectSymTabUniverse(); + expectSymTab("Program A:"); + expectSymTab(" Constant: int max = 12"); + expectSymTab(" Global Variable 0: char c"); + expectSymTab(" Global Variable 1: int i"); + expectSymTab(" Type B: class (2 fields)"); + expectSymTab(" Local Variable 0: int x"); + expectSymTab(" Local Variable 1: int y"); + expectSymTab(" Method: void main (3 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int[] iarr"); + expectSymTab(" Local Variable 1: class (2 fields) b"); + expectSymTab(" Local Variable 2: int n"); + } + + private void expectSymTabWithSum() { + expectSymTabUniverse(); + expectSymTab("Program A:"); + expectSymTab(" Constant: int max = 12"); + expectSymTab(" Global Variable 0: char c"); + expectSymTab(" Global Variable 1: int i"); + expectSymTab(" Type B: class (2 fields)"); + expectSymTab(" Local Variable 0: int x"); + expectSymTab(" Local Variable 1: int y"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int[] iarr"); + expectSymTab(" Local Variable 1: class (2 fields) b"); + expectSymTab(" Local Variable 2: int n"); + expectSymTab(" Local Variable 3: int sum"); + } + + @Test + public void bsp11() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(i); " + LF + // + " if (i <= n) n = 1;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "1"); + addExpectedRun("1", "0"); + parseVerifyVisualize(); + } + + @Test + public void bsp12() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(i); " + LF + // + " n = 1; " + LF + // + " if (i <= n && n < 0) n = 2;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "1"); + addExpectedRun("2", "1"); + parseVerifyVisualize(); + } + + @Test + public void bsp13() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(i); " + LF + // + " n = 1; " + LF + // + " if (i <= n || i < 10) n = 2;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "2"); + addExpectedRun("2", "2"); + addExpectedRun("20", "1"); + parseVerifyVisualize(); + } + + @Test + public void bsp14() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(i); " + LF + // + " n = 1; " + LF + // + " if (i <= n || i < 10 && i > 5) n = 2;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "2"); + addExpectedRun("2", "1"); + addExpectedRun("6", "2"); + addExpectedRun("20", "1"); + parseVerifyVisualize(); + } + + @Test + public void bsp15() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(n); " + LF + // + " while (i <= n) { i++; }" + LF + // + " print(i); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "1"); + addExpectedRun("-1", "0"); + addExpectedRun("1", "2"); + addExpectedRun("10", "11"); + parseVerifyVisualize(); + } + + @Test + public void bsp16() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " read(i); " + LF + // + " if (i <= max) n = 1; else n = 2;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectExampleSymTab(); + addExpectedRun("0", "1"); + addExpectedRun("13", "2"); + addExpectedRun("12", "1"); + addExpectedRun("-1", "1"); + addExpectedRun("-13", "1"); + parseVerifyVisualize(); + } + + @Test + public void bsp17() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n; int sum;" + LF + // + " {" + LF + // + " read(n); " + LF + // + " sum = 0; " + LF + // + " while (i <= n) { sum += i; i++; }" + LF + // + " print(sum); " + LF + // + " }" + LF + // + "}"); + + expectSymTabWithSum(); + addExpectedRun("0", "0"); + addExpectedRun("-1", "0"); + addExpectedRun("1", "1"); + addExpectedRun("10", "55"); + parseVerifyVisualize(); + } + + @Test + public void bsp18() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n; int sum;" + LF + // + " {" + LF + // + " read(n); " + LF + // + " sum = 0; " + LF + // + " i = 2;" + LF + // + " while (i <= n) { sum += i; i++; }" + LF + // + " print(sum); " + LF + // + " }" + LF + // + "}"); + + expectSymTabWithSum(); + addExpectedRun("0", "0"); + addExpectedRun("-1", "0"); + addExpectedRun("1", "0"); + addExpectedRun("10", "54"); + parseVerifyVisualize(); + } + + @Test + public void methodCall() { + initCode("program A" + LF + // 1 + "{" + LF + // 2 + " void bar() {" + LF + // 3 + " print('b');" + LF + // 4 + " print('a');" + LF + // 5 + " print('r');" + LF + // 6 + " }" + LF + // 7 + " void foo() {" + LF + // 8 + " print('f');" + LF + // 9 + " print('o');" + LF + // 10 + " print('o');" + LF + // 11 + " }" + LF + // 12 + " void main () {" + LF + // 13 + " foo();" + LF + // 14 + " }" + LF + // 15 + "}"); // 16 + + addExpectedRun("", "foo"); + parseVerifyVisualize(); + } + + @Test + public void fib() { + initCode("program A" + LF + // + "{" + LF + // + " int fib(int n) {" + LF + // + " if (n <= 1) return 1; " + LF + // + " return fib(n-1) + fib(n-2); " + LF + // + " }" + LF + // + " void main ()" + LF + // + " int n;" + LF + // + " {" + LF + // + " read(n); " + LF + // + " print(fib(n)); " + LF + // + " }" + LF + // + "}"); + + addExpectedRun("-1", "1"); + addExpectedRun("0", "1"); + addExpectedRun("1", "1"); + addExpectedRun("2", "2"); + addExpectedRun("3", "3"); + addExpectedRun("4", "5"); + addExpectedRun("5", "8"); + addExpectedRun("6", "13"); + addExpectedRun("7", "21"); + addExpectedRun("8", "34"); + addExpectedRun("9", "55"); + addExpectedRun("10", "89"); + addExpectedRun("11", "144"); + addExpectedRun("22", "28657"); + parseVerifyVisualize(); + } + + @Test + public void fibDyn() { + initCode("program A" + LF + // + " int[] matrix; " + LF + // + "{" + LF + // + " int fib(int n) int r; {" + LF + // + " if (n <= 1) return 1; " + LF + // + " if(matrix[n] != 0) return matrix[n]; " + LF + // + " r = fib(n-1) + fib(n-2); " + LF + // + " matrix[n] = r; " + LF + // + " return r; " + LF + // + " }" + LF + // + " void main ()" + LF + // + " int n;" + LF + // + " {" + LF + // + " matrix = new int[1000]; " + LF + // + " read(n); " + LF + // + " print(fib(n)); " + LF + // + " }" + LF + // + "}"); + + addExpectedRun("-1", "1"); + addExpectedRun("0", "1"); + addExpectedRun("1", "1"); + addExpectedRun("2", "2"); + addExpectedRun("3", "3"); + addExpectedRun("4", "5"); + addExpectedRun("5", "8"); + addExpectedRun("6", "13"); + addExpectedRun("7", "21"); + addExpectedRun("8", "34"); + addExpectedRun("9", "55"); + addExpectedRun("10", "89"); + addExpectedRun("11", "144"); + addExpectedRun("22", "28657"); + addExpectedRun("30", "1346269"); + addExpectedRun("40", "165580141"); + addExpectedRun("45", "1836311903"); + parseVerifyVisualize(); + } + + @Test + public void testElseIf() { + initCode("program Test {" + LF + // 1 + " void main() int i; {" + LF + // 2 + " read(i);" + LF + // 3 + " if (i == 1) print(9);" + LF + // 4 + " else if (i == 2) print(8);" + LF + // 5 + " else print(7);" + LF + // 6 + " }" + LF + // 7 + "}"); + addExpectedRun("1", "9"); + addExpectedRun("2", "8"); + addExpectedRun("3", "7"); + addExpectedRun("4", "7"); + parseVerifyVisualize(); + } + + @Test + public void mainVar() { + initCode("program Test" + LF + // + " int main;" + LF + // + "{" + LF + // + "}"); + expectError(4, 2, MAIN_NOT_FOUND); + parseVerifyVisualize(); + } + + @Test + public void mainNotVoid() { + initCode("program Test {" + LF + // + " char main() { }" + LF + // + "}"); + expectError(2, 15, MAIN_NOT_VOID); + parseVerifyVisualize(); + } + + @Test + public void noLoop() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " break;" + LF + // + " }" + LF + // + "}"); + expectError(3, 10, BREAK_OUTSIDE_LOOP); + parseVerifyVisualize(); + } + + @Test + public void returnVoid() { + initCode("program Test {" + LF + // + " void test() {" + LF + // + " return 5;" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(3, 12, UNEXPECTED_RETURN_VALUE); + parseVerifyVisualize(); + } + + @Test + public void wrongReturnType() { + initCode("program Test {" + LF + // + " int test() {" + LF + // + " return 'x';" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(3, 15, RETURN_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void wrongReturnTypeNull() { + initCode("program Test {" + LF + // + " int test() {" + LF + // + " return null;" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(3, 16, RETURN_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void noReturnVal() { + initCode("program Test {" + LF + // + " int test() {" + LF + // + " return;" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(3, 11, MISSING_RETURN_VALUE); + parseVerifyVisualize(); + } + + @Test + public void wrongReturnTypeArr() { + initCode("program Test {" + LF + // + " int[] test() {" + LF + // + " return new int[10];" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(2, 9, ILLEGAL_METHOD_RETURN_TYPE); + parseVerifyVisualize(); + } + + @Test + public void wrongReturnClass() { + initCode("program Test" + LF + // + " class C1 { }" + LF + // + "{" + LF + // + " C1 test() {" + LF + // + " return new C1;" + LF + // + " }" + LF + // + " void main() {}" + LF + // + "}"); + expectError(4, 6, ILLEGAL_METHOD_RETURN_TYPE); + parseVerifyVisualize(); + } + + @Test + public void noMeth() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i(10);" + LF + // + " }" + LF + // + "}"); + expectError(3, 7, CALL_TO_NON_METHOD); + parseVerifyVisualize(); + } + + @Test + public void paramType() { + initCode("program Test {" + LF + // + " void method(int x) { }" + LF + // + " void main() {" + LF + // + " method('a');" + LF + // + " }" + LF + // + "}"); + expectError(4, 15, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void paramTypeArr() { + initCode("program Test {" + LF + // + " void method(int[] x) { }" + LF + // + " void main() {" + LF + // + " method(new char[10]);" + LF + // + " }" + LF + // + "}"); + expectError(4, 24, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void paramTypeClass() { + initCode("program Test" + LF + // + " class C1 { }" + LF + // + " class C2 { }" + LF + // + "{" + LF + // + " void method(C1 c1) { }" + LF + // + " void main() {" + LF + // + " method(new C2);" + LF + // + " }" + LF + // + "}"); + expectError(7, 18, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void moreParams() { + initCode("program Test {" + LF + // + " void method(int x, char c) { }" + LF + // + " void main() {" + LF + // + " method(1, 'a', 1);" + LF + // + " }" + LF + // + "}"); + expectError(4, 21, WRONG_ARGUMENT_COUNT); + parseVerifyVisualize(); + } + + @Test + public void lessParams() { + initCode("program Test {" + LF + // + " void method(int x, char c) { }" + LF + // + " void main() {" + LF + // + " method(1);" + LF + // + " }" + LF + // + "}"); + expectError(4, 13, WRONG_ARGUMENT_COUNT); + parseVerifyVisualize(); + } + + @Test + public void incompTypesCond() { + initCode("program Test {" + LF + // + " void main() int i; { " + LF + // + " if (i > null) { }" + LF + // + " }" + LF + // + "}"); + expectError(3, 17, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void incompTypesCondArr() { + initCode("program Test {" + LF + // + " void main() int[] ia; char[] ca; { " + LF + // + " if (ia > ca) { }" + LF + // + " }" + LF + // + "}"); + expectError(3, 16, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void incompTypesCondClass() { + initCode("program Test" + LF + // + " class C1 { }" + LF + // + "{" + LF + // + " void main() C1 c1; int i; { " + LF + // + " if (c1 > i) { };" + LF + // + " }" + LF + // + "}"); + expectError(5, 15, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void wrongEqCheck() { + initCode("program Test {" + LF + // + " void main() int[] ia1, ia2; {" + LF + // + " if (ia1 > ia2) { }" + LF + // + " }" + LF + // + "}"); + expectError(3, 18, ILLEGAL_REFERENCE_COMPARISON); + parseVerifyVisualize(); + } + + @Test + public void testSimpleBreak() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " while(42 > 0) /* while(true) */" + LF + // + " {" + LF + // + " break;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + parseVerifyVisualize(); + } + + @Test + public void testBreak() { + initCode("program A" + LF + // + " int i;" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int n;" + LF + // + " {" + LF + // + " read(n); " + LF + // + " while (i <= n) { while(1 < 2) { if(1 == 1) { break; } } if(i == 5) break; i++; }" + + LF + // + " print(i); " + LF + // + " }" + LF + // + "}"); + addExpectedRun("10", "5"); + parseVerifyVisualize(); + } + + @Test + public void testNestedBreak() { + initCode("program Test {" + LF + // + " void main() " + LF + // + " int n, o;" + LF + // + " {" + LF + // + " o = 21;" + LF + // + " while(83 < 84)" + LF + // + " {" + LF + // + " while(167 < 168)" + LF + // + " {" + LF + // + " break;" + LF + // + " }" + LF + // + " break;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + parseVerifyVisualize(); + } + + @Test + public void lenTest() { + initCode("program A" + LF + // + " class A { int[] x; }" + LF + // + " class B { A a; }" + LF + // + " class C { B b; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " C[] c;" + LF + // + " {" + LF + // + " c = new C[5];" + LF + // + " print(len(c));" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("5"); + parseVerifyVisualize(); + } + + @Test + public void basicOrdChrTest() { + initCode("program Test {" + LF + // + " void main() int i; char c; {" + LF + // + " i = ord('A');" + LF + // + " print(i);" + LF + // + " i = ord('*');" + LF + // + " print(i);" + LF + // + " c = chr(49);" + LF + // + " print(c);" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("65421"); + parseVerifyVisualize(); + } + + @Test + public void trappingOrdChrTest() { + initCode("program Test {" + LF + // + " int trap() {" + LF + // + " print(7 * 7);" + LF + // + " }" + LF + // + " void main() int i; char c; {" + LF + // + " ord('!');" + LF + // + " chr(42);" + LF + // + " i = ord('!');" + LF + // + " c = chr(42);" + LF + // + " }" + LF + // + "}"); + + addExpectedRun(""); + parseVerifyVisualize(); + } + + @Test + public void unusedReturnVal() { + initCode("program Test {" + LF + // + " int getUnused() {" + LF + // + " return 351;" + LF + // + " }" + LF + // + " int polluteAndGet() {" + LF + // + " getUnused();" + LF + // + " return 42;" + LF + // + " }" + LF + // + " void main() {" + LF + // + " print(932 + polluteAndGet());" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("974"); + parseVerifyVisualize(); + } + + @Test + public void coverUniverseMethod() { + initCode("program Test {" + LF + // + " int cast(char c) { return ord(c); }" + LF + // + " int ord(char c) { return cast(c) - 30; }" + LF + // + " void main() {" + LF + // + " print(chr(ord('A')));" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("#"); + parseVerifyVisualize(); + } + + @Test + public void paramType2() { + initCode("program Test {" + LF + // + " void method(int x, int y) { }" + LF + // + " void main() {" + LF + // + " method(1, 'a');" + LF + // + " }" + LF + // + "}"); + expectError(4, 18, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void paramTypeArr2() { + initCode("program Test {" + LF + // + " void method(int[] x, int y) { }" + LF + // + " void main() {" + LF + // + " method(new int[10], new char[10]);" + LF + // + " }" + LF + // + "}"); + expectError(4, 37, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void paramTypeClass2() { + initCode("program Test" + LF + // + " class C1 { }" + LF + // + " class C2 { }" + LF + // + "{" + LF + // + " void method(C1 c1, C2 c2) { }" + LF + // + " void main() {" + LF + // + " method(new C1, new C1);" + LF + // + " }" + LF + // + "}"); + expectError(7, 26, ARGUMENT_TYPE_MISMATCH); + parseVerifyVisualize(); + } + + @Test + public void testRelops() { + initFile("relops.mj"); + addExpectedRun("0", "!=,<,<=,"); + addExpectedRun("1", "==,<=,>=,"); + addExpectedRun("2", "!=,>,>=,"); + parseVerifyVisualize(); + } + + @Test + public void testAnimals() { + initFile("animals.mj"); + addExpectedRun("0", "cat"); + addExpectedRun("1", "dog"); + addExpectedRun("2", "octopus"); + parseVerifyVisualize(); + } + + @Test + public void compareNeg() { + initCode("program A" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int neg;" + LF + // + " {" + LF + // + " neg = -42;" + LF + // + " if (neg == -42) print(42);" + LF + // + " else print(neg); " + LF + // + " }" + LF + // + "}"); + + addExpectedRun("42"); + parseVerifyVisualize(); + } + + // index from end tests + @Test + public void arrayFromEndWithFunctionCall() { + initCode(""" + program Test + final int len = 2; + { + int const1() { + return 1; + } + + void main() int[] a; { + a = new int[len]; + a[0] = 13; + a[~const1()] = 42; + print(a[~const1()]); + } + }"""); + addExpectedRun("42"); + parseVerifyVisualize(); + } + + @Test + public void createPalindrom() { + initCode(""" + program Test { + void toPalindrom(char[] in, char[] out) int i, l; { + l = len(in); + i = 0; + while (i < l) { + out[i] = in[i]; + out[~(i + 1)] = in[i]; + i++; + } + } + + void printText(char[] text) int i; { + i = 0; + while (i < len(text)) { + print(text[i]); + i++; + } + } + + void main() char[] a, out; int i; { + a = new char[5]; + a[0] = 'l'; + a[1] = 'a'; + a[2] = 'g'; + a[3] = 'e'; + a[4] = 'r'; + + out = new char[10]; + + toPalindrom(a, out); + printText(out); + + a = new char[2]; + a[0] = 'o'; + a[1] = 't'; + + out = new char[4]; + + toPalindrom(a, out); + printText(out); + } + }"""); + addExpectedRun("lagerregalotto"); + parseVerifyVisualize(); + } + + @Test + public void iterateArrayFromEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + a[0] = 1; + a[1] = 2; + a[2] = 3; + i = 1; + while (i <= len) { + print(a[~i]); + i++; + } + } + }"""); + addExpectedRun("321"); + parseVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/ParserTest.java b/MicroJava Tests/tests/ssw/mj/test/ParserTest.java new file mode 100644 index 0000000..1554c89 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/ParserTest.java @@ -0,0 +1,406 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.scanner.Token; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static ssw.mj.Errors.Message.*; + +public class ParserTest extends BaseCompilerTestCase { + + @Test + public void testWorkingFinalDecls() { + initCode("program Test" + LF + // 1 + " final int i = 1;" + LF + // 2 + " final int j = 1;" + LF + // 3 + " final int k = 1;" + LF + // 4 + "{ void main() { } }"); // 5 + parseVerifyVisualize(); + } + + @Test + public void testWorkingDecls() { + initCode("program Test" + LF + // 1 + " int i;" + LF + // 2 + " int j, k;" + LF + // 3 + "{ void main() { } }"); // 4 + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethods() { + initCode("program Test" + LF + // 1 + " int i;" + LF + // 2 + " int j, k;" + LF + // 3 + "{" + LF + // 4 + " void foo() { }" + LF + // 5 + " void bar() { }" + LF + // 6 + " void main() { }" + LF + // 7 + " }" + LF // 8 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodsWithParameters() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo(int i) { }" + LF + // 3 + " void bar(int i, char c) { }" + LF + // 4 + " void main() { }" + LF + // 5 + " }" + LF // 6 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodsWithLocals() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo() int i; { }" + LF + // 3 + " void bar() int i; char c; { }" + LF + // 4 + " void main() { }" + LF + // 5 + " }" + LF // 6 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodsWithParametersAndLocals() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo(char ch) int i; { }" + LF + // 3 + " void bar(int x, int y) int i; char c; { }" + LF + // 4 + " void main() { }" + LF + // 5 + " }" + LF // 6 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodCall() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo(char ch) int i; { }" + LF + // 3 + " void main() { foo('a'); }" + LF + // 4 + " }" + LF // 5 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodCallTwoParams() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo(char ch, int x) int i; { }" + LF + // 3 + " void main() { foo('a', 1); }" + LF + // 4 + " }" + LF // 5 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingMethodCallThreeParams() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo(char ch, int x, char ch2) int i; { }" + LF + // 3 + " void main() { foo('a', 1, 'b'); }" + LF + // 4 + " }" + LF // 5 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingClass() { + initCode("program Test" + LF + // 1 + "class X { int i; int j; }" + LF + // 2 + "{" + LF + // 3 + " void main() X x; { x = new X; }" + LF + // 4 + "}" + LF // 5 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingArray() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void main() int[] x; { x = new int[10]; }" + LF + // 3 + "}" + LF // 4 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingIncDec() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void main() int i; { i--; i++; }" + LF + // 3 + " }" + LF // 4 + ); + parseVerifyVisualize(); + } + + @Test + public void testWorkingElseIf() { + initCode("program Test {" + LF + // 1 + " void main() int i; {" + LF + // 2 + " if (i > 10) i++;" + LF + // 3 + " else if (i < 5) i--;" + LF + // 4 + " else i += 8;" + LF + // 5 + " }" + LF + // 6 + "}"); + parseVerifyVisualize(); + } + + @Test + public void testWorkingLoop() { + initCode("program Test {" + LF + // 1 + " void main () int i; {" + LF + // 2 + " i = 0;" + LF + // 3 + " while (i < 42) {" + LF + // 4 + " i++;" + LF + // 5 + " }" + LF + // 6 + " }" + LF + // 7 + "}"); + parseVerifyVisualize(); + } + + @Test + public void mulAssign() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i = 2;" + LF + // + " i *= 2;" + LF + // + " }" + LF + // + "}"); + parseVerifyVisualize(); + } + + @Test + public void returnExpr() { + initCode("program Test {" + LF + // + " void main() { }" + LF + // + " int wrong1() { " + LF + // + " return 2 + 3;" + LF + // + " }" + LF + // + "}"); + parseVerifyVisualize(); + } + + @Test + public void wrongConstDecl() { + initCode("program Test" + LF + // + " final int i = a;" + LF + // + "{ void main() { } }"); + expectError(2, 17, INVALID_CONST_TYPE); + parseVerifyVisualize(); + } + + @Test + public void wrongDesignFollow() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i**;" + LF + // + " }" + LF + // + "}"); + expectError(3, 6, INVALID_DESIGNATOR_STATEMENT); + parseVerifyVisualize(); + } + + @Test + public void wrongFactor() { + initCode("program Test {" + LF + // + " void main () int i; { " + LF + // + " i = i + if;" + LF + // + " }" + LF + // + "}"); + expectError(3, 13, INVALID_FACTOR); + parseVerifyVisualize(); + } + + @Test + public void wrongRelOp() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " if (i x 5);" + LF + // + " }" + LF + // + "}"); + expectError(3, 11, INVALID_REL_OP); + parseVerifyVisualize(); + } + + @Test + public void wrongStart() { + initCode("noprogram Test { }"); + expectError(1, 1, TOKEN_EXPECTED, "program"); + parseVerifyVisualize(); + } + + @Test + public void noProgName() { + initCode("program { }"); + expectError(1, 9, TOKEN_EXPECTED, "identifier"); + parseVerifyVisualize(); + } + + @Test + public void wrongVarDecl() { + initCode("program Test " + LF + // + "int var1,,,,var2;" + LF + // + "{ void main() { } }"); + expectError(2, 10, TOKEN_EXPECTED, Token.Kind.ident.label()); + parseVerifyVisualize(); + } + + @Test + public void eofExpected() { + initCode("program Test {" + LF + // + " void main() {}" + LF + // + "}moretext"); + expectError(3, 2, TOKEN_EXPECTED, "end of file"); + parseVerifyVisualize(); + } + + @Test + public void invalidEOF1() { + initCode("program Test {" + LF + // + " void main() {"); + expectError(2, 16, TOKEN_EXPECTED, "}"); + parseVerifyVisualize(); + } + + @Test + public void invalidEOF2() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " if ()"); + expectError(3, 9, INVALID_FACTOR); + parseVerifyVisualize(); + } + + @Test + public void invalidEOF3() { + initCode("program Test" + LF + // + " class C {" + LF + // + " int i"); + expectError(3, 10, TOKEN_EXPECTED, ";"); + parseVerifyVisualize(); + } + + @Test + public void testWorkingReadAndPrint() { + initCode("program Test {" + LF + // 1 + " void main() int i; {" + LF + // 2 + " read(i);" + LF + + " print(i);" + LF + + " }" + LF + //3 + "}");//4 + parseVerifyVisualize(); + } + + // index from end tests + @Test + public void wrongTildeInExpr() { + initCode(""" + program Test { + void main() int i; { + i = ~1; + } + }"""); + expectError(3, 9, INVALID_FACTOR); + parseVerifyVisualize(); + } + + @Test + public void wrongTildeCompoundAssign() { + initCode(""" + program Test { + void main() int i; { + i ~= 2; + } + }"""); + expectError(3, 7, INVALID_DESIGNATOR_STATEMENT); + parseVerifyVisualize(); + } + + @Test + public void wrongDoubleTilde() { + initCode(""" + program Test { + void main() int[] a; int i; { + i = a[~~1]; + } + } + """); + expectError(3, 12, INVALID_FACTOR); + parseVerifyVisualize(); + } + + @Test + public void constantArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + a = new int[3]; + a[~1] = 3; + a[~2] = 2; + a[~3] = 1; + i = a[~1]; + print(i); + } + }"""); + parseVerifyVisualize(); + } + + @Test + public void computedArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + a = new int[3]; + a[~(2 * 6 - 11)] = 3; + a[~(9 - 11 + 4)] = 2; + a[~(-(-3))] = 1; + i = a[~(a[0] + 2 * 6 - 11)]; + print(i); + } + }"""); + parseVerifyVisualize(); + } + + @Test + public void dynamicArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + read(i); + a = new int[i]; + a[~i] = 1; + a[~(i - 1)] = 2; + a[~(i - 2)] = 3; + i = a[~i]; + print(i); + } + }"""); + parseVerifyVisualize(); + } + + @Test + public void globalArrayAccessFromEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + a[~len] = 1; + a[~(len - 1)] = 2; + a[~(len - 2)] = 3; + i = a[~len]; + print(i); + } + }"""); + parseVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/RecoverTest.java b/MicroJava Tests/tests/ssw/mj/test/RecoverTest.java new file mode 100644 index 0000000..6d678a7 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/RecoverTest.java @@ -0,0 +1,177 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.Errors.Message; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static ssw.mj.Errors.Message.*; + +public class RecoverTest extends BaseCompilerTestCase { + @Test + public void wrongGlobalDecl() { + initCode("program Test" + LF + // + " 123;" + LF + // + "{ void main() { } }"); + expectError(2, 3, DECLARATION_RECOVERY); + parseVerifyVisualize(); + } + + @Test + public void wrongMethDecl1() { + initCode("program Test {" + LF + // + " void main() { }" + LF + // + " program wrong1() { " + LF + // + " if (1>2);" + LF + // + " }" + LF + // + "}"); + expectError(3, 3, METHOD_DECL_RECOVERY); + parseVerifyVisualize(); + } + + @Test + public void wrongMethDecl2() { + initCode("program Test {" + LF + // + " program wrong1() { " + LF + // + " if (1>2);" + LF + // + " }" + LF + // + " void main() { }" + LF + // + " program wrong2() {" + LF + // + " if (1>2);" + LF + // + " }" + LF + // + "}"); + expectError(2, 3, METHOD_DECL_RECOVERY); + expectError(6, 3, METHOD_DECL_RECOVERY); + parseVerifyVisualize(); + } + + @Test + public void wrongMethDecl3() { + initCode("program Test {" + LF + // + " program wrong1() { }" + LF + // + " void main() { }" + LF + // + " program wrong2() { }" + LF + // + "}"); + expectError(2, 3, METHOD_DECL_RECOVERY); + expectError(4, 3, METHOD_DECL_RECOVERY); + parseVerifyVisualize(); + } + + @Test + public void wrongStat() { + initCode("program Test {" + LF + // + " void main() { " + LF + // + " 123;" + LF + // + " }" + LF + // + "}"); + expectError(3, 5, STATEMENT_RECOVERY); + parseVerifyVisualize(); + } + + @Test + public void multipleErrors() { + initCode("program Test " + LF + // + " int x" + LF + // + "{" + LF + // + " void main( {" + LF + // + " if (1 x 2);" + LF + // + " }" + LF + // + "}"); + expectError(3, 1, TOKEN_EXPECTED, ";"); + expectError(4, 14, TOKEN_EXPECTED, ")"); + expectError(5, 11, INVALID_REL_OP); + parseVerifyVisualize(); + } + + // ---- multiple errors & recovery + @Test + public void noRecover1() { + initCode("program Test {" + LF + // + " void main this method will never recover"); + + expectError(2, 13, TOKEN_EXPECTED, "("); + parseVerifyVisualize(); + } + + @Test + public void noRecover2() { + initCode("program Test {" + LF + // + " void main() { " + LF + // + " if this method will never recover"); + + expectError(3, 8, TOKEN_EXPECTED, "("); + parseVerifyVisualize(); + } + + @Test + public void recoverDecl1() { + initCode("program Test" + LF + // + " int i1, if" + LF + // + " in i2;" + LF + // + " final int i3 = 0;" + LF + // + "{" + LF + // + " void main() { " + LF + // + " if (i1 < i3);" + LF + // + " }" + LF + // + "}"); + + expectError(2, 11, TOKEN_EXPECTED, "identifier"); + parseVerifyVisualize(); + } + + @Test + public void recoverDecl2() { + initCode("program Test" + LF + // + " int i1, if" + LF + // + " in i2;" + LF + // + " int i3;" + LF + // + "{" + LF + // + " void main() { " + LF + // + " if (i1 < i3);" + LF + // + " }" + LF + // + "}"); + + expectError(2, 11, TOKEN_EXPECTED, "identifier"); + parseVerifyVisualize(); + } + + @Test + public void recoverStat() { + initCode("program Test {" + LF + // + " void main() { " + LF + // + " 567 since distance stays too small no follow up errors here;" + LF + // + " if (1 < 2);" + LF + // + " if (1 x 2);" + LF + // + " }" + LF + // + "}"); + + expectError(3, 5, STATEMENT_RECOVERY); + expectError(5, 11, INVALID_REL_OP); + parseVerifyVisualize(); + } + + @Test + public void resetErrDist() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " if () if () if();" + LF + // + " }" + LF + // + "}"); + expectError(3, 9, INVALID_FACTOR); + expectError(3, 15, INVALID_FACTOR); + expectError(3, 20, INVALID_FACTOR); + parseVerifyVisualize(); + } + + @Test + public void illegalMethodStart() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void foo()" + LF + // 3 + " void foo(char x) { }" + LF + // 4 + " void main() { }" + LF + // 5 + "}" + LF // 6 + ); + expectError(4, 3, Message.TOKEN_EXPECTED, "{"); + parseVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/ScannerTest.java b/MicroJava Tests/tests/ssw/mj/test/ScannerTest.java new file mode 100644 index 0000000..43112fb --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/ScannerTest.java @@ -0,0 +1,888 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static ssw.mj.Errors.Message.*; +import static ssw.mj.scanner.Token.Kind.*; + +public class ScannerTest extends BaseCompilerTestCase { + private static final char invalidChar = (char) 65533; + + @Test + public void oneToken() { + initScannerCode(";"); + + expectToken(semicolon, 1, 1); + expectToken(eof, 1, 2); + + scanVerifyVisualize(); + } + + @Test + public void twoTokens() { + initScannerCode(";;"); + + expectToken(semicolon, 1, 1); + expectToken(semicolon, 1, 2); + expectToken(eof, 1, 3); + + scanVerifyVisualize(); + } + + @Test + public void space() { + initScannerCode("; ;"); + + expectToken(semicolon, 1, 1); + expectToken(semicolon, 1, 4); + expectToken(eof, 1, 5); + + scanVerifyVisualize(); + } + + @Test + public void tabulator() { + initScannerCode(";\t\t;"); + + expectToken(semicolon, 1, 1); + expectToken(semicolon, 1, 4); + expectToken(eof, 1, 5); + + scanVerifyVisualize(); + } + + @Test + public void noToken() { + initScannerCode(""); + + expectToken(eof, 1, 1); + + scanVerifyVisualize(); + } + + @Test + public void crLfLineSeparators() { + initScannerCode(";" + CR + LF + " ;" + CR + LF + " ; "); + + expectToken(semicolon, 1, 1); + expectToken(semicolon, 2, 2); + expectToken(semicolon, 3, 3); + expectToken(eof, 3, 5); + + scanVerifyVisualize(); + } + + @Test + public void lFLineSeparators() { + initScannerCode(";" + LF + " ;" + LF + " ; "); + + expectToken(semicolon, 1, 1); + expectToken(semicolon, 2, 2); + expectToken(semicolon, 3, 3); + expectToken(eof, 3, 5); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar1() { + initScannerCode(" {" + invalidChar + "} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, invalidChar); + expectToken(rbrace, 1, 4); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar2() { + initScannerCode(" {\0} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, '\0'); + expectToken(rbrace, 1, 4); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar3() { + initScannerCode(" {&} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, '&'); + expectToken(rbrace, 1, 4); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar4() { + initScannerCode(" {|} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, '|'); + expectToken(rbrace, 1, 4); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar5() { + initScannerCode(" {!} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, '!'); + expectToken(rbrace, 1, 4); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void invalidChar6() { + initScannerCode(" {ident" + invalidChar + "} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "ident"); + expectToken(none, 1, 8); + expectError(1, 8, INVALID_CHAR, invalidChar); + expectToken(rbrace, 1, 9); + expectToken(eof, 1, 11); + + scanVerifyVisualize(); + } + + @Test + public void ident() { + initScannerCode(" {i I i1 i_ i1I_i} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "i"); + expectToken(ident, 1, 5, "I"); + expectToken(ident, 1, 7, "i1"); + expectToken(ident, 1, 10, "i_"); + expectToken(ident, 1, 13, "i1I_i"); + expectToken(rbrace, 1, 18); + expectToken(eof, 1, 20); + + scanVerifyVisualize(); + } + + @Test + public void indentSepararator() { + initScannerCode(" {i[ii]i} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "i"); + expectToken(lbrack, 1, 4); + expectToken(ident, 1, 5, "i"); + expectToken(lss, 1, 6); + expectToken(ident, 1, 7, "i0i_i"); + expectToken(gtr, 1, 12); + expectToken(ident, 1, 13, "i"); + expectToken(rbrack, 1, 14); + expectToken(ident, 1, 15, "i"); + expectToken(rbrace, 1, 16); + expectToken(eof, 1, 18); + + scanVerifyVisualize(); + } + + @Test + public void singleIdent() { + initScannerCode("i"); + + expectToken(ident, 1, 1, "i"); + expectToken(eof, 1, 2); + + scanVerifyVisualize(); + } + + @Test + public void number() { + initScannerCode(" {123 2147483647} "); + + expectToken(lbrace, 1, 2); + expectToken(number, 1, 3, 123); + expectToken(number, 1, 7, 2147483647); + expectToken(rbrace, 1, 17); + expectToken(eof, 1, 19); + + scanVerifyVisualize(); + } + + @Test + public void singleNumber() { + initScannerCode("123"); + + expectToken(number, 1, 1, 123); + expectToken(eof, 1, 4); + + scanVerifyVisualize(); + } + + @Test + public void negativeNumber() { + initScannerCode(" {-123} "); + + expectToken(lbrace, 1, 2); + expectToken(minus, 1, 3); + expectToken(number, 1, 4, 123); + expectToken(rbrace, 1, 7); + expectToken(eof, 1, 9); + + scanVerifyVisualize(); + } + + @Test + public void bigNumber() { + initScannerCode(" {2147483648} "); + + expectToken(lbrace, 1, 2); + expectInvalidToken(number, 1, 3); + expectError(1, 3, BIG_NUM, "2147483648"); + expectToken(rbrace, 1, 13); + expectToken(eof, 1, 15); + + scanVerifyVisualize(); + } + + @Test + public void negativeBigNumber() { + initScannerCode(" {-2147483648} "); + + expectToken(lbrace, 1, 2); + expectToken(minus, 1, 3); + expectInvalidToken(number, 1, 4); + expectError(1, 4, BIG_NUM, "2147483648"); + expectToken(rbrace, 1, 14); + expectToken(eof, 1, 16); + + scanVerifyVisualize(); + } + + @Test + public void reallyBigNumber() { + initScannerCode(" {1234567890123456789012345678901234567890} "); + + expectToken(lbrace, 1, 2); + expectInvalidToken(number, 1, 3); + expectError(1, 3, BIG_NUM, "1234567890123456789012345678901234567890"); + expectToken(rbrace, 1, 43); + expectToken(eof, 1, 45); + + scanVerifyVisualize(); + } + + @Test + public void numberIdent() { + initScannerCode(" {123abc123 123break} "); + + expectToken(lbrace, 1, 2); + expectToken(number, 1, 3, 123); + expectToken(ident, 1, 6, "abc123"); + expectToken(number, 1, 13, 123); + expectToken(break_, 1, 16); + expectToken(rbrace, 1, 21); + expectToken(eof, 1, 23); + + scanVerifyVisualize(); + } + + @Test + public void numbersSeparated() { + initScannerCode("123.456,789"); + + expectToken(number, 1, 1, 123); + expectToken(period, 1, 4); + expectToken(number, 1, 5, 456); + expectToken(comma, 1, 8); + expectToken(number, 1, 9, 789); + expectToken(eof, 1, 12); + + scanVerifyVisualize(); + } + + @Test + public void identsSeparated() { + initScannerCode("abc.def,ghi\njkl"); + + expectToken(ident, 1, 1, "abc"); + expectToken(period, 1, 4); + expectToken(ident, 1, 5, "def"); + expectToken(comma, 1, 8); + expectToken(ident, 1, 9, "ghi"); + expectToken(ident, 2, 1, "jkl"); + expectToken(eof, 2, 4); + + scanVerifyVisualize(); + } + + @Test + public void newlineBetweenIdentifiersAndTokens() { + initScannerCode("anIdentifier" + LF + "class" + LF + "anotherIdentifier"); + + expectToken(ident, 1, 1, "anIdentifier"); + expectToken(class_, 2, 1); + expectToken(ident, 3, 1, "anotherIdentifier"); + + scanVerifyVisualize(); + } + + @Test + public void newlineAndSpacesBetweenIdentifiersAndTokens() { + initScannerCode(" anIdentifier" + LF + " class" + LF + " anotherIdentifier"); + + expectToken(ident, 1, 2, "anIdentifier"); + expectToken(class_, 2, 3); + expectToken(ident, 3, 4, "anotherIdentifier"); + + scanVerifyVisualize(); + } + + @Test + public void charConst() { + initScannerCode(" {' ' 'A' 'z' '0' '!' '\"' '" + invalidChar + "' '\0'} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, ' '); + expectToken(charConst, 1, 7, 'A'); + expectToken(charConst, 1, 11, 'z'); + expectToken(charConst, 1, 15, '0'); + expectToken(charConst, 1, 19, '!'); + expectToken(charConst, 1, 23, '"'); + expectToken(charConst, 1, 27, invalidChar); + expectToken(charConst, 1, 31, '\0'); + expectToken(rbrace, 1, 34); + expectToken(eof, 1, 36); + + scanVerifyVisualize(); + } + + @Test + public void singleCharConst() { + initScannerCode("'x'"); + + expectToken(charConst, 1, 1, 'x'); + expectToken(eof, 1, 4); + + scanVerifyVisualize(); + } + + @Test + public void escapeCharConst() { + initScannerCode(" {'\\n' '\\r' '\\\\' '\\''} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\n'); + expectToken(charConst, 1, 8, '\r'); + expectToken(charConst, 1, 13, '\\'); + expectToken(charConst, 1, 18, '\''); + expectToken(rbrace, 1, 22); + expectToken(eof, 1, 24); + + scanVerifyVisualize(); + } + + @Test + public void singleEscapeCharConst() { + initScannerCode("'\\n'"); + + expectToken(charConst, 1, 1, '\n'); + expectToken(eof, 1, 5); + + scanVerifyVisualize(); + } + + @Test + public void emptyCharConst() { + initScannerCode(" {''} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, EMPTY_CHARCONST); + expectToken(rbrace, 1, 5); + expectToken(eof, 1, 7); + + scanVerifyVisualize(); + } + + @Test + public void unclosedCharConst() { + initScannerCode(" {'a} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, MISSING_QUOTE); + expectToken(rbrace, 1, 5); + expectToken(eof, 1, 7); + + scanVerifyVisualize(); + } + + @Test + public void emptyAndUnclosedCharConst() { + initScannerCode(" ''' "); + + expectToken(charConst, 1, 2, '\0'); + expectError(1, 2, EMPTY_CHARCONST); + expectToken(charConst, 1, 4, '\0'); + expectError(1, 4, MISSING_QUOTE); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void unclosedEscapeCharConst() { + initScannerCode(" {'\\r} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, MISSING_QUOTE); + expectToken(rbrace, 1, 6); + expectToken(eof, 1, 8); + + scanVerifyVisualize(); + } + + @Test + public void unclosedBackslashCharConst() { + initScannerCode(" {'\\'} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, MISSING_QUOTE); + expectToken(rbrace, 1, 6); + expectToken(eof, 1, 8); + + scanVerifyVisualize(); + } + + @Test + public void invalidEscapeCharConst() { + initScannerCode(" {'\\a'} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, UNDEFINED_ESCAPE, 'a'); + expectToken(rbrace, 1, 7); + expectToken(eof, 1, 9); + + scanVerifyVisualize(); + } + + @Test + public void invalidEscapeCharMissingQuote() { + initScannerCode(" '\\a "); + + expectToken(charConst, 1, 2, '\0'); + expectError(1, 2, UNDEFINED_ESCAPE, 'a'); + expectError(1, 2, MISSING_QUOTE); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void fileEndCharConst() { + initScannerCode(" {'"); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, EOF_IN_CHAR); + expectToken(eof, 1, 4); + + scanVerifyVisualize(); + } + + @Test + public void lineEndCharConst() { + initScannerCode(" {'" + LF + "'a'} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, ILLEGAL_LINE_END); + expectToken(charConst, 2, 1, 'a'); + expectToken(rbrace, 2, 4); + expectToken(eof, 2, 6); + + scanVerifyVisualize(); + } + + @Test + public void lineEndWithCRCharConst() { + initScannerCode(" {'" + CR + LF + "'a'} "); + + expectToken(lbrace, 1, 2); + expectToken(charConst, 1, 3, '\0'); + expectError(1, 3, ILLEGAL_LINE_END); + expectToken(charConst, 2, 1, 'a'); + expectToken(rbrace, 2, 4); + expectToken(eof, 2, 6); + + scanVerifyVisualize(); + } + + @Test + public void keyword1() { + initScannerCode(" { if } "); + + expectToken(lbrace, 1, 2); + expectToken(if_, 1, 4); + expectToken(rbrace, 1, 7); + expectToken(eof, 1, 9); + + scanVerifyVisualize(); + } + + @Test + public void keyword2() { + initScannerCode(" {if} "); + + expectToken(lbrace, 1, 2); + expectToken(if_, 1, 3); + expectToken(rbrace, 1, 5); + expectToken(eof, 1, 7); + + scanVerifyVisualize(); + } + + @Test + public void singleKeyword() { + initScannerCode("if"); + + expectToken(if_, 1, 1); + expectToken(eof, 1, 3); + + scanVerifyVisualize(); + } + + @Test + public void keyword3() { + initScannerCode(" {for_} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "for_"); + expectToken(rbrace, 1, 7); + expectToken(eof, 1, 9); + + scanVerifyVisualize(); + } + + @Test + public void keyword4() { + initScannerCode(" {&if} "); + + expectToken(lbrace, 1, 2); + expectToken(none, 1, 3); + expectError(1, 3, INVALID_CHAR, '&'); + expectToken(if_, 1, 4); + expectToken(rbrace, 1, 6); + expectToken(eof, 1, 8); + + scanVerifyVisualize(); + } + + @Test + public void caseSensitive1() { + initScannerCode(" {For} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "For"); + expectToken(rbrace, 1, 6); + expectToken(eof, 1, 8); + + scanVerifyVisualize(); + } + + @Test + public void caseSensitive2() { + initScannerCode(" {FOR} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "FOR"); + expectToken(rbrace, 1, 6); + expectToken(eof, 1, 8); + + scanVerifyVisualize(); + } + + @Test + public void simpleSingleLineComment() { + initScannerCode(" {/* Simple / single * line comment. */} "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 1, 40); + expectToken(eof, 1, 42); + + scanVerifyVisualize(); + } + + @Test + public void simpleMultiLineComment() { + initScannerCode(" {" + LF + " /* Simple " + LF + " / multi * line " + LF // + + " comment. */ " + LF + " } "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 5, 2); + expectToken(eof, 5, 4); + + scanVerifyVisualize(); + } + + @Test + public void nestedSingleLineComment2() { + initScannerCode(" {/*//*///****/**/*/} "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 1, 21); + expectToken(eof, 1, 23); + + scanVerifyVisualize(); + } + + @Test + public void nestedSingleLineComment() { + initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/} "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 1, 62); + expectToken(eof, 1, 64); + + scanVerifyVisualize(); + } + + @Test + public void nestedMultiLineComment() { + initScannerCode(" {" + LF + " /* This / is * a " + LF + " /* nested " + LF // + + " /* multi line */" + LF + " comment. " + LF + " */" + LF // + + " */ " + LF + " } "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 8, 2); + expectToken(eof, 8, 4); + + scanVerifyVisualize(); + } + + @Test + public void nestedMultiLineComment2() { + initScannerCode(" {" + LF + " /* This / is * a " + LF + " /* nested " + LF // + + " /* multi /*/* double nestet */*/ line */" + LF + " comment. " + LF + " */" + LF // + + " */ " + LF + " } "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 8, 2); + expectToken(eof, 8, 4); + + scanVerifyVisualize(); + } + + @Test + public void commentAtEnd1() { + initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/ "); + + expectToken(lbrace, 1, 2); + expectToken(eof, 1, 63); + + scanVerifyVisualize(); + } + + @Test + public void commentAtEnd2() { + initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/"); + + expectToken(lbrace, 1, 2); + expectToken(eof, 1, 62); + + scanVerifyVisualize(); + } + + @Test + public void unclosedComment() { + initScannerCode(" {/* This / is * a nested unclosed comment. } "); + + expectToken(lbrace, 1, 2); + expectError(1, 3, EOF_IN_COMMENT); + expectToken(eof, 1, 47); + + scanVerifyVisualize(); + } + + @Test + public void unclosedComment2() { + initScannerCode(" {/*/"); + + expectToken(lbrace, 1, 2); + expectError(1, 3, EOF_IN_COMMENT); + expectToken(eof, 1, 6); + + scanVerifyVisualize(); + } + + @Test + public void nestedUnclosedComment() { + initScannerCode(" {/* This / is * a /* nested /* unclosed comment. */} "); + + expectToken(lbrace, 1, 2); + expectError(1, 3, EOF_IN_COMMENT); + expectToken(eof, 1, 55); + + scanVerifyVisualize(); + } + + @Test + public void nestedUnclosedComment2() { + initScannerCode(" {/* This / is * a nested unclosed /* comment. } */"); + + expectToken(lbrace, 1, 2); + expectError(1, 3, EOF_IN_COMMENT); + expectToken(eof, 1, 52); + + scanVerifyVisualize(); + } + + @Test + public void noLineComment() { + initScannerCode(" {This is // no comment} "); + + expectToken(lbrace, 1, 2); + expectToken(ident, 1, 3, "This"); + expectToken(ident, 1, 8, "is"); + expectToken(slash, 1, 11); + expectToken(slash, 1, 12); + expectToken(ident, 1, 14, "no"); + expectToken(ident, 1, 17, "comment"); + expectToken(rbrace, 1, 24); + expectToken(eof, 1, 26); + + scanVerifyVisualize(); + } + + @Test + public void multipleComments() { + initScannerCode(" {/*a*/ /*b*/ /*c*/ } "); + + expectToken(lbrace, 1, 2); + expectToken(rbrace, 1, 23); + expectToken(eof, 1, 25); + + scanVerifyVisualize(); + } + + // index from end tests + + @Test + public void negativeIndexFromEnd() { + initScannerCode("~-7"); + + expectToken(tilde, 1, 1); + expectToken(minus, 1, 2); + expectToken(number, 1, 3, 7); + expectToken(eof, 1, 4); + + scanVerifyVisualize(); + } + + @Test + public void singleTildeChar() { + initScannerCode("'~'"); + + expectToken(charConst, 1, 1, '~'); + expectToken(eof, 1, 4); + + scanVerifyVisualize(); + } + + @Test + public void commentInIndexFromEnd() { + initScannerCode("~/*comment*/7"); + + expectToken(tilde, 1, 1); + expectToken(number, 1, 13, 7); + expectToken(eof, 1,14); + + scanVerifyVisualize(); + } + + @Test + public void doubleTilde() { + initScannerCode("~~"); + + expectToken(tilde, 1, 1); + expectToken(tilde, 1, 2); + expectToken(eof, 1,3); + + scanVerifyVisualize(); + } + + @Test + public void allTokens() { + initScannerCode("anIdentifier 123 'c'" + LF // + + "+ - * / % == != < <= > >= && || = += -= *= /= %= ++ -- ; , . ( ) [ ] { }" + LF // + + "break class else final if new print program read return void while ~" + LF); + + expectToken(ident, 1, 1, "anIdentifier"); + expectToken(number, 1, 14, 123); + expectToken(charConst, 1, 18, 'c'); + expectToken(plus, 2, 1); + expectToken(minus, 2, 3); + expectToken(times, 2, 5); + expectToken(slash, 2, 7); + expectToken(rem, 2, 9); + expectToken(eql, 2, 11); + expectToken(neq, 2, 14); + expectToken(lss, 2, 17); + expectToken(leq, 2, 19); + expectToken(gtr, 2, 22); + expectToken(geq, 2, 24); + expectToken(and, 2, 27); + expectToken(or, 2, 30); + expectToken(assign, 2, 33); + expectToken(plusas, 2, 35); + expectToken(minusas, 2, 38); + expectToken(timesas, 2, 41); + expectToken(slashas, 2, 44); + expectToken(remas, 2, 47); + expectToken(pplus, 2, 50); + expectToken(mminus, 2, 53); + expectToken(semicolon, 2, 56); + expectToken(comma, 2, 58); + expectToken(period, 2, 60); + expectToken(lpar, 2, 62); + expectToken(rpar, 2, 64); + expectToken(lbrack, 2, 66); + expectToken(rbrack, 2, 68); + expectToken(lbrace, 2, 70); + expectToken(rbrace, 2, 72); + expectToken(break_, 3, 1); + expectToken(class_, 3, 7); + expectToken(else_, 3, 13); + expectToken(final_, 3, 18); + expectToken(if_, 3, 24); + expectToken(new_, 3, 27); + expectToken(print, 3, 31); + expectToken(program, 3, 37); + expectToken(read, 3, 45); + expectToken(return_, 3, 50); + expectToken(void_, 3, 57); + expectToken(while_, 3, 62); + expectToken(tilde, 3, 68); + expectToken(eof, 4, 1); + + scanVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/SimpleCodeGenerationTest.java b/MicroJava Tests/tests/ssw/mj/test/SimpleCodeGenerationTest.java new file mode 100644 index 0000000..15af513 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/SimpleCodeGenerationTest.java @@ -0,0 +1,1076 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.codegen.Operand; +import ssw.mj.symtab.Obj; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static ssw.mj.Errors.Message.*; + +public class SimpleCodeGenerationTest extends BaseCompilerTestCase { + + /** + * Symbol table for most examples of this test class. + */ + private void expectSymTab() { + expectSymTabUniverse(); + expectSymTab("Program A:"); + expectSymTab(" Constant: int max = 12"); + expectSymTab(" Global Variable 0: char c"); + expectSymTab(" Global Variable 1: int i"); + expectSymTab(" Type B: class (2 fields)"); + expectSymTab(" Local Variable 0: int x"); + expectSymTab(" Local Variable 1: int y"); + expectSymTab(" Method: void main (3 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int[] iarr"); + expectSymTab(" Local Variable 1: class (2 fields) b"); + expectSymTab(" Local Variable 2: int n"); + } + + @Test + public void undefNameMeth() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " method();" + LF + // + " }" + LF + // + "}"); + expectError(3, 11, NAME_NOT_FOUND, "method"); + parseVerifyVisualize(); + } + + @Test + public void forwardDeclErrorMissingMethod() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void main() { foo(); }" + LF + // 3 + " void foo() {}" + LF + // 4 + "}" + LF // 5 + ); + expectError(3, 20, NAME_NOT_FOUND, "foo"); + + parseVerifyVisualize(); + } + + @Test + public void undefNameVar() { + initCode("program Test {" + LF + // + " void main() {" + LF + // + " var++;" + LF + // + " }" + LF + // + "}"); + expectError(3, 8, NAME_NOT_FOUND, "var"); + parseVerifyVisualize(); + } + + @Test + public void bspEmpty() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + + parseVerifyVisualize(); + } + + @Test + public void bsp01() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " n = 3;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("3"); + parseVerifyVisualize(); + } + + @Test + public void bsp01a() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " n = -1 + 2;" + LF + // + " print(n); " + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("1"); + parseVerifyVisualize(); + } + + @Test + public void bsp02() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " i = 10;" + LF + // + " print(i);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("10"); + parseVerifyVisualize(); + } + + @Test + public void bsp03() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " i = 1;" + LF + // + " n = 3 + i;" + LF + // + " print(n);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("4"); + parseVerifyVisualize(); + } + + @Test + public void bsp04() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " i = 1;" + LF + // + " n = 3 + i * max - n;" + LF + // + " print(n);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("15"); + parseVerifyVisualize(); + } + + @Test + public void bsp05() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " iarr = new int[10];" + LF + // + " iarr[5] = 10;" + LF + // + " print(iarr[0]);" + LF + // + " print(iarr[5]);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + + addExpectedRun("010"); + parseVerifyVisualize(); + } + + @Test + public void bsp06() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " iarr = new int[10];" + LF + // + " iarr[5] = 10;" + LF + // + " b = new B;" + LF + // + " b.y = iarr[5] * 3;" + LF + // + " print(b.y);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("30"); + + parseVerifyVisualize(); + } + + @Test + public void bsp07() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " n--;" + LF + // + " print(n);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("-1"); + parseVerifyVisualize(); + } + + @Test + public void bsp08() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " i--;" + LF + // + " print(i);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("-1"); + parseVerifyVisualize(); + } + + @Test + public void bsp09() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " b = new B;" + LF + // + " b.y--;" + LF + // + " print(b.y);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("-1"); + parseVerifyVisualize(); + } + + @Test + public void bsp10() { + initCode("program A" + LF + // + " final int max = 12;" + LF + // + " char c; int i;" + LF + // + " class B { int x, y; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int[] iarr; B b; int n;" + LF + // + " {" + LF + // + " iarr = new int[10];" + LF + // + " iarr[0]--;" + LF + // + " print(iarr[0]);" + LF + // + " }" + LF + // + "}"); + + expectSymTab(); + addExpectedRun("-1"); + parseVerifyVisualize(); + } + + // ---- Errors in Code.java + @Test + public void noVarMethod() { + initCode("program Test {" + LF + // + " int method() { return 0; }" + LF + // + " void main() int i; {" + LF + // + " method = i;" + LF + // + " }" + LF + // + "}"); + expectError(4, 12, CANNOT_STORE_TO_READONLY, Operand.Kind.Meth.name()); + parseVerifyVisualize(); + } + + @Test + public void noVarIncMethod() { + initCode("program Test {" + LF + // + " int method() { return 0; }" + LF + // + " void main() int i; {" + LF + // + " method++;" + LF + // + " }" + LF + // + "}"); + expectError(4, 11, CANNOT_STORE_TO_READONLY, Operand.Kind.Meth.name()); + parseVerifyVisualize(); + } + + @Test + public void noOperand() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " Test = i;" + LF + // + " }" + LF + // + "}"); + expectError(3, 10, ILLEGAL_OPERAND_KIND, Obj.Kind.Prog); + parseVerifyVisualize(); + } + + @Test + public void noValueAssign() { + initCode("program Test {" + LF + // + " char method() { return 'a'; }" + LF + // + " void main() char c; {" + LF + // + " c = method;" + LF + // + " }" + LF + // + "}"); + expectError(4, 15, CANNOT_LOAD_OPERAND); + parseVerifyVisualize(); + } + + @Test + public void noValueCalc() { + initCode("program Test {" + LF + // + " int method() { return 0; }" + LF + // + " void main() int i; {" + LF + // + " i = 5 * method;" + LF + // + " }" + LF + // + "}"); + expectError(4, 19, CANNOT_LOAD_OPERAND); + parseVerifyVisualize(); + } + + @Test + public void noValueInc() { + initCode("program Test {" + LF + // + " int method() { return 0; }" + LF + // + " void main() int i; {" + LF + // + " i += method;" + LF + // + " }" + LF + // + "}"); + expectError(4, 16, CANNOT_LOAD_OPERAND); + parseVerifyVisualize(); + } + + @Test + public void assignPlusNoIntOp() { + initCode("program Test {" + LF + // + " void main() int i; char c; {" + LF + // + " c += i;" + LF + // + " }" + LF + // + "}"); + expectError(3, 11, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void assignTimesNoIntOp() { + initCode("program Test {" + LF + // + " void main() int i; char c; {" + LF + // + " i *= c;" + LF + // + " }" + LF + // + "}"); + expectError(3, 11, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void incompTypes() { + initCode("program Test {" + LF + // + " void main() int i; { " + LF + // + " i = null;" + LF + // + " }" + LF + // + "}"); + expectError(3, 13, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void incompTypesArr() { + initCode("program Test {" + LF + // + " void main() int[] ia; char[] ca; { " + LF + // + " ia = ca;" + LF + // + " }" + LF + // + "}"); + expectError(3, 12, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void incompTypesClass() { + initCode("program Test" + LF + // + " class C1 { }" + LF + // + " class C2 { }" + LF + // + "{" + LF + // + " void main() C1 c1; C2 c2; { " + LF + // + " c1 = c2;" + LF + // + " }" + LF + // + "}"); + expectError(6, 12, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void noIntegerInc() { + initCode("program Test {" + LF + // + " void main() char ch; {" + LF + // + " ch++;" + LF + // + " }" + LF + // + "}"); + expectError(3, 7, INC_DEC_EXPECTS_INT); + parseVerifyVisualize(); + } + + @Test + public void noIntegerDec() { + initCode("program Test {" + LF + // + " void main() int[] ia; {" + LF + // + " ia--;" + LF + // + " }" + LF + // + "}"); + expectError(3, 7, INC_DEC_EXPECTS_INT); + parseVerifyVisualize(); + } + + @Test + public void wrongReadValue() { + initCode("program Test {" + LF + // + " void main() int[] ia; { " + LF + // + " read(ia);" + LF + // + " }" + LF + // + "}"); + expectError(3, 12, ILLEGAL_READ_ARGUMENT); + parseVerifyVisualize(); + } + + @Test + public void wrongPrintValue() { + initCode("program Test" + LF + // + " class C { }" + LF + // + "{" + LF + // + " void main() C obj; { " + LF + // + " print(obj);" + LF + // + " }" + LF + // + "}"); + expectError(5, 14, ILLEGAL_PRINT_ARGUMENT); + parseVerifyVisualize(); + } + + @Test + public void noIntUnaryMinus() { + initCode("program Test {" + LF + // + " void main() int i; char c; {" + LF + // + " i = -c;" + LF + // + " }" + LF + // + "}"); + expectError(3, 11, UNARY_MINUS_EXPECTS_INT); + parseVerifyVisualize(); + } + + @Test + public void noIntOpPlus() { + initCode("program Test {" + LF + // + " void main() int i; int[] ia; {" + LF + // + " i = i + ia;" + LF + // + " }" + LF + // + "}"); + expectError(3, 15, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void noIntOpTimes() { + initCode("program Test {" + LF + // + " void main() int i; int[] ia; {" + LF + // + " i = ia * i;" + LF + // + " }" + LF + // + "}"); + expectError(3, 15, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void procAsFunc() { + initCode("program Test {" + LF + // + " void main() int x; {" + LF + // + " x = main();" + LF + // + " }" + LF + // + "}"); + expectError(3, 13, VOID_CALL_IN_EXPRESSION); + parseVerifyVisualize(); + } + + @Test + public void noTypeNew() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i = new main;" + LF + // + " }" + LF + // + "}"); + expectError(3, 17, TYPE_EXPECTED); + parseVerifyVisualize(); + } + + @Test + public void wrongArraySize() { + initCode("program Test {" + LF + // + " void main() int[] ia; {" + LF + // + " ia = new int[ia];" + LF + // + " }" + LF + // + "}"); + expectError(3, 20, ARRAY_SIZE_EXPECTS_INT); + parseVerifyVisualize(); + } + + @Test + public void noClass() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i = i.i;" + LF + // + " }" + LF + // + "}"); + expectError(3, 10, FIELD_ACCESS_TO_NON_CLASS); + parseVerifyVisualize(); + } + + @Test + public void noArrayIndex() { + initCode("program Test {" + LF + // + " void main() int[] ia; {" + LF + // + " ia[ia] = 1;" + LF + // + " }" + LF + // + "}"); + expectError(3, 10, ARRAY_INDEX_EXPECTS_INT); + parseVerifyVisualize(); + } + + @Test + public void noArray() { + initCode("program Test {" + LF + // + " void main() char c; int i; {" + LF + // + " print(c[i]);" + LF + // + " }" + LF + // + "}"); + expectError(3, 12, INDEXED_ACCESS_TO_NON_ARRAY); + expectError(3, 15, CANNOT_LOAD_OPERAND); + parseVerifyVisualize(); + } + + @Test + public void noTypeNewArray() { + initCode("program A {" + LF + // + " void main () {" + LF + // + " print(len(new null[10]));" + LF + // + " }" + LF + // + "}"); + expectError(3, 23, TYPE_EXPECTED); + parseVerifyVisualize(); + } + + @Test + public void testPrint() { + initCode("program A {" + LF + // + " void main () {" + LF + // + " print('a');" + LF + // + " print('b',1);" + LF + // + " print('c',2);" + LF + // + " print('d',3);" + LF + // + " print('e',4);" + LF + // + " }" + LF + // + "}"); + addExpectedRun("ab c d e"); + parseVerifyVisualize(); + } + + @Test + public void testDesignator() { + initCode("program A" + LF + // + " class A { int x; }" + LF + // + " class B { A a; }" + LF + // + " class C { B b; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " A a; B b; C c;" + LF + // + " {" + LF + // + " c = new C;" + LF + // + " c.b = new B;" + LF + // + " c.b.a = new A;" + LF + // + " c.b.a.x++;" + LF + // + " print(c.b.a.x);" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("1"); + parseVerifyVisualize(); + } + + @Test + public void testArrayAndDesignator() { + initCode("program A" + LF + // + " class A { int[] x; }" + LF + // + " class B { A a; }" + LF + // + " class C { B b; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " A a; B b; C[] c;" + LF + // + " {" + LF + // + " c = new C[5];" + LF + // + " c[0] = new C;" + LF + // + " c[0].b = new B;" + LF + // + " c[0].b.a = new A;" + LF + // + " c[0].b.a.x = new int[10];" + LF + // + " c[3] = new C;" + LF + // + " c[3].b = new B;" + LF + // + " c[3].b.a = new A;" + LF + // + " c[3].b.a.x = new int[30];" + LF + // + " c[0].b.a.x[0]--;" + LF + // + " c[0].b.a.x[8]++;" + LF + // + " c[3].b.a.x[2]++;" + LF + // + " c[3].b.a.x[2]*=3;" + LF + // + " c[0].b.a.x[8]+=50 + c[3].b.a.x[2] * c[3].b.a.x[2] * c[0].b.a.x[0];" + LF + // + " print(c[0].b.a.x[8]);" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("42"); + parseVerifyVisualize(); + } + + @Test + public void testArrayAndDesignatorAndAssign() { + initCode("program A" + LF + // + " class A { int[] x; }" + LF + // + " class B { A a; }" + LF + // + " class C { B b; }" + LF + // + "{" + LF + // + " void main ()" + LF + // + " A a; B b; C[] c;" + LF + // + " {" + LF + // + " c = new C[5];" + LF + // + " c[0] = new C;" + LF + // + " c[0].b = new B;" + LF + // + " c[0].b.a = new A;" + LF + // + " c[0].b.a.x = new int[2];" + LF + // + " c[3] = new C;" + LF + // + " c[3].b = new B;" + LF + // + " c[3].b.a = new A;" + LF + // + " c[3].b.a.x = new int[3];" + LF + // + " c[0].b.a.x[1]++;" + LF + // + " c[0].b.a.x[1]*=256;" + LF + // + " c[0].b.a.x[1]/=2;" + LF + // + " c[0].b.a.x[1]--;" + LF + // + " c[0].b.a.x[1]%=64;" + LF + // + " c[3].b.a.x[2]++;" + LF + // + " c[3].b.a.x[2]*=21;" + LF + // + " c[0].b.a.x[1]-=c[3].b.a.x[2];" + LF + // + " print(c[0].b.a.x[1]);" + LF + // + " }" + LF + // + "}"); + + addExpectedRun("42"); + parseVerifyVisualize(); + } + + @Test + public void testArrayIndexExpression() { + initCode("program A" + LF + // 1 + "{" + LF + // 2 + " void main()" + LF + // 3 + " int[] arr;" + LF + // 4 + " {" + LF + // 5 + " arr = new int[10];" + LF + // 6 + " arr[ ( 1 + 2 ) * 3 ] = 4;" + LF + // 7 + " arr[ 4 - 2 * 2 ] = 2;" + LF + // 8 + " print(arr[ 90 / 10 ]);" + LF + // 9 + " print(arr[ 6 - 3 * 2 ]);" + LF + // 10 + " }" + LF + // 11 + "}"); + addExpectedRun("42"); + parseVerifyVisualize(); + } + + @Test + public void testReadAndPrint() { + initCode("program A" + LF + // 1 + "{" + LF + // 2 + " void main()" + LF + // 3 + " int n;" + LF + // 4 + " {" + LF + // 5 + " n = 0;" + LF + // 6 + " read(n);" + LF + // 7 + " print(n);" + LF + // 7 + " }" + LF + // 9 + "}"); + addExpectedRun("2", "2"); + parseVerifyVisualize(); + } + + @Test + public void testFields() { + initCode("program A" + LF + // + " class A { int x; }" + LF + // + " class B { A a; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " A a;" + LF + // + " B b;" + LF + // + " {" + LF + // + " a = new A;" + LF + // + " b = new B;" + LF + // + " a.x = 20;" + LF + // + " a.x++;" + LF + // + " a.x /= 7;" + LF + // + " a.x *= a.x;" + LF + // + " a.x %= a.x - 5;" + LF + // + " b.a = new A;" + LF + // + " b.a.x = -12;" + LF + // + " b.a.x -= a.x;" + LF + // + " b.a.x *= -a.x;" + LF + // + " b.a.x %= 5;" + LF + // + " b.a.x *= a.x + 2 * 3;" + LF + // + " print(b.a.x);" + LF + // + " }" + LF + // + "}"); + addExpectedRun("21"); + parseVerifyVisualize(); + } + + @Test + public void twoMethods() { + initCode("program A" + LF + // 1 + "{" + LF + // 2 + " void deadMethodToMoveMainPcFrom0()" + LF + // 3 + " int n;" + LF + // 4 + " {" + LF + // 5 + " n = 0;" + LF + // 6 + " }" + LF + // 7 + " void main()" + LF + // 8 + " {" + LF + // 9 + " print(2);" + LF + // 10 + " }" + LF + // 11 + "}"); + addExpectedRun("2"); + parseVerifyVisualize(); + assertTrue( + parser.code.mainpc > 0, + "In this example mainpc must be > 0, most likely it should be 7, but it is: " + parser.code.mainpc); + } + + @Test + public void noMain() { + initCode("program Test {" + LF + // + " void main_() { }" + LF + // + "}"); + expectError(3, 2, MAIN_NOT_FOUND); + parseVerifyVisualize(); + } + + @Test + public void noValueAssignopMethod() { + initCode("program Test {" + LF + // + " int method() { return 0; }" + LF + // + " void main() int i; {" + LF + // + " method += i;" + LF + // + " }" + LF + // + "}"); + expectError(4, 12, CANNOT_STORE_TO_READONLY, Operand.Kind.Meth.name()); + parseVerifyVisualize(); + } + + @Test + public void testMulops() { + initCode("program Mulops" + LF + // + "{" + LF + // + " void main ()" + LF + // + " int a; int b;" + LF + // + " {" + LF + // + " a = 42;" + LF + // + " b = 3;" + LF + // + " a = a / b;" + LF + // + " a = a % ( b * b );" + LF + // + " print(a);" + LF + // + " }" + LF + // + "}"); + addExpectedRun("5"); + parseVerifyVisualize(); + } + + @Test + public void testLocalVarsIncDec() { + initCode("program LocalVars" + LF + // + "{" + LF + // + " void main()" + LF + // + " int a;" + LF + // + " int b;" + LF + // + " {" + LF + // + " a = 2;" + LF + // + " b = 5;" + LF + // + " a++;" + LF + // + " b--;" + LF + // + " print(a+b);" + LF + // + " }" + LF + // + "}"); + addExpectedRun("7"); + parseVerifyVisualize(); + } + + @Test + public void testConstDecl() { + initCode("program ConstDecl" + LF + // + " final int a = 100;" + LF + // + " final char b = 'A';" + LF + // + "{" + LF + // + " void main()" + LF + // + " {" + LF + // + " print(a);" + LF + // + " print(b);" + LF + // + " }" + LF + // + "}"); + addExpectedRun("100A"); + parseVerifyVisualize(); + } + + @Test + public void testMethodAsOperand() { + initCode("program ConstDecl" + LF + // + "{" + LF + // + " int foo() {}" + LF + // + " void main()" + LF + // + " {" + LF + // + " foo++;" + LF + // + " }" + LF + // + "}"); + expectError(6, 8, CANNOT_STORE_TO_READONLY, Operand.Kind.Meth.name()); + parseVerifyVisualize(); + } + + @Test + public void testTypeAsOperand() { + initCode("program ConstDecl" + LF + // + "class Foo {}" + LF + // + "{" + LF + // + " void main()" + LF + // + " {" + LF + // + " Foo++;" + LF + // + " }" + LF + // + "}"); + expectError(6, 8, ILLEGAL_OPERAND_KIND, Obj.Kind.Type); + parseVerifyVisualize(); + } + + @Test + public void writeConstant() { + initCode("program Test" + LF + // + " final int max = 42;" + LF + // + "{" + LF + // + " void main() {" + LF + // + " max = 68;" + LF + // + " }" + LF + // + "}"); + expectError(5, 9, CANNOT_STORE_TO_READONLY, Operand.Kind.Con.name()); + parseVerifyVisualize(); + } + + @Test + public void negativeArrayIndex() { + initCode(""" + program Test { + void main() int[] a; int i; { + a = new int[1]; + i = a[-1]; + print(i); + } + }"""); + addFailingRun("index out of bounds"); + parseVerifyVisualize(); + } + + // index from end tests + + @Test + public void constantArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + a = new int[3]; + a[~1] = 3; + a[~2] = 2; + a[~3] = 1; + i = a[~1]; + print(i); + } + }"""); + addExpectedRun("3"); + parseVerifyVisualize(); + } + + @Test + public void computedArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + a = new int[3]; + a[~(2 * 6 - 11)] = 3; + a[~(9 - 11 + 4)] = 2; + a[~(-(-3))] = 1; + i = a[~(a[0] + 2 * 6 - 11)]; + print(i); + } + }"""); + addExpectedRun("2"); + parseVerifyVisualize(); + } + + @Test + public void dynamicArrayAccessFromEnd() { + initCode(""" + program Test { + void main() int[] a; int i; { + read(i); + a = new int[i]; + a[~i] = 1; + a[~(i - 1)] = 2; + a[~(i - 2)] = 3; + i = a[~i]; + print(i); + } + }"""); + addExpectedRun("3", "1"); + parseVerifyVisualize(); + } + + @Test + public void globalArrayAccessFromEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + a[~len] = 1; + a[~(len - 1)] = 2; + a[~(len - 2)] = 3; + i = a[~len]; + print(i); + } + }"""); + addExpectedRun("1"); + parseVerifyVisualize(); + } + + @Test + public void arrayAccessFromZeroEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + i = a[~0]; + print(i); + } + }"""); + addFailingRun("index out of bounds"); + parseVerifyVisualize(); + } + + @Test + public void negativeOutOfBoundsArrayAccessFromEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + i = a[~(-20)]; + print(i); + } + }"""); + addFailingRun("index out of bounds"); + parseVerifyVisualize(); + } + + @Test + public void positiveOutOfBoundsArrayAccessFromEnd() { + initCode(""" + program Test + final int len = 3; + { + void main() int[] a; int i; { + a = new int[len]; + i = a[~1000]; + print(i); + } + }"""); + addFailingRun("index out of bounds"); + parseVerifyVisualize(); + } + + @Test + public void arrayFromEndIncrement() { + initCode(""" + program Test + final int len = 1; + { + void main() int[] a; { + a = new int[len]; + a[~1]++; + print(a[0]); + } + }"""); + addExpectedRun("1"); + parseVerifyVisualize(); + } + + @Test + public void arrayFromEndCompoundAssignment() { + initCode(""" + program Test + final int len = 2; + { + void main() int[] a; { + a = new int[len]; + a[0] = 1; + a[1] = 1; + a[~1]++; + a[~2]--; + print(a[0]); + print(a[1]); + } + }"""); + addExpectedRun("02"); + parseVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/SymbolTableTest.java b/MicroJava Tests/tests/ssw/mj/test/SymbolTableTest.java new file mode 100644 index 0000000..2b43744 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/SymbolTableTest.java @@ -0,0 +1,1102 @@ +package ssw.mj.test; + +import org.junit.jupiter.api.Test; +import ssw.mj.test.support.BaseCompilerTestCase; + +import static ssw.mj.Errors.Message.*; + +public class SymbolTableTest extends BaseCompilerTestCase { + + @Test + public void shortestProgram() { + initCode("program Test { void main() { } }"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void definitions() { + initCode("program Test" + LF + // + " class myClass {" + LF + // + " int i;" + LF + // + " char c;" + LF + // + " int[] ia;" + LF + // + " myClass o;" + LF + // + " myClass[] oa;" + LF + // + " }" + LF + // + " final int fi = 20;" + LF + // + " final char fc = 'x';" + LF + // + " int gi;" + LF + // + " char gc;" + LF + // + " int[] gia;" + LF + // + " myClass go;" + LF + // + " myClass[] goa;" + LF + // + "{" + LF + // + " int method() " + LF + // + " int i;" + LF + // + " char c;" + LF + // + " char[] ca;" + LF + // + " myClass o;" + LF + // + " myClass[] oa;" + LF + // + " {" + LF + // + " return 1;" + LF + // + " }" + LF + // + " void main() { }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type myClass: class (5 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + expectSymTab(" Local Variable 2: int[] ia"); + expectSymTab(" Local Variable 3: class (5 fields) o"); + expectSymTab(" Local Variable 4: class (5 fields)[] oa"); + expectSymTab(" Constant: int fi = 20"); + expectSymTab(" Constant: char fc = 'x'"); + expectSymTab(" Global Variable 0: int gi"); + expectSymTab(" Global Variable 1: char gc"); + expectSymTab(" Global Variable 2: int[] gia"); + expectSymTab(" Global Variable 3: class (5 fields) go"); + expectSymTab(" Global Variable 4: class (5 fields)[] goa"); + expectSymTab(" Method: int method (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + expectSymTab(" Local Variable 2: char[] ca"); + expectSymTab(" Local Variable 3: class (5 fields) o"); + expectSymTab(" Local Variable 4: class (5 fields)[] oa"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void types() { + initCode("program Test" + LF + // + " class C { int i1; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " int i1;" + LF + // + " int i2;" + LF + // + " C o1;" + LF + // + " C o2;" + LF + // + " C[] oa1;" + LF + // + " C[] oa2;" + LF + // + " {" + LF + // + " i1 = 0;" + LF + // + " i1 = i2;" + LF + // + " o1 = null;" + LF + // + " o1 = o2;" + LF + // + " oa1 = null;" + LF + // + " oa1 = oa2;" + LF + // + " oa1[i1] = o1;" + LF + // + " oa1[i1].i1 = i2;" + LF + // + " if (0 > i1) { }" + LF + // + " if (null != o1) { }" + LF + // + " if (null == oa1) { }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Method: void main (6 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: class (1 fields) o1"); + expectSymTab(" Local Variable 3: class (1 fields) o2"); + expectSymTab(" Local Variable 4: class (1 fields)[] oa1"); + expectSymTab(" Local Variable 5: class (1 fields)[] oa2"); + + parseVerifyVisualize(); + } + + @Test + public void exprLocal() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " int i1, i2, i3, i4, i5;" + LF + // + " {" + LF + // + " i1 = i2;" + LF + // + " i1 += i2 + 3 * i3 - i4 % i5;" + LF + // + " i1++;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Method: void main (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + expectSymTab(" Local Variable 4: int i5"); + + parseVerifyVisualize(); + } + + @Test + public void exprGlobal() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + " int i1, i2, i3, i4, i5;" + LF + // + "{" + LF + // + " void main()" + LF + // + " {" + LF + // + " i1 = i2;" + LF + // + " i1 -= i2 + 3 * i3 - i4 % i5;" + LF + // + " i1--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Global Variable 0: int i1"); + expectSymTab(" Global Variable 1: int i2"); + expectSymTab(" Global Variable 2: int i3"); + expectSymTab(" Global Variable 3: int i4"); + expectSymTab(" Global Variable 4: int i5"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void exprField() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " C i1, i2, i3, i4, i5;" + LF + // + " {" + LF + // + " i1.i = i2.i;" + LF + // + " i1.i *= i2.i + 3 * i3.i - i4.i % i5.i;" + LF + // + " i1.i++;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Method: void main (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: class (1 fields) i1"); + expectSymTab(" Local Variable 1: class (1 fields) i2"); + expectSymTab(" Local Variable 2: class (1 fields) i3"); + expectSymTab(" Local Variable 3: class (1 fields) i4"); + expectSymTab(" Local Variable 4: class (1 fields) i5"); + + parseVerifyVisualize(); + } + + @Test + public void exprArray() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " C[] i1, i2, i3, i4, i5;" + LF + // + " {" + LF + // + " i1[1].i = i2[2].i;" + LF + // + " i1[1].i %= i2[2].i + 3 * i3[3].i - i4[4].i % i5[5].i;" + LF + // + " i1[1].i--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Method: void main (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: class (1 fields)[] i1"); + expectSymTab(" Local Variable 1: class (1 fields)[] i2"); + expectSymTab(" Local Variable 2: class (1 fields)[] i3"); + expectSymTab(" Local Variable 3: class (1 fields)[] i4"); + expectSymTab(" Local Variable 4: class (1 fields)[] i5"); + + parseVerifyVisualize(); + } + + @Test + public void minus() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + "{" + LF + // + " void main()" + LF + // + " int i1, i2, i3, i4, i5;" + LF + // + " {" + LF + // + " i1 = -i2;" + LF + // + " i1 = -i2 + (-3) * (-(-i3)) - (-i4) % (-i5);" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Method: void main (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + expectSymTab(" Local Variable 4: int i5"); + + parseVerifyVisualize(); + } + + @Test + public void caseSensitiv() { + initCode("program Test" + LF + // + " int a, A, b;" + LF + // + "{" + LF + // + " void main()" + LF + // + " int a, b, B;" + LF + // + " {" + LF + // + " a = A;" + LF + // + " b = B;" + LF + // + " B = a;" + LF + // + " A = b;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Global Variable 0: int a"); + expectSymTab(" Global Variable 1: int A"); + expectSymTab(" Global Variable 2: int b"); + expectSymTab(" Method: void main (3 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int a"); + expectSymTab(" Local Variable 1: int b"); + expectSymTab(" Local Variable 2: int B"); + + parseVerifyVisualize(); + } + + @Test + public void constTest() { + initCode("program Test" + LF + // + " final int fi = 20;" + LF + // + " final char fc = 'a';" + LF + // + "{" + LF + // + " void main()" + LF + // + " int i;" + LF + // + " char c;" + LF + // + " {" + LF + // + " i = 4;" + LF + // + " i = -1;" + LF + // + " i = 10;" + LF + // + " i = fi;" + LF + // + " i = -fi;" + LF + // + " i = fi + 5;" + LF + // + " i = 5 * (-fi);" + LF + // + " c = fc;" + LF + // + " c = 'a';" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Constant: int fi = 20"); + expectSymTab(" Constant: char fc = 'a'"); + expectSymTab(" Method: void main (2 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + + parseVerifyVisualize(); + } + + @Test + public void newTest() { + initCode("program Test" + LF + // 1 + " class C { int i; }" + LF + // 2 + "{" + LF + // 3 + " void main()" + LF + // 4 + " int i1, i2;" + LF + // 5 + " C obj;" + LF + // 6 + " char[] ca;" + LF + // 7 + " C[] oa;" + LF + // 8 + " {" + LF + // 9 + " obj = new C;" + LF + // 10 + " ca = new char[5];" + LF + // 11 + " oa = new C[i1 * obj.i - oa[obj.i - 5].i];" + LF + // 12 + " }" + LF + // 13 + "}" // 14 + ); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (1 fields)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Method: void main (5 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: class (1 fields) obj"); + expectSymTab(" Local Variable 3: char[] ca"); + expectSymTab(" Local Variable 4: class (1 fields)[] oa"); + + parseVerifyVisualize(); + } + + @Test + public void ifGt() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " if (i1 > i2) i1++; else i1--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void ifAnd() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " if (i1 > i2 && i3 < i4) i1++; else i1--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void ifOr() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " if (i1 > i2 || i3 < i4) i1++; else i1--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void ifAndOr() { + initCode("program Test {" + LF // + + " void main() int i1, i2, i3, i4; {" + LF // + + " if (i1 > i2 && i3 < i4 || i1 == i2 && i3 != i4) i1++; else i1--;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void ifNested() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " if (i1 > i2) {" + LF + // + " if (i3 < i4) i1++;" + LF + // + " } else {" + LF + // + " if (i3 > i4) i1--;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void forGt() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " while (i1 > i2) {" + LF + // + " i1++;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void forAnd() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " while (i1 > i2 && i3 > i4) {" + LF + // + " i1++;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void forOr() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " while (i1 > i2 || i3 > i4) {" + LF + // + " i1++;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void forNested() { + initCode("program Test {" + LF + // + " void main() int i1, i2, i3, i4; {" + LF + // + " if (i1 > i2) {" + LF + // + " i1++;" + LF + // + " if (i3 < i4) {" + LF + // + " while (i1 == i2) ;" + LF + // + " }" + LF + // + " while (i3 != i4) ;" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (4 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i1"); + expectSymTab(" Local Variable 1: int i2"); + expectSymTab(" Local Variable 2: int i3"); + expectSymTab(" Local Variable 3: int i4"); + + parseVerifyVisualize(); + } + + @Test + public void forBreak() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " while (i < 10) {" + LF + // + " i++;" + // + " break;" + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (1 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + + parseVerifyVisualize(); + } + + @Test + public void predefMeth() { + initCode("program Test {" + LF + // + " void main() int i; char c; int[] ia; {" + LF + // + " i = ord(c);" + LF + // + " c = chr(i);" + LF + // + " i = len(ia);" + LF + // + " print(i);" + LF + // + " print(c, 4);" + LF + // + " read(i);" + LF + // + " read(c);" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (3 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + expectSymTab(" Local Variable 2: int[] ia"); + + parseVerifyVisualize(); + } + + @Test + public void predefFunAsMeth() { + initCode("program Test {" + LF + // + " void main() int i; char c; int[] ia; {" + LF + // + " ord(c);" + LF + // + " chr(i);" + LF + // + " len(ia);" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (3 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + expectSymTab(" Local Variable 2: int[] ia"); + + parseVerifyVisualize(); + } + + @Test + public void callVoid() { + initCode("program Test {" + LF + // + " void method() {" + LF + // + " return;" + LF + // + " }" + LF + // + " void main() {" + LF + // + " method();" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void method (0 locals, 0 parameters)"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void callInt() { + initCode("program Test {" + LF + // + " int method() {" + LF + // + " return 1;" + LF + // + " }" + LF + // + " void main() {" + LF + // + " print(method());" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: int method (0 locals, 0 parameters)"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void callParam() { + initCode("program Test {" + LF + // + " int method(int i, char c, int[] ia) {" + LF + // + " return i + ia[ord(c)];" + LF + // + " }" + LF + // + " void main() int i; char c; {" + LF + // + " print(method(i, c, new int[i]));" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: int method (3 locals, 3 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + expectSymTab(" Local Variable 2: int[] ia"); + expectSymTab(" Method: void main (2 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab(" Local Variable 1: char c"); + + parseVerifyVisualize(); + } + + @Test + public void callFuncAsProc() { + initCode("program Test {" + LF + // + " int method() {" + LF + // + " return 1;" + LF + // + " }" + LF + // + " void main() {" + LF + // + " method();" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: int method (0 locals, 0 parameters)"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void multReturn() { + initCode("program Test {" + LF + // + " int method(int p) {" + LF + // + " if (p > 0) return 1;" + LF + // + " if (p < 0) return -1;" + LF + // + " return 0;" + LF + // + " }" + LF + // + " void main() {" + LF + // + " print(method(5));" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: int method (1 locals, 1 parameters)"); + expectSymTab(" Local Variable 0: int p"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void manyLocals() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 127; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test {" + LF + // + " void main()" + LF + // + " int " + names + ";" + LF + // + " {" + LF + // + " i0 = i126;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (127 locals, 0 parameters)"); + for (int i = 0; i < 127; i++) { + expectSymTab(" Local Variable " + i + ": int i" + i); + } + + parseVerifyVisualize(); + } + + @Test + public void manyGlobals() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 300; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test" + LF + // + " int " + names + ";" + LF + // + "{" + LF + // + " void main() {" + LF + // + " i0 = i299;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + for (int i = 0; i < 300; i++) { + expectSymTab(" Global Variable " + i + ": int i" + i); + } + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void manyFields() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 300; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test" + LF + // + " class C {" + LF + // + " int " + names + ";" + LF + // + " }" + LF + // + "{" + LF + // + " void main()" + LF + // + " C c;" + LF + // + " {" + LF + // + " c.i0 = c.i299;" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Type C: class (300 fields)"); + for (int i = 0; i < 300; i++) { + expectSymTab(" Local Variable " + i + ": int i" + i); + } + expectSymTab(" Method: void main (1 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: class (300 fields) c"); + + parseVerifyVisualize(); + } + + @Test + public void scriptExample() { + initCode("program P" + LF + // + " final int size = 10;" + LF + // + "" + LF + // + " class Table {" + LF + // + " int[] pos;" + LF + // + " int[] neg;" + LF + // + " }" + LF + // + "" + LF + // + " Table val;" + LF + // + "" + LF + // + "{" + LF + // + " void main()" + LF + // + " int x, i;" + LF + // + " { /*---------- Initialize val */" + LF + // + " val = new Table;" + LF + // + " val.pos = new int[size];" + LF + // + " val.neg = new int[size];" + LF + // + " i = 0;" + LF + // + " while (i < size) {" + LF + // + " val.pos[i] = 0; val.neg[i] = 0; i++;" + LF + // + " }" + LF + // + " /*---------- Read values */" + LF + // + " read(x);" + LF + // + " while (x != 0) {" + LF + // + " if (0 <= x && x < size) {" + LF + // + " val.pos[x]++;" + LF + // + " } else if (-size < x && x < 0) {" + LF + // + " val.neg[-x]++;" + LF + // + " }" + LF + // + " read(x);" + LF + // + " }" + LF + // + " }" + LF + // + "}"); + + expectSymTabUniverse(); + expectSymTab("Program P:"); + expectSymTab(" Constant: int size = 10"); + expectSymTab(" Type Table: class (2 fields)"); + expectSymTab(" Local Variable 0: int[] pos"); + expectSymTab(" Local Variable 1: int[] neg"); + expectSymTab(" Global Variable 0: class (2 fields) val"); + expectSymTab(" Method: void main (2 locals, 0 parameters)"); + expectSymTab(" Local Variable 0: int x"); + expectSymTab(" Local Variable 1: int i"); + + parseVerifyVisualize(); + } + + @Test + public void tooManyLocals() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 127; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test {" + LF + // + " void main()" + LF + // + " int " + names + ";" + LF + // + " int error;" + LF + // + " {" + LF + // + " }" + LF + // + "}"); + expectError(5, 3, TOO_MANY_LOCALS); + parseVerifyVisualize(); + } + + @Test + public void tooManyLocals2() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 126; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test {" + LF + // + " void foo(int x)" + LF + // + " int " + names + ";" + LF + // + " int error;" + LF + // + " {" + LF + // + " }" + LF + // + " void main()" + LF + // + " {}" + LF + // + "}"); + expectError(5, 3, TOO_MANY_LOCALS); + parseVerifyVisualize(); + } + + @Test + public void tooManyGlobals() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 32767; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test" + LF + // + " int " + names + ";" + LF + // + " int error;" + LF + // + "{" + LF + // + " void main() { }" + LF + // + "}"); + expectError(4, 1, TOO_MANY_GLOBALS); + + parseVerifyVisualize(); + } + + @Test + public void tooManyFields() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 32767; i++) { + if (i > 0) { + sb.append(","); + } + sb.append("i"); + sb.append(i); + } + String names = sb.toString(); + + initCode("program Test" + LF + // + " class C {" + LF + // + " int " + names + ";" + LF + // + " int error;" + LF + // + " }" + LF + // + "{" + LF + // + " void main() { }" + LF + // + "}"); + expectError(5, 3, TOO_MANY_FIELDS); + + parseVerifyVisualize(); + } + + @Test + public void doubleDeclVar() { + initCode("program Test " + LF + // + " int x;" + LF + // + " int x;" + LF + // + "{" + LF + // + " void main() { }" + LF + // + "}"); + expectError(3, 8, DUPLICATE_NAME_IN_SCOPE, "x"); + parseVerifyVisualize(); + } + + @Test + public void doubleDeclMeth() { + initCode("program Test " + LF + // + " int x;" + LF + // + "{" + LF + // + " void x() { }" + LF + // + " void main() { }" + LF + // + "}"); + expectError(4, 9, DUPLICATE_NAME_IN_SCOPE, "x"); + parseVerifyVisualize(); + } + + @Test + public void doubleDeclLocal() { + initCode("program Test {" + LF + // + " void method(int x)" + LF + // + " int x;" + LF + // + " { }" + LF + // + " void main() { }" + LF + // + "}"); + expectError(3, 10, DUPLICATE_NAME_IN_SCOPE, "x"); + parseVerifyVisualize(); + } + + @Test + public void undefType() { + initCode("program Test" + LF + // + " type x;" + LF + // + "{" + LF + // + " void main() { }" + LF + // + "}"); + expectError(2, 8, NAME_NOT_FOUND, "type"); + parseVerifyVisualize(); + } + + @Test + public void undefVar() { + initCode("program Test { void main() { a++; } }"); + + expectError(1, 31, NAME_NOT_FOUND, "a"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void undefMeth() { + initCode("program Test { void main() { foo(); } }"); + + expectError(1, 33, NAME_NOT_FOUND, "foo"); + + expectSymTabUniverse(); + expectSymTab("Program Test:"); + expectSymTab(" Method: void main (0 locals, 0 parameters)"); + + parseVerifyVisualize(); + } + + @Test + public void noType() { + initCode("program Test" + LF + // + " int i;" + LF + // + " i s;" + LF + // + "{ void main() { } }"); + expectError(3, 5, TYPE_EXPECTED); + parseVerifyVisualize(); + } + + @Test + public void wrongConstTypeInt() { + initCode("program Test" + LF + // + " final int i = 'a';" + LF + // + "{ void main() { } }"); + expectError(2, 17, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void wrongConstTypeChar() { + initCode("program Test" + LF + // + " final char ch = 32;" + LF + // + "{ void main() { } }"); + expectError(2, 19, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void wrongConstType() { + initCode("program Test" + LF + // + " class C { int i; }" + LF + // + " final C c = 32;" + LF + // + "{ void main() { } }"); + expectError(3, 15, INCOMPATIBLE_TYPES); + parseVerifyVisualize(); + } + + @Test + public void mainNotVoid() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " int main() { }" + LF + // 3 + "}" + LF // 6 + ); + expectError(3, 14, MAIN_NOT_VOID); + parseVerifyVisualize(); + } + + @Test + public void mainNoParams() { + initCode("program Test" + LF + // 1 + "{" + LF + // 2 + " void main(int x) { }" + LF + // 3 + "}" + LF // 6 + ); + expectError(3, 20, MAIN_WITH_PARAMS); + parseVerifyVisualize(); + } + + @Test + public void noField() { + initCode("program Test" + LF + // + " class C { }" + LF + // + "{" + LF + // + " void main() C obj; {" + LF + // + " obj.field++;" + LF + // + " }" + LF + // + "}"); + expectError(5, 14, FIELD_NOT_FOUND, "field"); + parseVerifyVisualize(); + } + + @Test + public void noClassType() { + initCode("program Test {" + LF + // + " void main() int i; {" + LF + // + " i = new int;" + LF + // + " }" + LF + // + "}"); + expectError(3, 16, CLASS_TYPE_EXPECTED); + parseVerifyVisualize(); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/support/BaseCompilerTestCase.java b/MicroJava Tests/tests/ssw/mj/test/support/BaseCompilerTestCase.java new file mode 100644 index 0000000..fadfba0 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/support/BaseCompilerTestCase.java @@ -0,0 +1,401 @@ +package ssw.mj.test.support; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Timeout; +import ssw.mj.Errors; +import ssw.mj.Interpreter; +import ssw.mj.Visualizer; +import ssw.mj.codegen.Decoder; +import ssw.mj.impl.Parser; +import ssw.mj.impl.Scanner; +import ssw.mj.scanner.Token; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.StringReader; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.StringTokenizer; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Base class for test cases with utility methods used by all tests. + */ + +@Timeout(value = Configuration.TIMEOUT, threadMode = Timeout.ThreadMode.SEPARATE_THREAD) +public abstract class BaseCompilerTestCase { + + public static final String CR = "\r"; + public static final String LF = "\n"; + private List expectedErrors; + private List expectedTokens; + private List expectedTokensFull; + private List expectedSymTab; + private List expectedRuntimeErrors; + private String source; + private Scanner scanner; + protected Parser parser; + private String callingClassAndMethod; + private final List runInputs = new ArrayList<>(); + private final List expectedOutputs = new ArrayList<>(); + + @BeforeEach + public void setUp() { + // initialize expected compiler output + expectedErrors = new ArrayList<>(); + expectedTokens = new ArrayList<>(); + expectedTokensFull = new ArrayList<>(); + expectedSymTab = new ArrayList<>(); + expectedRuntimeErrors = new ArrayList<>(); + + if (Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES) { + // print header for console output + System.out.println("--------------------------------------------------"); + } + } + + protected void initCode(String code) { + initScannerCode(code); + parser = new Parser(scanner); + } + + protected void initFile(String filename) { + initScannerFile(filename); + parser = new Parser(scanner); + } + + protected void initScannerCode(String code) { + source = code; + scanner = new Scanner(new StringReader(code)); + } + + protected void initScannerFile(String filename) { + try { + ClassLoader classLoader = getClass().getClassLoader(); + URL resource = classLoader.getResource(filename); + if (resource == null) { + throw new RuntimeException("resource %s not found".formatted(filename)); + } + String urlAsStr = resource.getFile(); + // replaces %20 Urlencoding with " " (blank space), as e.g. Linux cannot handle url paths + String path = URLDecoder.decode(urlAsStr, StandardCharsets.UTF_8); + File file = new File(path); + scanner = new Scanner(new FileReader(file)); + } catch (FileNotFoundException e) { + throw new RuntimeException(e.getMessage()); + } + } + + private List splitString(String s) { + StringTokenizer st = new StringTokenizer(s, "\n"); + List result = new ArrayList<>(); + while (st.hasMoreTokens()) { + result.add(st.nextToken()); + } + return result; + } + + private void print(String title, List expected, List actual) { + if (expected.isEmpty() && actual.isEmpty()) { + return; + } + System.out.format("%s - %s\n", callingClassAndMethod, title); + if (Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES || !expected.equals(actual)) { + System.out.format(" %-60s %s\n", "expected", "actual"); + int lines = Math.max(expected.size(), actual.size()); + for (int i = 0; i < lines; i++) { + String expectedLine = (i < expected.size() ? expected.get(i) : ""); + String actualLine = (i < actual.size() ? actual.get(i) : ""); + System.out.format("%s %-60s %s\n", (expectedLine.equals(actualLine) ? " " : "x"), expectedLine, + actualLine); + } + } else { + if (expected.equals(actual)) { + System.out.println(" correct (exact comparison hidden, enable via Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES)"); + } + } + } + + private void addRun(String input, String output, String error) { + runInputs.add(input); + expectedOutputs.add(output); + expectedRuntimeErrors.add(error); + } + + protected void addExpectedRun(String output) { + addExpectedRun("", output); + } + + protected void addExpectedRun(String input, String output) { + addRun(input, output, ""); + } + + protected void addFailingRun(String error) { + addFailingRun("", error); + } + + protected void addFailingRun(String input, String error) { + addRun(input, "", error); + } + + /** + * Scans the given code and checks the scanned tokens against the expected ones. + * Also checks that expected errors occur. + * Finally, the method creates a visualization of the scanned tokens if the test was run + * with @link ssw.mj.TracingClassLoader as system classloader. + */ + protected void scanVerifyVisualize() { + callingClassAndMethod = getCallingClassAndMethod(1); + + List actualTokens = new ArrayList<>(); + + // scan only the expected number of tokens to prevent endless loops + for (int i = 0; i < getExpectedTokens().size(); i++) { + actualTokens.add(scanner.next()); + } + + List actualTokenStrings = actualTokens.stream().map(Token::toString).toList(); + + Visualizer.createScannerVisualization(source, actualTokens, getExpectedTokensFull(), false); + + printErrors(); + printTokens(actualTokenStrings); + + verifyErrors(); + verifyTokens(actualTokenStrings); + } + + /** + * Parses the given code and checks it for expected errors, matching sym tab and matching byte code. + * Then it executed the interpreter for all given inputs. + * Finally, the method creates a visualization of the parse tree if the test was run + * with @link ssw.mj.TracingClassLoader as system classloader. + */ + protected void parseVerifyVisualize() { + callingClassAndMethod = getCallingClassAndMethod(1); + + try { + parser.parse(); + assertEquals(Token.Kind.eof, scanner.next().kind, "Complete input should be scanned"); + } catch (Errors.PanicMode error) { + // Ignore, nothing to do + } + + printErrors(); + printSymTab(); + + verifyErrors(); + verifySymTab(); + + if (ByteCodeTestSupport.GENERATE_REFERENCE_BYTE_CODE && expectedErrors.isEmpty()) { + ByteCodeTestSupport.generateReferenceByteCode(callingClassAndMethod, parser); + } else { + printAndVerifyByteCode(callingClassAndMethod); + } + + for (int i = 0; i < runInputs.size(); i++) { + run(i); + } + + Visualizer.createParserVisualization(source, false); + } + + private static String getCallingClassAndMethod(int up) { + // [0] getStackTrace -> [1] getCallingMethodName -> [2] caller of getCallingMethodName -> [3] ... + StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace(); + StackTraceElement e = stacktrace[2 + up]; + String fullyQualifiedClassName = e.getClassName(); + String className = fullyQualifiedClassName.substring(Math.max(fullyQualifiedClassName.lastIndexOf(".") + 1, 0)); + return className + "." + e.getMethodName() + "()"; + } + + private void run(int i) { + Interpreter.BufferIO io = new Interpreter.BufferIO(runInputs.get(i)); + Interpreter interpreter = new Interpreter( + parser.code.buf, + parser.code.mainpc, + parser.code.dataSize, + io, + Configuration.PRINT_INTERPRETER_DEBUG_OUTPUT); + try { + interpreter.run(); + } catch (IllegalStateException e) { + verifyRuntimeError(i, e); + } + String output = io.getOutput(); + verifyOutput(i, output); + } + + + private void printErrors() { + print("Errors", expectedErrors, getActualErrors()); + } + + private void printTokens(List actualTokens) { + print("Tokens", getExpectedTokens(), actualTokens); + } + + private void printSymTab() { + if (!expectedSymTab.isEmpty()) { + print("Symbol Table", getExpectedSymTab(), getActualSymTab()); + } + } + + private void verifyErrors() { + assertEquals(expectedErrors, getActualErrors(), "Errors"); + } + + private void verifyTokens(List actualTokens) { + assertEquals(getExpectedTokens(), actualTokens, "Tokens"); + assertTrue(scanner.next().toString().contains("end of file"), "Complete Input Scanned"); + } + + private void verifySymTab() { + if (!expectedSymTab.isEmpty()) { + assertEquals(getExpectedSymTab(), getActualSymTab(), "Symbol Table"); + } + } + + private void printAndVerifyByteCode(String callingClassAndMethod) { + if (ByteCodeTestSupport.BYTE_CODES.containsKey(callingClassAndMethod)) { + List possibleByteCodes = ByteCodeTestSupport.BYTE_CODES.get(callingClassAndMethod); + if (possibleByteCodes.size() == 1) { + List expected = getExpectedByteCodeLines(possibleByteCodes.get(0)); + print("Bytecode", expected, getActualByteCodeLines()); + // Verify that the bytecode is correct + assertEquals(expected, getActualByteCodeLines(), "Byte Code"); + } else { + int matchIdx = -1; + for (int i = 0; i < possibleByteCodes.size(); i++) { + List expected = getExpectedByteCodeLines(possibleByteCodes.get(i)); + if (expected.equals(getActualByteCodeLines())) { + matchIdx = i; + break; + } + } + if (matchIdx < 0) { + // No bytecode matched + // print all + for (int i = 0; i < possibleByteCodes.size(); i++) { + List expected = getExpectedByteCodeLines(possibleByteCodes.get(i)); + print("Possible Bytecode %d".formatted(i + 1), expected, getActualByteCodeLines()); + } + // fail assert on first + assertEquals(getExpectedByteCodeLines(possibleByteCodes.get(0)), getActualByteCodeLines(), "Byte Code"); + } else { + // bytecode at idx matchIdx correctly generated + // print working bytecode + print("Bytecode", getExpectedByteCodeLines(possibleByteCodes.get(matchIdx)), getActualByteCodeLines()); + // assert not really necessary since we already know we matched successfully + assertEquals(getExpectedByteCodeLines(possibleByteCodes.get(matchIdx)), getActualByteCodeLines(), "Byte Code"); + } + } + } + } + + private void verifyOutput(int runIdx, String actualOutput) { + assertEquals(expectedOutputs.get(runIdx), actualOutput, "Unexpected result when input is \"" + runInputs.get(runIdx) + "\": "); + } + + private void verifyRuntimeError(int runIdx, IllegalStateException e) { + assertEquals(expectedRuntimeErrors.get(runIdx), e.getMessage(), "Unexpected runtime error message when input is \"" + runInputs.get(runIdx) + "\": "); + } + + private List getExpectedByteCodeLines(String bytecode) { + return Arrays.stream(bytecode.split("\n")).toList(); + } + + private List getActualByteCodeLines() { + return Arrays.stream(new Decoder().decode(parser.code).split("\n")).toList(); + } + + private List getActualErrors() { + return splitString(scanner.errors.dump()); + } + + private List getExpectedTokens() { + return expectedTokens; + } + + private List getExpectedTokensFull() { + return expectedTokensFull; + } + + private List getExpectedSymTab() { + return expectedSymTab; + } + + private List getActualSymTab() { + return splitString(SymTabDumper.dump(parser.tab)); + } + + protected void expectError(int line, int col, Errors.Message msg, Object... msgParams) { + expectedErrors.add("-- line " + line + " col " + col + ": " + msg.format(msgParams)); + } + + protected void expectToken(Token.Kind kind, int line, int col) { + expectedTokens.add("line " + line + ", col " + col + ", kind " + kind); + expectedTokensFull.add(new Token(kind, line, col)); + } + + protected void expectToken(Token.Kind kind, int line, int col, String val) { + expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + val); + + Token token = new Token(kind, line, col); + token.val = val; + expectedTokensFull.add(token); + } + + protected void expectToken(Token.Kind kind, int line, int col, int val) { + expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + val + ", numVal " + val); + + Token token = new Token(kind, line, col); + token.val = String.valueOf(val); + token.numVal = val; + expectedTokensFull.add(token); + } + + protected void expectToken(Token.Kind kind, int line, int col, char ch) { + expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + ch + ", numVal " + (int) ch); + + Token token = new Token(kind, line, col); + token.val = String.valueOf(ch); + token.numVal = ch; + expectedTokensFull.add(token); + } + + protected void expectInvalidToken(Token.Kind kind, int line, int col) { + expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val null, numVal 0"); + + Token token = new Token(kind, line, col); + token.val = null; + token.numVal = 0; + expectedTokensFull.add(token); + } + + protected void expectSymTab(String line) { + expectedSymTab.add(line); + } + + protected void expectSymTabUniverse() { + // first part of the symbol table (universe) that is equal for all + // programs + expectSymTab("-- begin scope (0 variables) --"); + expectSymTab("Type int: int"); + expectSymTab("Type char: char"); + expectSymTab("Constant: class (0 fields) null = 0"); + expectSymTab("Method: char chr (1 locals, 1 parameters)"); + expectSymTab(" Local Variable 0: int i"); + expectSymTab("Method: int ord (1 locals, 1 parameters)"); + expectSymTab(" Local Variable 0: char ch"); + expectSymTab("Method: int len (1 locals, 1 parameters)"); + expectSymTab(" Local Variable 0: void[] arr"); + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/support/ByteCodeTestSupport.java b/MicroJava Tests/tests/ssw/mj/test/support/ByteCodeTestSupport.java new file mode 100644 index 0000000..d7f12ec --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/support/ByteCodeTestSupport.java @@ -0,0 +1,103 @@ +package ssw.mj.test.support; + +import ssw.mj.codegen.Decoder; +import ssw.mj.impl.Parser; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Stream; + +public class ByteCodeTestSupport { + /** + * This flag is used by the lecturers to generate the reference solutions for + * the bytecodes generated during code generation. + * Students should not change this flag, it should stay false for the whole course. + */ + public static final boolean GENERATE_REFERENCE_BYTE_CODE = false; + + // For each test, 0 to n correct byte codes can be added to bytecodes.txt + // If one of these codes is generated by the Parser, the test does not fail. + // This way, we can provide multiple correct solutions for the same test case. + // + // The keys of this map are in the format "TestClass.TestMethodName()" + public static final HashMap> BYTE_CODES = new HashMap<>(); + + static { + File bytecodesFile = getBytecodesFile(); + if (bytecodesFile.exists()) { + String[] lineArr; + try (Stream lines = Files.lines(bytecodesFile.toPath())) { + lineArr = lines.toArray(String[]::new); + } catch (IOException e) { + throw new RuntimeException(e); + } + String currentlyReadClassAndMethod = null; + StringBuilder currentReadBytecode = null; + for (String line : lineArr) { + if (line.isBlank()) { + continue; + } + if (line.startsWith("#")) { + if (currentlyReadClassAndMethod != null) { + if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) { + BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>()); + } + BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString()); + } + currentlyReadClassAndMethod = line.substring(1); + currentReadBytecode = new StringBuilder(); + } else { + currentReadBytecode.append(line).append("\n"); + } + } + if (currentlyReadClassAndMethod != null) { + if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) { + BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>()); + } + BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString()); + } + } + } + + public static File getBytecodesFile() { + String filename = "bytecodes.txt"; + ClassLoader classLoader = BaseCompilerTestCase.class.getClassLoader(); + URL resource = classLoader.getResource(filename); + if (resource == null) { + throw new RuntimeException("resource %s not found".formatted(filename)); + } + String urlAsStr = resource.getFile(); + // replaces %20 Urlencoding with " " (blank space), as e.g. Linux cannot handle url paths + String path = URLDecoder.decode(urlAsStr, StandardCharsets.UTF_8); + return new File(path); + } + + + public static void generateReferenceByteCode(String classAndMethod, Parser parser) { + // Generate and store bytecode for correct test programs + + // Output is in the form: + // #TestClass.TestMethodName() + // ... output from Decoder.decode() ... + File bytecodesFile = ByteCodeTestSupport.getBytecodesFile(); + try (BufferedWriter bw = Files.newBufferedWriter(bytecodesFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + String bytecode = new Decoder().decode(parser.code); + bw.write("#"); + bw.write(classAndMethod); + bw.write("\n"); + bw.write(bytecode); + bw.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/MicroJava Tests/tests/ssw/mj/test/support/Configuration.java b/MicroJava Tests/tests/ssw/mj/test/support/Configuration.java new file mode 100644 index 0000000..7d3a327 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/support/Configuration.java @@ -0,0 +1,27 @@ +package ssw.mj.test.support; + +public class Configuration { + /** + * set to true to print expected and actual values of all testcases, + * not only failing ones (prints expected errors, tokens, symbol table, code) + */ + public static final boolean ALSO_PRINT_SUCCESSFUL_TESTCASES = Boolean.getBoolean("microjava.testcaseOutput"); + + /** + * Set to true to print debug information of the interpreter. Equal to + * "-debug" on the command line.
+ * Remark:
+ * This is a lot of output, some test cases might time out, e.g. + * CodeGenerationTest.fib + */ + public static final boolean PRINT_INTERPRETER_DEBUG_OUTPUT = Boolean.getBoolean("microjava.interpreterOutput"); + + /** + * Determines the timeout after which a test case should fail automatically. + * Default: 10 seconds. The default should work for all test cases + * on most machines.
+ * Attention: For most computers it is likely that there is an + * endless loop in the MicroJava compiler if a test fails for a timeout. + */ + public static final long TIMEOUT = 10; +} diff --git a/MicroJava Tests/tests/ssw/mj/test/support/SymTabDumper.java b/MicroJava Tests/tests/ssw/mj/test/support/SymTabDumper.java new file mode 100644 index 0000000..1031713 --- /dev/null +++ b/MicroJava Tests/tests/ssw/mj/test/support/SymTabDumper.java @@ -0,0 +1,119 @@ +package ssw.mj.test.support; + +import ssw.mj.impl.Tab; +import ssw.mj.symtab.Obj; +import ssw.mj.symtab.Scope; +import ssw.mj.symtab.Struct; + +import java.util.Collection; + +public class SymTabDumper { + public static String dump(Tab tab) { + StringBuilder sb = new StringBuilder(); + if (tab.curScope != null) { + dump(tab.curScope, sb); + } + return sb.toString(); + } + + private static void dump(Scope scope, StringBuilder sb) { + sb.append("-- begin scope (").append(scope.nVars()).append(" variables) --\n"); + if (!scope.locals().isEmpty()) { + dump(scope.locals().values(), sb, ""); + } + if (scope.outer() != null) { + sb.append("\n"); + dump(scope.outer(), sb); + } + } + + private static void dump(Collection objects, StringBuilder sb, String indent) { + for (Obj obj : objects) { + dump(obj, sb, indent); + } + } + + private static void dump(Obj obj, StringBuilder sb, String indent) { + sb.append(indent); + + switch (obj.kind) { + case Con -> dumpCon(obj, sb, indent); + case Var -> dumpVar(obj, sb, indent); + case Type -> dumpType(obj, sb, indent); + case Meth -> dumpMethod(obj, sb, indent); + case Prog -> dumpProgram(obj, sb); + } + + if (obj.locals != null) { + sb.append("\n"); + dump(obj.locals.values(), sb, indent + " "); + } + sb.append("\n"); + } + + private static void dumpCon(Obj obj, StringBuilder sb, String indent) { + sb.append("Constant: "); + if (obj.type != null) { + dump(obj.type, sb, indent, false); + } + sb.append(" ").append(obj.name).append(" = "); + if (obj.type == Tab.charType) { + sb.append("'").append((char) obj.val).append("'"); + } else { + sb.append(obj.val); + } + } + + private static void dumpVar(Obj obj, StringBuilder sb, String indent) { + if (obj.level == 0) { + sb.append("Global Variable "); + } else { + sb.append("Local Variable "); + } + sb.append(obj.adr).append(": "); + if (obj.type != null) { + dump(obj.type, sb, indent, false); + } + sb.append(" ").append(obj.name); + } + + private static void dumpType(Obj type, StringBuilder sb, String indent) { + sb.append("Type ").append(type.name).append(": "); + if (type.type != null) { + dump(type.type, sb, indent + " ", true); + } + } + + private static void dumpMethod(Obj meth, StringBuilder sb, String indent) { + sb.append("Method: "); + if (meth.type != null) { + dump(meth.type, sb, indent, false); + } + sb.append(" ").append(meth.name).append(" (").append(meth.locals.size()).append(" locals, ").append(meth.nPars).append(" parameters").append(")"); + } + + private static void dumpProgram(Obj obj, StringBuilder sb) { + sb.append("Program ").append(obj.name).append(":"); + } + + private static void dump(Struct struct, StringBuilder sb, String indent, boolean dumpFields) { + switch (struct.kind) { + case None -> sb.append("void"); + case Int -> sb.append("int"); + case Char -> sb.append("char"); + case Arr -> { + if (struct.elemType != null) { + dump(struct.elemType, sb, indent, dumpFields); + } + sb.append("[]"); + } + case Class -> { + sb.append("class (").append(struct.nrFields()).append(" fields)"); + if (dumpFields && struct.fields != null) { + sb.append("\n"); + dump(struct.fields.values(), sb, indent); + } + } + } + } +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..344deab --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1760524057, + "narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..22ffdc1 --- /dev/null +++ b/flake.nix @@ -0,0 +1,28 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { + nixpkgs, + ... + }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { + inherit system; + config = { + allowUnfree = true; + }; + }; + in + { + devShells."${system}".default = pkgs.mkShell { + packages = [ + pkgs.jetbrains.idea-ultimate + pkgs.jdk21 + ]; + }; + }; +} diff --git a/lib/apiguardian-api-1.1.2.jar b/lib/apiguardian-api-1.1.2.jar new file mode 100644 index 0000000..2b678e1 Binary files /dev/null and b/lib/apiguardian-api-1.1.2.jar differ diff --git a/lib/gson-2.10.1.jar b/lib/gson-2.10.1.jar new file mode 100644 index 0000000..a88c5bd Binary files /dev/null and b/lib/gson-2.10.1.jar differ diff --git a/lib/javassist.jar b/lib/javassist.jar new file mode 100644 index 0000000..0fbf8ed Binary files /dev/null and b/lib/javassist.jar differ diff --git a/lib/junit-jupiter-5.12.2.jar b/lib/junit-jupiter-5.12.2.jar new file mode 100644 index 0000000..a373a88 Binary files /dev/null and b/lib/junit-jupiter-5.12.2.jar differ diff --git a/lib/junit-jupiter-api-5.12.2.jar b/lib/junit-jupiter-api-5.12.2.jar new file mode 100644 index 0000000..8598a77 Binary files /dev/null and b/lib/junit-jupiter-api-5.12.2.jar differ diff --git a/lib/junit-jupiter-engine-5.12.2.jar b/lib/junit-jupiter-engine-5.12.2.jar new file mode 100644 index 0000000..63b4a07 Binary files /dev/null and b/lib/junit-jupiter-engine-5.12.2.jar differ diff --git a/lib/junit-jupiter-params-5.12.2.jar b/lib/junit-jupiter-params-5.12.2.jar new file mode 100644 index 0000000..5c215ea Binary files /dev/null and b/lib/junit-jupiter-params-5.12.2.jar differ diff --git a/lib/junit-platform-commons-1.12.2.jar b/lib/junit-platform-commons-1.12.2.jar new file mode 100644 index 0000000..a01aeb9 Binary files /dev/null and b/lib/junit-platform-commons-1.12.2.jar differ diff --git a/lib/junit-platform-engine-1.12.2.jar b/lib/junit-platform-engine-1.12.2.jar new file mode 100644 index 0000000..b130d6e Binary files /dev/null and b/lib/junit-platform-engine-1.12.2.jar differ diff --git a/lib/opentest4j-1.3.0.jar b/lib/opentest4j-1.3.0.jar new file mode 100644 index 0000000..7ec7bc5 Binary files /dev/null and b/lib/opentest4j-1.3.0.jar differ