initial commit, template added
This commit is contained in:
27
MicroJava Compiler/Prims.mj
Normal file
27
MicroJava Compiler/Prims.mj
Normal 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);
|
||||
}
|
||||
}
|
||||
BIN
MicroJava Compiler/Prims.obj
Normal file
BIN
MicroJava Compiler/Prims.obj
Normal file
Binary file not shown.
87
MicroJava Compiler/StudentList.mj
Normal file
87
MicroJava Compiler/StudentList.mj
Normal 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');
|
||||
}
|
||||
}
|
||||
BIN
MicroJava Compiler/StudentList.obj
Normal file
BIN
MicroJava Compiler/StudentList.obj
Normal file
Binary file not shown.
6
MicroJava Compiler/StudentListOutput.txt
Normal file
6
MicroJava Compiler/StudentListOutput.txt
Normal 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
|
||||
75
MicroJava Compiler/TestProgram.mj
Normal file
75
MicroJava Compiler/TestProgram.mj
Normal 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');
|
||||
}
|
||||
}
|
||||
BIN
MicroJava Compiler/TestProgram.obj
Normal file
BIN
MicroJava Compiler/TestProgram.obj
Normal file
Binary file not shown.
11
MicroJava Compiler/Trap.mj
Normal file
11
MicroJava Compiler/Trap.mj
Normal file
@@ -0,0 +1,11 @@
|
||||
program Test
|
||||
{
|
||||
|
||||
int Trap()
|
||||
{
|
||||
}
|
||||
|
||||
void main () {
|
||||
print(Trap());
|
||||
}
|
||||
}
|
||||
56
MicroJava Compiler/src/ssw/mj/Compiler.java
Normal file
56
MicroJava Compiler/src/ssw/mj/Compiler.java
Normal 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 <<i>MJ-Source-Filename</i>></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());
|
||||
}
|
||||
}
|
||||
}
|
||||
140
MicroJava Compiler/src/ssw/mj/Errors.java
Normal file
140
MicroJava Compiler/src/ssw/mj/Errors.java
Normal 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;
|
||||
}
|
||||
}
|
||||
517
MicroJava Compiler/src/ssw/mj/Interpreter.java
Normal file
517
MicroJava Compiler/src/ssw/mj/Interpreter.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
158
MicroJava Compiler/src/ssw/mj/Recorder.java
Normal file
158
MicroJava Compiler/src/ssw/mj/Recorder.java
Normal 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);
|
||||
}
|
||||
}
|
||||
84
MicroJava Compiler/src/ssw/mj/Run.java
Normal file
84
MicroJava Compiler/src/ssw/mj/Run.java
Normal 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);
|
||||
}
|
||||
}
|
||||
173
MicroJava Compiler/src/ssw/mj/TracingClassLoader.java
Normal file
173
MicroJava Compiler/src/ssw/mj/TracingClassLoader.java
Normal 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);
|
||||
}
|
||||
}
|
||||
218
MicroJava Compiler/src/ssw/mj/Visualizer.java
Normal file
218
MicroJava Compiler/src/ssw/mj/Visualizer.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
MicroJava Compiler/src/ssw/mj/codegen/Decoder.java
Normal file
97
MicroJava Compiler/src/ssw/mj/codegen/Decoder.java
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
65
MicroJava Compiler/src/ssw/mj/codegen/Label.java
Normal file
65
MicroJava Compiler/src/ssw/mj/codegen/Label.java
Normal 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;
|
||||
}
|
||||
}
|
||||
152
MicroJava Compiler/src/ssw/mj/codegen/Operand.java
Normal file
152
MicroJava Compiler/src/ssw/mj/codegen/Operand.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
294
MicroJava Compiler/src/ssw/mj/impl/Code.java
Normal file
294
MicroJava Compiler/src/ssw/mj/impl/Code.java
Normal 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
|
||||
}
|
||||
|
||||
// =================================================
|
||||
// =================================================
|
||||
}
|
||||
144
MicroJava Compiler/src/ssw/mj/impl/Parser.java
Normal file
144
MicroJava Compiler/src/ssw/mj/impl/Parser.java
Normal 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)
|
||||
|
||||
// ====================================
|
||||
// ====================================
|
||||
}
|
||||
108
MicroJava Compiler/src/ssw/mj/impl/Scanner.java
Normal file
108
MicroJava Compiler/src/ssw/mj/impl/Scanner.java
Normal 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';
|
||||
}
|
||||
|
||||
// ================================================
|
||||
// ================================================
|
||||
}
|
||||
100
MicroJava Compiler/src/ssw/mj/impl/Tab.java
Normal file
100
MicroJava Compiler/src/ssw/mj/impl/Tab.java
Normal 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;
|
||||
}
|
||||
|
||||
// ===============================================
|
||||
// ===============================================
|
||||
}
|
||||
126
MicroJava Compiler/src/ssw/mj/scanner/Token.java
Normal file
126
MicroJava Compiler/src/ssw/mj/scanner/Token.java
Normal 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;
|
||||
}
|
||||
}
|
||||
144
MicroJava Compiler/src/ssw/mj/symtab/Obj.java
Normal file
144
MicroJava Compiler/src/ssw/mj/symtab/Obj.java
Normal 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);
|
||||
}
|
||||
}
|
||||
58
MicroJava Compiler/src/ssw/mj/symtab/Scope.java
Normal file
58
MicroJava Compiler/src/ssw/mj/symtab/Scope.java
Normal 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);
|
||||
}
|
||||
}
|
||||
116
MicroJava Compiler/src/ssw/mj/symtab/Struct.java
Normal file
116
MicroJava Compiler/src/ssw/mj/symtab/Struct.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user