initial commit, template added

This commit is contained in:
2025-10-16 18:54:20 +02:00
commit ececfae877
69 changed files with 10398 additions and 0 deletions

View File

@@ -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);
}
}

Binary file not shown.

View File

@@ -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');
}
}

Binary file not shown.

View File

@@ -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

View File

@@ -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');
}
}

Binary file not shown.

View File

@@ -0,0 +1,11 @@
program Test
{
int Trap()
{
}
void main () {
print(Trap());
}
}

View File

@@ -0,0 +1,56 @@
package ssw.mj;
import ssw.mj.impl.Parser;
import ssw.mj.impl.Scanner;
import java.io.*;
/**
* <code>Compiler</code> is the driver for the MicroJava-Compiler.
* <p>
* Execute<br>
* <code>java ssw.mj.Compiler &lt;<i>MJ-Source-Filename</i>&gt;</code><br>
* 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());
}
}
}

View File

@@ -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<String> 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<String> getErrors() {
return errors;
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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<String, String> 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<String, String> 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<RecorderTraceEntry> trace;
private RecorderTraceEntry currentEntry = null;
private final Map<String, Integer> 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<String, String> 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<RecorderTraceEntry> 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);
}
}

View File

@@ -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);
}
}

View File

@@ -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 @<hashcode> attempted duplicate class definition for <classname>
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<URL> 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);
}
}

View File

@@ -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<Token> receivedTokens, List<Token> expectedTokens) {
public String toJson() {
return gson.toJson(this);
}
}
private record ParserPayload(List<Recorder.RecorderTraceEntry> 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<Token> receivedTokens,
List<Token> 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 = "<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
" <head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
" <title>Compiler Visualization</title>\n" +
" </head>\n" +
" <body>\n" +
" <div id=\"app\"></div>\n" +
"\n" +
" <script id=\"__DATA__\" type=\"application/json\">\n" +
" " + (data.toJson()) + "\n" + // Inject the visualization data
" </script>\n" +
"\n" +
" <script type=\"text/javascript\">\n" +
" let data = JSON.parse(document.getElementById('__DATA__').textContent);\n" +
" let app = document.getElementById('app');\n" +
"\n" +
" let iframe = document.createElement('iframe');\n" +
" iframe.src = '" + visualizerUrl + "';\n" +
" iframe.style.position = 'fixed';\n" +
" iframe.style.top = 0;\n" +
" iframe.style.left = 0;\n" +
" iframe.style.right = 0;\n" +
" iframe.style.bottom = 0;\n" +
" iframe.style.border = 0;\n" +
" iframe.style.width = '100%';\n" +
" iframe.style.height = '100%';\n" +
"\n" +
" app.appendChild(iframe);\n" +
" \n" +
" iframe.addEventListener('load', () => {\n" +
" setTimeout(() => {\n" +
" iframe.contentWindow.postMessage({\n" +
" type: 'boot',\n" +
" data\n" +
" }, '*');\n" +
" }, 100);\n" +
" });\n" +
" </script>\n" +
" </body>\n" +
"</html>";
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;
}
}
}

View File

@@ -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]);
}
}
}

View File

@@ -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<Integer> 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 <code>this</code> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
// =================================================
// =================================================
}

View File

@@ -0,0 +1,144 @@
package ssw.mj.impl;
import ssw.mj.Errors;
import ssw.mj.Errors.Message;
import ssw.mj.scanner.Token;
import static ssw.mj.Errors.Message.TOKEN_EXPECTED;
import static ssw.mj.scanner.Token.Kind.eof;
import static ssw.mj.scanner.Token.Kind.none;
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
static {
// Initialize first and follow sets.
}
// ---------------------------------
// TODO Exercise UE-P-2: One top-down parsing method per production
/**
* Program = <br>
* "program" ident <br>
* { ConstDecl | VarDecl | ClassDecl } <br>
* "{" { MethodDecl } "}" .
*/
private void Program() {
// TODO Exercise UE-P-2
}
// ...
// ------------------------------------
// TODO Exercise UE-P-3: Error recovery methods: recoverDecl, recoverMethodDecl and recoverStat (+ TODO Exercise UE-P-5: Check idents for Type kind)
// ====================================
// ====================================
}

View File

@@ -0,0 +1,108 @@
package ssw.mj.impl;
import ssw.mj.Errors;
import ssw.mj.scanner.Token;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import static ssw.mj.scanner.Token.Kind.none;
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<String, Token.Kind> keywords;
static {
keywords = new HashMap<>();
}
/**
* Returns next token. To be used by parser.
*/
public Token next() {
// TODO Exercise UE-P-1: implementation of next method
Token t = new Token(none, 1, 1);
return t;
}
private void nextCh() {
// TODO Exercise UE-P-1: implementation of nextCh method and other private helper methods
}
// 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';
}
// ================================================
// ================================================
}

View File

@@ -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 <code>name</code> from the innermost scope.
*/
public Obj find(String name) {
// TODO Exercise UE-P-4
return noObj;
}
/**
* Retrieves the field <code>name</code> from the fields of
* <code>type</code>.
*/
public Obj findField(String name, Struct type) {
// TODO Exercise UE-P-4
return noObj;
}
// ===============================================
// ===============================================
}

View File

@@ -0,0 +1,126 @@
package ssw.mj.scanner;
/**
* A <code>Token</code> 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;
}
}

View File

@@ -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 <code>Obj</code> 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<String, Obj> 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<Entry<String, Obj>> it = locals.entrySet().iterator();
for (int i = 0; i < nPars; i++) {
if (!first) {
sb.append(", ");
}
Entry<String, Obj> 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);
}
}

View File

@@ -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<String, Obj> 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<String, Obj> locals() {
return Collections.unmodifiableMap(locals);
}
}

View File

@@ -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.
* <br>
* 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<String, Obj> 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 <code>name</code>.
*/
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<String, Obj> 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);
}
}