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

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
out

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions

104
.idea/MicroJava.iml generated Normal file
View File

@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/MicroJava Compiler/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/MicroJava Tests/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/MicroJava Tests/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/MicroJava VM Tests/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/gson-2.10.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/javassist.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/apiguardian-api-1.1.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-5.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-api-5.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-engine-5.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-params-5.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-platform-commons-1.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-platform-engine-1.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library" scope="TEST">
<library>
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/opentest4j-1.3.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>

10
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="JAVA">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

9
.idea/exercise02.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,java.io.PrintStream,printf,java.net.http.HttpClient,newHttpClient" />
</inspection_tool>
</profile>
</component>

17
.idea/libraries/junit_jupiter.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<component name="libraryTable">
<library name="junit.jupiter" type="repository">
<properties maven-id="org.junit.jupiter:junit-jupiter:5.12.2" />
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/junit-jupiter-5.12.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-jupiter-api-5.12.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/opentest4j-1.3.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-platform-commons-1.12.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-jupiter-params-5.12.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-jupiter-engine-5.12.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/junit-platform-engine-1.12.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

5
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/MicroJava.iml" filepath="$PROJECT_DIR$/.idea/MicroJava.iml" />
</modules>
</component>
</project>

19
.idea/runConfigurations/UE_P_1.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-1" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="ssw.mj.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<option name="PACKAGE_NAME" value="ssw.mj.test" />
<option name="MAIN_CLASS_NAME" value="ssw.mj.test.ScannerTest" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="class" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

16
.idea/runConfigurations/UE_P_2.xml generated Normal file
View File

@@ -0,0 +1,16 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-2" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="pattern" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<patterns>
<pattern testClass="ssw.mj.test.ScannerTest" />
<pattern testClass="ssw.mj.test.ParserTest" />
</patterns>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

17
.idea/runConfigurations/UE_P_3.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-3" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="pattern" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<patterns>
<pattern testClass="ssw.mj.test.ScannerTest" />
<pattern testClass="ssw.mj.test.ParserTest" />
<pattern testClass="ssw.mj.test.RecoverTest" />
</patterns>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

18
.idea/runConfigurations/UE_P_4.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-4" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="pattern" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<patterns>
<pattern testClass="ssw.mj.test.ScannerTest" />
<pattern testClass="ssw.mj.test.ParserTest" />
<pattern testClass="ssw.mj.test.RecoverTest" />
<pattern testClass="ssw.mj.test.SymbolTableTest" />
</patterns>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

19
.idea/runConfigurations/UE_P_5.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-5" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="pattern" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<patterns>
<pattern testClass="ssw.mj.test.ScannerTest" />
<pattern testClass="ssw.mj.test.ParserTest" />
<pattern testClass="ssw.mj.test.RecoverTest" />
<pattern testClass="ssw.mj.test.SymbolTableTest" />
<pattern testClass="ssw.mj.test.SimpleCodeGenerationTest" />
</patterns>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

13
.idea/runConfigurations/UE_P_6.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UE-P-6" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="directory" />
<option name="VM_PARAMETERS" value="-ea -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<dir value="$PROJECT_DIR$/MicroJava Tests/tests" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -0,0 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="(for lecturers) Run verbose tracing" type="JUnit" factoryName="JUnit">
<module name="MicroJava" />
<option name="MAIN_CLASS_NAME" value="" />
<option name="METHOD_NAME" value="" />
<option name="TEST_OBJECT" value="directory" />
<option name="VM_PARAMETERS" value="-ea -verbose:class -verbose:dynload -Djava.system.class.loader=ssw.mj.TracingClassLoader" />
<dir value="$PROJECT_DIR$/MicroJava Tests/tests" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

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

View File

@@ -0,0 +1,54 @@
program Animals
class Animal {
char[] name;
}
{
void setName(Animal a, char[] name) {
a.name = name;
}
void aPrint(Animal a)
int i, l;
char c;
{
l = len(a.name);
i = 0;
while (i < l) {
print(a.name[i]);
i += 1;
}
}
void main()
int a;
Animal[] animals;
char[] cat;
char[] dog;
char[] octopus;
{
cat = new char[3];
cat[0] = 'c';
cat[1] = 'a';
cat[2] = 't';
dog = new char[3];
dog[0] = 'd';
dog[1] = 'o';
dog[2] = 'g';
octopus = new char[7];
octopus[0] = 'o';
octopus[1] = 'c';
octopus[2] = 't';
octopus[3] = 'o';
octopus[4] = 'p';
octopus[5] = 'u';
octopus[6] = 's';
animals = new Animal[3];
animals[0] = new Animal;
animals[1] = new Animal;
animals[2] = new Animal;
setName(animals[0],cat);
setName(animals[1],dog);
setName(animals[2],octopus);
read(a);
aPrint(animals[a]);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
program Test
{
void main()
int a;
{
read(a);
if (a == 1) { print('='); print('='); print(','); }
if (a != 1) { print('!'); print('='); print(','); }
if (a < 1) { print('<'); print(','); }
if (a <= 1) { print('<'); print('='); print(','); }
if (a > 1) { print('>'); print(','); }
if (a >= 1) { print('>'); print('='); print(','); }
}
}

View File

@@ -0,0 +1,880 @@
package ssw.mj.test;
import org.junit.jupiter.api.Test;
import ssw.mj.test.support.BaseCompilerTestCase;
import static ssw.mj.Errors.Message.*;
public class CodeGenerationTest extends BaseCompilerTestCase {
/**
* Symbol table for most examples of this test class.
*/
private void expectExampleSymTab() {
expectSymTabUniverse();
expectSymTab("Program A:");
expectSymTab(" Constant: int max = 12");
expectSymTab(" Global Variable 0: char c");
expectSymTab(" Global Variable 1: int i");
expectSymTab(" Type B: class (2 fields)");
expectSymTab(" Local Variable 0: int x");
expectSymTab(" Local Variable 1: int y");
expectSymTab(" Method: void main (3 locals, 0 parameters)");
expectSymTab(" Local Variable 0: int[] iarr");
expectSymTab(" Local Variable 1: class (2 fields) b");
expectSymTab(" Local Variable 2: int n");
}
private void expectSymTabWithSum() {
expectSymTabUniverse();
expectSymTab("Program A:");
expectSymTab(" Constant: int max = 12");
expectSymTab(" Global Variable 0: char c");
expectSymTab(" Global Variable 1: int i");
expectSymTab(" Type B: class (2 fields)");
expectSymTab(" Local Variable 0: int x");
expectSymTab(" Local Variable 1: int y");
expectSymTab(" Method: void main (4 locals, 0 parameters)");
expectSymTab(" Local Variable 0: int[] iarr");
expectSymTab(" Local Variable 1: class (2 fields) b");
expectSymTab(" Local Variable 2: int n");
expectSymTab(" Local Variable 3: int sum");
}
@Test
public void bsp11() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(i); " + LF + //
" if (i <= n) n = 1;" + LF + //
" print(n); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "1");
addExpectedRun("1", "0");
parseVerifyVisualize();
}
@Test
public void bsp12() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(i); " + LF + //
" n = 1; " + LF + //
" if (i <= n && n < 0) n = 2;" + LF + //
" print(n); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "1");
addExpectedRun("2", "1");
parseVerifyVisualize();
}
@Test
public void bsp13() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(i); " + LF + //
" n = 1; " + LF + //
" if (i <= n || i < 10) n = 2;" + LF + //
" print(n); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "2");
addExpectedRun("2", "2");
addExpectedRun("20", "1");
parseVerifyVisualize();
}
@Test
public void bsp14() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(i); " + LF + //
" n = 1; " + LF + //
" if (i <= n || i < 10 && i > 5) n = 2;" + LF + //
" print(n); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "2");
addExpectedRun("2", "1");
addExpectedRun("6", "2");
addExpectedRun("20", "1");
parseVerifyVisualize();
}
@Test
public void bsp15() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(n); " + LF + //
" while (i <= n) { i++; }" + LF + //
" print(i); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "1");
addExpectedRun("-1", "0");
addExpectedRun("1", "2");
addExpectedRun("10", "11");
parseVerifyVisualize();
}
@Test
public void bsp16() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n;" + LF + //
" {" + LF + //
" read(i); " + LF + //
" if (i <= max) n = 1; else n = 2;" + LF + //
" print(n); " + LF + //
" }" + LF + //
"}");
expectExampleSymTab();
addExpectedRun("0", "1");
addExpectedRun("13", "2");
addExpectedRun("12", "1");
addExpectedRun("-1", "1");
addExpectedRun("-13", "1");
parseVerifyVisualize();
}
@Test
public void bsp17() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n; int sum;" + LF + //
" {" + LF + //
" read(n); " + LF + //
" sum = 0; " + LF + //
" while (i <= n) { sum += i; i++; }" + LF + //
" print(sum); " + LF + //
" }" + LF + //
"}");
expectSymTabWithSum();
addExpectedRun("0", "0");
addExpectedRun("-1", "0");
addExpectedRun("1", "1");
addExpectedRun("10", "55");
parseVerifyVisualize();
}
@Test
public void bsp18() {
initCode("program A" + LF + //
" final int max = 12;" + LF + //
" char c; int i;" + LF + //
" class B { int x, y; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int[] iarr; B b; int n; int sum;" + LF + //
" {" + LF + //
" read(n); " + LF + //
" sum = 0; " + LF + //
" i = 2;" + LF + //
" while (i <= n) { sum += i; i++; }" + LF + //
" print(sum); " + LF + //
" }" + LF + //
"}");
expectSymTabWithSum();
addExpectedRun("0", "0");
addExpectedRun("-1", "0");
addExpectedRun("1", "0");
addExpectedRun("10", "54");
parseVerifyVisualize();
}
@Test
public void methodCall() {
initCode("program A" + LF + // 1
"{" + LF + // 2
" void bar() {" + LF + // 3
" print('b');" + LF + // 4
" print('a');" + LF + // 5
" print('r');" + LF + // 6
" }" + LF + // 7
" void foo() {" + LF + // 8
" print('f');" + LF + // 9
" print('o');" + LF + // 10
" print('o');" + LF + // 11
" }" + LF + // 12
" void main () {" + LF + // 13
" foo();" + LF + // 14
" }" + LF + // 15
"}"); // 16
addExpectedRun("", "foo");
parseVerifyVisualize();
}
@Test
public void fib() {
initCode("program A" + LF + //
"{" + LF + //
" int fib(int n) {" + LF + //
" if (n <= 1) return 1; " + LF + //
" return fib(n-1) + fib(n-2); " + LF + //
" }" + LF + //
" void main ()" + LF + //
" int n;" + LF + //
" {" + LF + //
" read(n); " + LF + //
" print(fib(n)); " + LF + //
" }" + LF + //
"}");
addExpectedRun("-1", "1");
addExpectedRun("0", "1");
addExpectedRun("1", "1");
addExpectedRun("2", "2");
addExpectedRun("3", "3");
addExpectedRun("4", "5");
addExpectedRun("5", "8");
addExpectedRun("6", "13");
addExpectedRun("7", "21");
addExpectedRun("8", "34");
addExpectedRun("9", "55");
addExpectedRun("10", "89");
addExpectedRun("11", "144");
addExpectedRun("22", "28657");
parseVerifyVisualize();
}
@Test
public void fibDyn() {
initCode("program A" + LF + //
" int[] matrix; " + LF + //
"{" + LF + //
" int fib(int n) int r; {" + LF + //
" if (n <= 1) return 1; " + LF + //
" if(matrix[n] != 0) return matrix[n]; " + LF + //
" r = fib(n-1) + fib(n-2); " + LF + //
" matrix[n] = r; " + LF + //
" return r; " + LF + //
" }" + LF + //
" void main ()" + LF + //
" int n;" + LF + //
" {" + LF + //
" matrix = new int[1000]; " + LF + //
" read(n); " + LF + //
" print(fib(n)); " + LF + //
" }" + LF + //
"}");
addExpectedRun("-1", "1");
addExpectedRun("0", "1");
addExpectedRun("1", "1");
addExpectedRun("2", "2");
addExpectedRun("3", "3");
addExpectedRun("4", "5");
addExpectedRun("5", "8");
addExpectedRun("6", "13");
addExpectedRun("7", "21");
addExpectedRun("8", "34");
addExpectedRun("9", "55");
addExpectedRun("10", "89");
addExpectedRun("11", "144");
addExpectedRun("22", "28657");
addExpectedRun("30", "1346269");
addExpectedRun("40", "165580141");
addExpectedRun("45", "1836311903");
parseVerifyVisualize();
}
@Test
public void testElseIf() {
initCode("program Test {" + LF + // 1
" void main() int i; {" + LF + // 2
" read(i);" + LF + // 3
" if (i == 1) print(9);" + LF + // 4
" else if (i == 2) print(8);" + LF + // 5
" else print(7);" + LF + // 6
" }" + LF + // 7
"}");
addExpectedRun("1", "9");
addExpectedRun("2", "8");
addExpectedRun("3", "7");
addExpectedRun("4", "7");
parseVerifyVisualize();
}
@Test
public void mainVar() {
initCode("program Test" + LF + //
" int main;" + LF + //
"{" + LF + //
"}");
expectError(4, 2, MAIN_NOT_FOUND);
parseVerifyVisualize();
}
@Test
public void mainNotVoid() {
initCode("program Test {" + LF + //
" char main() { }" + LF + //
"}");
expectError(2, 15, MAIN_NOT_VOID);
parseVerifyVisualize();
}
@Test
public void noLoop() {
initCode("program Test {" + LF + //
" void main() {" + LF + //
" break;" + LF + //
" }" + LF + //
"}");
expectError(3, 10, BREAK_OUTSIDE_LOOP);
parseVerifyVisualize();
}
@Test
public void returnVoid() {
initCode("program Test {" + LF + //
" void test() {" + LF + //
" return 5;" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(3, 12, UNEXPECTED_RETURN_VALUE);
parseVerifyVisualize();
}
@Test
public void wrongReturnType() {
initCode("program Test {" + LF + //
" int test() {" + LF + //
" return 'x';" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(3, 15, RETURN_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void wrongReturnTypeNull() {
initCode("program Test {" + LF + //
" int test() {" + LF + //
" return null;" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(3, 16, RETURN_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void noReturnVal() {
initCode("program Test {" + LF + //
" int test() {" + LF + //
" return;" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(3, 11, MISSING_RETURN_VALUE);
parseVerifyVisualize();
}
@Test
public void wrongReturnTypeArr() {
initCode("program Test {" + LF + //
" int[] test() {" + LF + //
" return new int[10];" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(2, 9, ILLEGAL_METHOD_RETURN_TYPE);
parseVerifyVisualize();
}
@Test
public void wrongReturnClass() {
initCode("program Test" + LF + //
" class C1 { }" + LF + //
"{" + LF + //
" C1 test() {" + LF + //
" return new C1;" + LF + //
" }" + LF + //
" void main() {}" + LF + //
"}");
expectError(4, 6, ILLEGAL_METHOD_RETURN_TYPE);
parseVerifyVisualize();
}
@Test
public void noMeth() {
initCode("program Test {" + LF + //
" void main() int i; {" + LF + //
" i(10);" + LF + //
" }" + LF + //
"}");
expectError(3, 7, CALL_TO_NON_METHOD);
parseVerifyVisualize();
}
@Test
public void paramType() {
initCode("program Test {" + LF + //
" void method(int x) { }" + LF + //
" void main() {" + LF + //
" method('a');" + LF + //
" }" + LF + //
"}");
expectError(4, 15, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void paramTypeArr() {
initCode("program Test {" + LF + //
" void method(int[] x) { }" + LF + //
" void main() {" + LF + //
" method(new char[10]);" + LF + //
" }" + LF + //
"}");
expectError(4, 24, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void paramTypeClass() {
initCode("program Test" + LF + //
" class C1 { }" + LF + //
" class C2 { }" + LF + //
"{" + LF + //
" void method(C1 c1) { }" + LF + //
" void main() {" + LF + //
" method(new C2);" + LF + //
" }" + LF + //
"}");
expectError(7, 18, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void moreParams() {
initCode("program Test {" + LF + //
" void method(int x, char c) { }" + LF + //
" void main() {" + LF + //
" method(1, 'a', 1);" + LF + //
" }" + LF + //
"}");
expectError(4, 21, WRONG_ARGUMENT_COUNT);
parseVerifyVisualize();
}
@Test
public void lessParams() {
initCode("program Test {" + LF + //
" void method(int x, char c) { }" + LF + //
" void main() {" + LF + //
" method(1);" + LF + //
" }" + LF + //
"}");
expectError(4, 13, WRONG_ARGUMENT_COUNT);
parseVerifyVisualize();
}
@Test
public void incompTypesCond() {
initCode("program Test {" + LF + //
" void main() int i; { " + LF + //
" if (i > null) { }" + LF + //
" }" + LF + //
"}");
expectError(3, 17, INCOMPATIBLE_TYPES);
parseVerifyVisualize();
}
@Test
public void incompTypesCondArr() {
initCode("program Test {" + LF + //
" void main() int[] ia; char[] ca; { " + LF + //
" if (ia > ca) { }" + LF + //
" }" + LF + //
"}");
expectError(3, 16, INCOMPATIBLE_TYPES);
parseVerifyVisualize();
}
@Test
public void incompTypesCondClass() {
initCode("program Test" + LF + //
" class C1 { }" + LF + //
"{" + LF + //
" void main() C1 c1; int i; { " + LF + //
" if (c1 > i) { };" + LF + //
" }" + LF + //
"}");
expectError(5, 15, INCOMPATIBLE_TYPES);
parseVerifyVisualize();
}
@Test
public void wrongEqCheck() {
initCode("program Test {" + LF + //
" void main() int[] ia1, ia2; {" + LF + //
" if (ia1 > ia2) { }" + LF + //
" }" + LF + //
"}");
expectError(3, 18, ILLEGAL_REFERENCE_COMPARISON);
parseVerifyVisualize();
}
@Test
public void testSimpleBreak() {
initCode("program Test {" + LF + //
" void main() {" + LF + //
" while(42 > 0) /* while(true) */" + LF + //
" {" + LF + //
" break;" + LF + //
" }" + LF + //
" }" + LF + //
"}");
parseVerifyVisualize();
}
@Test
public void testBreak() {
initCode("program A" + LF + //
" int i;" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int n;" + LF + //
" {" + LF + //
" read(n); " + LF + //
" while (i <= n) { while(1 < 2) { if(1 == 1) { break; } } if(i == 5) break; i++; }"
+ LF + //
" print(i); " + LF + //
" }" + LF + //
"}");
addExpectedRun("10", "5");
parseVerifyVisualize();
}
@Test
public void testNestedBreak() {
initCode("program Test {" + LF + //
" void main() " + LF + //
" int n, o;" + LF + //
" {" + LF + //
" o = 21;" + LF + //
" while(83 < 84)" + LF + //
" {" + LF + //
" while(167 < 168)" + LF + //
" {" + LF + //
" break;" + LF + //
" }" + LF + //
" break;" + LF + //
" }" + LF + //
" }" + LF + //
"}");
parseVerifyVisualize();
}
@Test
public void lenTest() {
initCode("program A" + LF + //
" class A { int[] x; }" + LF + //
" class B { A a; }" + LF + //
" class C { B b; }" + LF + //
"{" + LF + //
" void main ()" + LF + //
" C[] c;" + LF + //
" {" + LF + //
" c = new C[5];" + LF + //
" print(len(c));" + LF + //
" }" + LF + //
"}");
addExpectedRun("5");
parseVerifyVisualize();
}
@Test
public void basicOrdChrTest() {
initCode("program Test {" + LF + //
" void main() int i; char c; {" + LF + //
" i = ord('A');" + LF + //
" print(i);" + LF + //
" i = ord('*');" + LF + //
" print(i);" + LF + //
" c = chr(49);" + LF + //
" print(c);" + LF + //
" }" + LF + //
"}");
addExpectedRun("65421");
parseVerifyVisualize();
}
@Test
public void trappingOrdChrTest() {
initCode("program Test {" + LF + //
" int trap() {" + LF + //
" print(7 * 7);" + LF + //
" }" + LF + //
" void main() int i; char c; {" + LF + //
" ord('!');" + LF + //
" chr(42);" + LF + //
" i = ord('!');" + LF + //
" c = chr(42);" + LF + //
" }" + LF + //
"}");
addExpectedRun("");
parseVerifyVisualize();
}
@Test
public void unusedReturnVal() {
initCode("program Test {" + LF + //
" int getUnused() {" + LF + //
" return 351;" + LF + //
" }" + LF + //
" int polluteAndGet() {" + LF + //
" getUnused();" + LF + //
" return 42;" + LF + //
" }" + LF + //
" void main() {" + LF + //
" print(932 + polluteAndGet());" + LF + //
" }" + LF + //
"}");
addExpectedRun("974");
parseVerifyVisualize();
}
@Test
public void coverUniverseMethod() {
initCode("program Test {" + LF + //
" int cast(char c) { return ord(c); }" + LF + //
" int ord(char c) { return cast(c) - 30; }" + LF + //
" void main() {" + LF + //
" print(chr(ord('A')));" + LF + //
" }" + LF + //
"}");
addExpectedRun("#");
parseVerifyVisualize();
}
@Test
public void paramType2() {
initCode("program Test {" + LF + //
" void method(int x, int y) { }" + LF + //
" void main() {" + LF + //
" method(1, 'a');" + LF + //
" }" + LF + //
"}");
expectError(4, 18, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void paramTypeArr2() {
initCode("program Test {" + LF + //
" void method(int[] x, int y) { }" + LF + //
" void main() {" + LF + //
" method(new int[10], new char[10]);" + LF + //
" }" + LF + //
"}");
expectError(4, 37, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void paramTypeClass2() {
initCode("program Test" + LF + //
" class C1 { }" + LF + //
" class C2 { }" + LF + //
"{" + LF + //
" void method(C1 c1, C2 c2) { }" + LF + //
" void main() {" + LF + //
" method(new C1, new C1);" + LF + //
" }" + LF + //
"}");
expectError(7, 26, ARGUMENT_TYPE_MISMATCH);
parseVerifyVisualize();
}
@Test
public void testRelops() {
initFile("relops.mj");
addExpectedRun("0", "!=,<,<=,");
addExpectedRun("1", "==,<=,>=,");
addExpectedRun("2", "!=,>,>=,");
parseVerifyVisualize();
}
@Test
public void testAnimals() {
initFile("animals.mj");
addExpectedRun("0", "cat");
addExpectedRun("1", "dog");
addExpectedRun("2", "octopus");
parseVerifyVisualize();
}
@Test
public void compareNeg() {
initCode("program A" + LF + //
"{" + LF + //
" void main ()" + LF + //
" int neg;" + LF + //
" {" + LF + //
" neg = -42;" + LF + //
" if (neg == -42) print(42);" + LF + //
" else print(neg); " + LF + //
" }" + LF + //
"}");
addExpectedRun("42");
parseVerifyVisualize();
}
// index from end tests
@Test
public void arrayFromEndWithFunctionCall() {
initCode("""
program Test
final int len = 2;
{
int const1() {
return 1;
}
void main() int[] a; {
a = new int[len];
a[0] = 13;
a[~const1()] = 42;
print(a[~const1()]);
}
}""");
addExpectedRun("42");
parseVerifyVisualize();
}
@Test
public void createPalindrom() {
initCode("""
program Test {
void toPalindrom(char[] in, char[] out) int i, l; {
l = len(in);
i = 0;
while (i < l) {
out[i] = in[i];
out[~(i + 1)] = in[i];
i++;
}
}
void printText(char[] text) int i; {
i = 0;
while (i < len(text)) {
print(text[i]);
i++;
}
}
void main() char[] a, out; int i; {
a = new char[5];
a[0] = 'l';
a[1] = 'a';
a[2] = 'g';
a[3] = 'e';
a[4] = 'r';
out = new char[10];
toPalindrom(a, out);
printText(out);
a = new char[2];
a[0] = 'o';
a[1] = 't';
out = new char[4];
toPalindrom(a, out);
printText(out);
}
}""");
addExpectedRun("lagerregalotto");
parseVerifyVisualize();
}
@Test
public void iterateArrayFromEnd() {
initCode("""
program Test
final int len = 3;
{
void main() int[] a; int i; {
a = new int[len];
a[0] = 1;
a[1] = 2;
a[2] = 3;
i = 1;
while (i <= len) {
print(a[~i]);
i++;
}
}
}""");
addExpectedRun("321");
parseVerifyVisualize();
}
}

View File

@@ -0,0 +1,406 @@
package ssw.mj.test;
import org.junit.jupiter.api.Test;
import ssw.mj.scanner.Token;
import ssw.mj.test.support.BaseCompilerTestCase;
import static ssw.mj.Errors.Message.*;
public class ParserTest extends BaseCompilerTestCase {
@Test
public void testWorkingFinalDecls() {
initCode("program Test" + LF + // 1
" final int i = 1;" + LF + // 2
" final int j = 1;" + LF + // 3
" final int k = 1;" + LF + // 4
"{ void main() { } }"); // 5
parseVerifyVisualize();
}
@Test
public void testWorkingDecls() {
initCode("program Test" + LF + // 1
" int i;" + LF + // 2
" int j, k;" + LF + // 3
"{ void main() { } }"); // 4
parseVerifyVisualize();
}
@Test
public void testWorkingMethods() {
initCode("program Test" + LF + // 1
" int i;" + LF + // 2
" int j, k;" + LF + // 3
"{" + LF + // 4
" void foo() { }" + LF + // 5
" void bar() { }" + LF + // 6
" void main() { }" + LF + // 7
" }" + LF // 8
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodsWithParameters() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo(int i) { }" + LF + // 3
" void bar(int i, char c) { }" + LF + // 4
" void main() { }" + LF + // 5
" }" + LF // 6
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodsWithLocals() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo() int i; { }" + LF + // 3
" void bar() int i; char c; { }" + LF + // 4
" void main() { }" + LF + // 5
" }" + LF // 6
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodsWithParametersAndLocals() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo(char ch) int i; { }" + LF + // 3
" void bar(int x, int y) int i; char c; { }" + LF + // 4
" void main() { }" + LF + // 5
" }" + LF // 6
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodCall() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo(char ch) int i; { }" + LF + // 3
" void main() { foo('a'); }" + LF + // 4
" }" + LF // 5
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodCallTwoParams() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo(char ch, int x) int i; { }" + LF + // 3
" void main() { foo('a', 1); }" + LF + // 4
" }" + LF // 5
);
parseVerifyVisualize();
}
@Test
public void testWorkingMethodCallThreeParams() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo(char ch, int x, char ch2) int i; { }" + LF + // 3
" void main() { foo('a', 1, 'b'); }" + LF + // 4
" }" + LF // 5
);
parseVerifyVisualize();
}
@Test
public void testWorkingClass() {
initCode("program Test" + LF + // 1
"class X { int i; int j; }" + LF + // 2
"{" + LF + // 3
" void main() X x; { x = new X; }" + LF + // 4
"}" + LF // 5
);
parseVerifyVisualize();
}
@Test
public void testWorkingArray() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void main() int[] x; { x = new int[10]; }" + LF + // 3
"}" + LF // 4
);
parseVerifyVisualize();
}
@Test
public void testWorkingIncDec() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void main() int i; { i--; i++; }" + LF + // 3
" }" + LF // 4
);
parseVerifyVisualize();
}
@Test
public void testWorkingElseIf() {
initCode("program Test {" + LF + // 1
" void main() int i; {" + LF + // 2
" if (i > 10) i++;" + LF + // 3
" else if (i < 5) i--;" + LF + // 4
" else i += 8;" + LF + // 5
" }" + LF + // 6
"}");
parseVerifyVisualize();
}
@Test
public void testWorkingLoop() {
initCode("program Test {" + LF + // 1
" void main () int i; {" + LF + // 2
" i = 0;" + LF + // 3
" while (i < 42) {" + LF + // 4
" i++;" + LF + // 5
" }" + LF + // 6
" }" + LF + // 7
"}");
parseVerifyVisualize();
}
@Test
public void mulAssign() {
initCode("program Test {" + LF + //
" void main() int i; {" + LF + //
" i = 2;" + LF + //
" i *= 2;" + LF + //
" }" + LF + //
"}");
parseVerifyVisualize();
}
@Test
public void returnExpr() {
initCode("program Test {" + LF + //
" void main() { }" + LF + //
" int wrong1() { " + LF + //
" return 2 + 3;" + LF + //
" }" + LF + //
"}");
parseVerifyVisualize();
}
@Test
public void wrongConstDecl() {
initCode("program Test" + LF + //
" final int i = a;" + LF + //
"{ void main() { } }");
expectError(2, 17, INVALID_CONST_TYPE);
parseVerifyVisualize();
}
@Test
public void wrongDesignFollow() {
initCode("program Test {" + LF + //
" void main() int i; {" + LF + //
" i**;" + LF + //
" }" + LF + //
"}");
expectError(3, 6, INVALID_DESIGNATOR_STATEMENT);
parseVerifyVisualize();
}
@Test
public void wrongFactor() {
initCode("program Test {" + LF + //
" void main () int i; { " + LF + //
" i = i + if;" + LF + //
" }" + LF + //
"}");
expectError(3, 13, INVALID_FACTOR);
parseVerifyVisualize();
}
@Test
public void wrongRelOp() {
initCode("program Test {" + LF + //
" void main() int i; {" + LF + //
" if (i x 5);" + LF + //
" }" + LF + //
"}");
expectError(3, 11, INVALID_REL_OP);
parseVerifyVisualize();
}
@Test
public void wrongStart() {
initCode("noprogram Test { }");
expectError(1, 1, TOKEN_EXPECTED, "program");
parseVerifyVisualize();
}
@Test
public void noProgName() {
initCode("program { }");
expectError(1, 9, TOKEN_EXPECTED, "identifier");
parseVerifyVisualize();
}
@Test
public void wrongVarDecl() {
initCode("program Test " + LF + //
"int var1,,,,var2;" + LF + //
"{ void main() { } }");
expectError(2, 10, TOKEN_EXPECTED, Token.Kind.ident.label());
parseVerifyVisualize();
}
@Test
public void eofExpected() {
initCode("program Test {" + LF + //
" void main() {}" + LF + //
"}moretext");
expectError(3, 2, TOKEN_EXPECTED, "end of file");
parseVerifyVisualize();
}
@Test
public void invalidEOF1() {
initCode("program Test {" + LF + //
" void main() {");
expectError(2, 16, TOKEN_EXPECTED, "}");
parseVerifyVisualize();
}
@Test
public void invalidEOF2() {
initCode("program Test {" + LF + //
" void main() {" + LF + //
" if ()");
expectError(3, 9, INVALID_FACTOR);
parseVerifyVisualize();
}
@Test
public void invalidEOF3() {
initCode("program Test" + LF + //
" class C {" + LF + //
" int i");
expectError(3, 10, TOKEN_EXPECTED, ";");
parseVerifyVisualize();
}
@Test
public void testWorkingReadAndPrint() {
initCode("program Test {" + LF + // 1
" void main() int i; {" + LF + // 2
" read(i);" + LF +
" print(i);" + LF +
" }" + LF + //3
"}");//4
parseVerifyVisualize();
}
// index from end tests
@Test
public void wrongTildeInExpr() {
initCode("""
program Test {
void main() int i; {
i = ~1;
}
}""");
expectError(3, 9, INVALID_FACTOR);
parseVerifyVisualize();
}
@Test
public void wrongTildeCompoundAssign() {
initCode("""
program Test {
void main() int i; {
i ~= 2;
}
}""");
expectError(3, 7, INVALID_DESIGNATOR_STATEMENT);
parseVerifyVisualize();
}
@Test
public void wrongDoubleTilde() {
initCode("""
program Test {
void main() int[] a; int i; {
i = a[~~1];
}
}
""");
expectError(3, 12, INVALID_FACTOR);
parseVerifyVisualize();
}
@Test
public void constantArrayAccessFromEnd() {
initCode("""
program Test {
void main() int[] a; int i; {
a = new int[3];
a[~1] = 3;
a[~2] = 2;
a[~3] = 1;
i = a[~1];
print(i);
}
}""");
parseVerifyVisualize();
}
@Test
public void computedArrayAccessFromEnd() {
initCode("""
program Test {
void main() int[] a; int i; {
a = new int[3];
a[~(2 * 6 - 11)] = 3;
a[~(9 - 11 + 4)] = 2;
a[~(-(-3))] = 1;
i = a[~(a[0] + 2 * 6 - 11)];
print(i);
}
}""");
parseVerifyVisualize();
}
@Test
public void dynamicArrayAccessFromEnd() {
initCode("""
program Test {
void main() int[] a; int i; {
read(i);
a = new int[i];
a[~i] = 1;
a[~(i - 1)] = 2;
a[~(i - 2)] = 3;
i = a[~i];
print(i);
}
}""");
parseVerifyVisualize();
}
@Test
public void globalArrayAccessFromEnd() {
initCode("""
program Test
final int len = 3;
{
void main() int[] a; int i; {
a = new int[len];
a[~len] = 1;
a[~(len - 1)] = 2;
a[~(len - 2)] = 3;
i = a[~len];
print(i);
}
}""");
parseVerifyVisualize();
}
}

View File

@@ -0,0 +1,177 @@
package ssw.mj.test;
import org.junit.jupiter.api.Test;
import ssw.mj.Errors.Message;
import ssw.mj.test.support.BaseCompilerTestCase;
import static ssw.mj.Errors.Message.*;
public class RecoverTest extends BaseCompilerTestCase {
@Test
public void wrongGlobalDecl() {
initCode("program Test" + LF + //
" 123;" + LF + //
"{ void main() { } }");
expectError(2, 3, DECLARATION_RECOVERY);
parseVerifyVisualize();
}
@Test
public void wrongMethDecl1() {
initCode("program Test {" + LF + //
" void main() { }" + LF + //
" program wrong1() { " + LF + //
" if (1>2);" + LF + //
" }" + LF + //
"}");
expectError(3, 3, METHOD_DECL_RECOVERY);
parseVerifyVisualize();
}
@Test
public void wrongMethDecl2() {
initCode("program Test {" + LF + //
" program wrong1() { " + LF + //
" if (1>2);" + LF + //
" }" + LF + //
" void main() { }" + LF + //
" program wrong2() {" + LF + //
" if (1>2);" + LF + //
" }" + LF + //
"}");
expectError(2, 3, METHOD_DECL_RECOVERY);
expectError(6, 3, METHOD_DECL_RECOVERY);
parseVerifyVisualize();
}
@Test
public void wrongMethDecl3() {
initCode("program Test {" + LF + //
" program wrong1() { }" + LF + //
" void main() { }" + LF + //
" program wrong2() { }" + LF + //
"}");
expectError(2, 3, METHOD_DECL_RECOVERY);
expectError(4, 3, METHOD_DECL_RECOVERY);
parseVerifyVisualize();
}
@Test
public void wrongStat() {
initCode("program Test {" + LF + //
" void main() { " + LF + //
" 123;" + LF + //
" }" + LF + //
"}");
expectError(3, 5, STATEMENT_RECOVERY);
parseVerifyVisualize();
}
@Test
public void multipleErrors() {
initCode("program Test " + LF + //
" int x" + LF + //
"{" + LF + //
" void main( {" + LF + //
" if (1 x 2);" + LF + //
" }" + LF + //
"}");
expectError(3, 1, TOKEN_EXPECTED, ";");
expectError(4, 14, TOKEN_EXPECTED, ")");
expectError(5, 11, INVALID_REL_OP);
parseVerifyVisualize();
}
// ---- multiple errors & recovery
@Test
public void noRecover1() {
initCode("program Test {" + LF + //
" void main this method will never recover");
expectError(2, 13, TOKEN_EXPECTED, "(");
parseVerifyVisualize();
}
@Test
public void noRecover2() {
initCode("program Test {" + LF + //
" void main() { " + LF + //
" if this method will never recover");
expectError(3, 8, TOKEN_EXPECTED, "(");
parseVerifyVisualize();
}
@Test
public void recoverDecl1() {
initCode("program Test" + LF + //
" int i1, if" + LF + //
" in i2;" + LF + //
" final int i3 = 0;" + LF + //
"{" + LF + //
" void main() { " + LF + //
" if (i1 < i3);" + LF + //
" }" + LF + //
"}");
expectError(2, 11, TOKEN_EXPECTED, "identifier");
parseVerifyVisualize();
}
@Test
public void recoverDecl2() {
initCode("program Test" + LF + //
" int i1, if" + LF + //
" in i2;" + LF + //
" int i3;" + LF + //
"{" + LF + //
" void main() { " + LF + //
" if (i1 < i3);" + LF + //
" }" + LF + //
"}");
expectError(2, 11, TOKEN_EXPECTED, "identifier");
parseVerifyVisualize();
}
@Test
public void recoverStat() {
initCode("program Test {" + LF + //
" void main() { " + LF + //
" 567 since distance stays too small no follow up errors here;" + LF + //
" if (1 < 2);" + LF + //
" if (1 x 2);" + LF + //
" }" + LF + //
"}");
expectError(3, 5, STATEMENT_RECOVERY);
expectError(5, 11, INVALID_REL_OP);
parseVerifyVisualize();
}
@Test
public void resetErrDist() {
initCode("program Test {" + LF + //
" void main() {" + LF + //
" if () if () if();" + LF + //
" }" + LF + //
"}");
expectError(3, 9, INVALID_FACTOR);
expectError(3, 15, INVALID_FACTOR);
expectError(3, 20, INVALID_FACTOR);
parseVerifyVisualize();
}
@Test
public void illegalMethodStart() {
initCode("program Test" + LF + // 1
"{" + LF + // 2
" void foo()" + LF + // 3
" void foo(char x) { }" + LF + // 4
" void main() { }" + LF + // 5
"}" + LF // 6
);
expectError(4, 3, Message.TOKEN_EXPECTED, "{");
parseVerifyVisualize();
}
}

View File

@@ -0,0 +1,888 @@
package ssw.mj.test;
import org.junit.jupiter.api.Test;
import ssw.mj.test.support.BaseCompilerTestCase;
import static ssw.mj.Errors.Message.*;
import static ssw.mj.scanner.Token.Kind.*;
public class ScannerTest extends BaseCompilerTestCase {
private static final char invalidChar = (char) 65533;
@Test
public void oneToken() {
initScannerCode(";");
expectToken(semicolon, 1, 1);
expectToken(eof, 1, 2);
scanVerifyVisualize();
}
@Test
public void twoTokens() {
initScannerCode(";;");
expectToken(semicolon, 1, 1);
expectToken(semicolon, 1, 2);
expectToken(eof, 1, 3);
scanVerifyVisualize();
}
@Test
public void space() {
initScannerCode("; ;");
expectToken(semicolon, 1, 1);
expectToken(semicolon, 1, 4);
expectToken(eof, 1, 5);
scanVerifyVisualize();
}
@Test
public void tabulator() {
initScannerCode(";\t\t;");
expectToken(semicolon, 1, 1);
expectToken(semicolon, 1, 4);
expectToken(eof, 1, 5);
scanVerifyVisualize();
}
@Test
public void noToken() {
initScannerCode("");
expectToken(eof, 1, 1);
scanVerifyVisualize();
}
@Test
public void crLfLineSeparators() {
initScannerCode(";" + CR + LF + " ;" + CR + LF + " ; ");
expectToken(semicolon, 1, 1);
expectToken(semicolon, 2, 2);
expectToken(semicolon, 3, 3);
expectToken(eof, 3, 5);
scanVerifyVisualize();
}
@Test
public void lFLineSeparators() {
initScannerCode(";" + LF + " ;" + LF + " ; ");
expectToken(semicolon, 1, 1);
expectToken(semicolon, 2, 2);
expectToken(semicolon, 3, 3);
expectToken(eof, 3, 5);
scanVerifyVisualize();
}
@Test
public void invalidChar1() {
initScannerCode(" {" + invalidChar + "} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, invalidChar);
expectToken(rbrace, 1, 4);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void invalidChar2() {
initScannerCode(" {\0} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, '\0');
expectToken(rbrace, 1, 4);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void invalidChar3() {
initScannerCode(" {&} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, '&');
expectToken(rbrace, 1, 4);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void invalidChar4() {
initScannerCode(" {|} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, '|');
expectToken(rbrace, 1, 4);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void invalidChar5() {
initScannerCode(" {!} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, '!');
expectToken(rbrace, 1, 4);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void invalidChar6() {
initScannerCode(" {ident" + invalidChar + "} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "ident");
expectToken(none, 1, 8);
expectError(1, 8, INVALID_CHAR, invalidChar);
expectToken(rbrace, 1, 9);
expectToken(eof, 1, 11);
scanVerifyVisualize();
}
@Test
public void ident() {
initScannerCode(" {i I i1 i_ i1I_i} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "i");
expectToken(ident, 1, 5, "I");
expectToken(ident, 1, 7, "i1");
expectToken(ident, 1, 10, "i_");
expectToken(ident, 1, 13, "i1I_i");
expectToken(rbrace, 1, 18);
expectToken(eof, 1, 20);
scanVerifyVisualize();
}
@Test
public void indentSepararator() {
initScannerCode(" {i[i<i0i_i>i]i} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "i");
expectToken(lbrack, 1, 4);
expectToken(ident, 1, 5, "i");
expectToken(lss, 1, 6);
expectToken(ident, 1, 7, "i0i_i");
expectToken(gtr, 1, 12);
expectToken(ident, 1, 13, "i");
expectToken(rbrack, 1, 14);
expectToken(ident, 1, 15, "i");
expectToken(rbrace, 1, 16);
expectToken(eof, 1, 18);
scanVerifyVisualize();
}
@Test
public void singleIdent() {
initScannerCode("i");
expectToken(ident, 1, 1, "i");
expectToken(eof, 1, 2);
scanVerifyVisualize();
}
@Test
public void number() {
initScannerCode(" {123 2147483647} ");
expectToken(lbrace, 1, 2);
expectToken(number, 1, 3, 123);
expectToken(number, 1, 7, 2147483647);
expectToken(rbrace, 1, 17);
expectToken(eof, 1, 19);
scanVerifyVisualize();
}
@Test
public void singleNumber() {
initScannerCode("123");
expectToken(number, 1, 1, 123);
expectToken(eof, 1, 4);
scanVerifyVisualize();
}
@Test
public void negativeNumber() {
initScannerCode(" {-123} ");
expectToken(lbrace, 1, 2);
expectToken(minus, 1, 3);
expectToken(number, 1, 4, 123);
expectToken(rbrace, 1, 7);
expectToken(eof, 1, 9);
scanVerifyVisualize();
}
@Test
public void bigNumber() {
initScannerCode(" {2147483648} ");
expectToken(lbrace, 1, 2);
expectInvalidToken(number, 1, 3);
expectError(1, 3, BIG_NUM, "2147483648");
expectToken(rbrace, 1, 13);
expectToken(eof, 1, 15);
scanVerifyVisualize();
}
@Test
public void negativeBigNumber() {
initScannerCode(" {-2147483648} ");
expectToken(lbrace, 1, 2);
expectToken(minus, 1, 3);
expectInvalidToken(number, 1, 4);
expectError(1, 4, BIG_NUM, "2147483648");
expectToken(rbrace, 1, 14);
expectToken(eof, 1, 16);
scanVerifyVisualize();
}
@Test
public void reallyBigNumber() {
initScannerCode(" {1234567890123456789012345678901234567890} ");
expectToken(lbrace, 1, 2);
expectInvalidToken(number, 1, 3);
expectError(1, 3, BIG_NUM, "1234567890123456789012345678901234567890");
expectToken(rbrace, 1, 43);
expectToken(eof, 1, 45);
scanVerifyVisualize();
}
@Test
public void numberIdent() {
initScannerCode(" {123abc123 123break} ");
expectToken(lbrace, 1, 2);
expectToken(number, 1, 3, 123);
expectToken(ident, 1, 6, "abc123");
expectToken(number, 1, 13, 123);
expectToken(break_, 1, 16);
expectToken(rbrace, 1, 21);
expectToken(eof, 1, 23);
scanVerifyVisualize();
}
@Test
public void numbersSeparated() {
initScannerCode("123.456,789");
expectToken(number, 1, 1, 123);
expectToken(period, 1, 4);
expectToken(number, 1, 5, 456);
expectToken(comma, 1, 8);
expectToken(number, 1, 9, 789);
expectToken(eof, 1, 12);
scanVerifyVisualize();
}
@Test
public void identsSeparated() {
initScannerCode("abc.def,ghi\njkl");
expectToken(ident, 1, 1, "abc");
expectToken(period, 1, 4);
expectToken(ident, 1, 5, "def");
expectToken(comma, 1, 8);
expectToken(ident, 1, 9, "ghi");
expectToken(ident, 2, 1, "jkl");
expectToken(eof, 2, 4);
scanVerifyVisualize();
}
@Test
public void newlineBetweenIdentifiersAndTokens() {
initScannerCode("anIdentifier" + LF + "class" + LF + "anotherIdentifier");
expectToken(ident, 1, 1, "anIdentifier");
expectToken(class_, 2, 1);
expectToken(ident, 3, 1, "anotherIdentifier");
scanVerifyVisualize();
}
@Test
public void newlineAndSpacesBetweenIdentifiersAndTokens() {
initScannerCode(" anIdentifier" + LF + " class" + LF + " anotherIdentifier");
expectToken(ident, 1, 2, "anIdentifier");
expectToken(class_, 2, 3);
expectToken(ident, 3, 4, "anotherIdentifier");
scanVerifyVisualize();
}
@Test
public void charConst() {
initScannerCode(" {' ' 'A' 'z' '0' '!' '\"' '" + invalidChar + "' '\0'} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, ' ');
expectToken(charConst, 1, 7, 'A');
expectToken(charConst, 1, 11, 'z');
expectToken(charConst, 1, 15, '0');
expectToken(charConst, 1, 19, '!');
expectToken(charConst, 1, 23, '"');
expectToken(charConst, 1, 27, invalidChar);
expectToken(charConst, 1, 31, '\0');
expectToken(rbrace, 1, 34);
expectToken(eof, 1, 36);
scanVerifyVisualize();
}
@Test
public void singleCharConst() {
initScannerCode("'x'");
expectToken(charConst, 1, 1, 'x');
expectToken(eof, 1, 4);
scanVerifyVisualize();
}
@Test
public void escapeCharConst() {
initScannerCode(" {'\\n' '\\r' '\\\\' '\\''} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\n');
expectToken(charConst, 1, 8, '\r');
expectToken(charConst, 1, 13, '\\');
expectToken(charConst, 1, 18, '\'');
expectToken(rbrace, 1, 22);
expectToken(eof, 1, 24);
scanVerifyVisualize();
}
@Test
public void singleEscapeCharConst() {
initScannerCode("'\\n'");
expectToken(charConst, 1, 1, '\n');
expectToken(eof, 1, 5);
scanVerifyVisualize();
}
@Test
public void emptyCharConst() {
initScannerCode(" {''} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, EMPTY_CHARCONST);
expectToken(rbrace, 1, 5);
expectToken(eof, 1, 7);
scanVerifyVisualize();
}
@Test
public void unclosedCharConst() {
initScannerCode(" {'a} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, MISSING_QUOTE);
expectToken(rbrace, 1, 5);
expectToken(eof, 1, 7);
scanVerifyVisualize();
}
@Test
public void emptyAndUnclosedCharConst() {
initScannerCode(" ''' ");
expectToken(charConst, 1, 2, '\0');
expectError(1, 2, EMPTY_CHARCONST);
expectToken(charConst, 1, 4, '\0');
expectError(1, 4, MISSING_QUOTE);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void unclosedEscapeCharConst() {
initScannerCode(" {'\\r} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, MISSING_QUOTE);
expectToken(rbrace, 1, 6);
expectToken(eof, 1, 8);
scanVerifyVisualize();
}
@Test
public void unclosedBackslashCharConst() {
initScannerCode(" {'\\'} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, MISSING_QUOTE);
expectToken(rbrace, 1, 6);
expectToken(eof, 1, 8);
scanVerifyVisualize();
}
@Test
public void invalidEscapeCharConst() {
initScannerCode(" {'\\a'} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, UNDEFINED_ESCAPE, 'a');
expectToken(rbrace, 1, 7);
expectToken(eof, 1, 9);
scanVerifyVisualize();
}
@Test
public void invalidEscapeCharMissingQuote() {
initScannerCode(" '\\a ");
expectToken(charConst, 1, 2, '\0');
expectError(1, 2, UNDEFINED_ESCAPE, 'a');
expectError(1, 2, MISSING_QUOTE);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void fileEndCharConst() {
initScannerCode(" {'");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, EOF_IN_CHAR);
expectToken(eof, 1, 4);
scanVerifyVisualize();
}
@Test
public void lineEndCharConst() {
initScannerCode(" {'" + LF + "'a'} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, ILLEGAL_LINE_END);
expectToken(charConst, 2, 1, 'a');
expectToken(rbrace, 2, 4);
expectToken(eof, 2, 6);
scanVerifyVisualize();
}
@Test
public void lineEndWithCRCharConst() {
initScannerCode(" {'" + CR + LF + "'a'} ");
expectToken(lbrace, 1, 2);
expectToken(charConst, 1, 3, '\0');
expectError(1, 3, ILLEGAL_LINE_END);
expectToken(charConst, 2, 1, 'a');
expectToken(rbrace, 2, 4);
expectToken(eof, 2, 6);
scanVerifyVisualize();
}
@Test
public void keyword1() {
initScannerCode(" { if } ");
expectToken(lbrace, 1, 2);
expectToken(if_, 1, 4);
expectToken(rbrace, 1, 7);
expectToken(eof, 1, 9);
scanVerifyVisualize();
}
@Test
public void keyword2() {
initScannerCode(" {if} ");
expectToken(lbrace, 1, 2);
expectToken(if_, 1, 3);
expectToken(rbrace, 1, 5);
expectToken(eof, 1, 7);
scanVerifyVisualize();
}
@Test
public void singleKeyword() {
initScannerCode("if");
expectToken(if_, 1, 1);
expectToken(eof, 1, 3);
scanVerifyVisualize();
}
@Test
public void keyword3() {
initScannerCode(" {for_} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "for_");
expectToken(rbrace, 1, 7);
expectToken(eof, 1, 9);
scanVerifyVisualize();
}
@Test
public void keyword4() {
initScannerCode(" {&if} ");
expectToken(lbrace, 1, 2);
expectToken(none, 1, 3);
expectError(1, 3, INVALID_CHAR, '&');
expectToken(if_, 1, 4);
expectToken(rbrace, 1, 6);
expectToken(eof, 1, 8);
scanVerifyVisualize();
}
@Test
public void caseSensitive1() {
initScannerCode(" {For} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "For");
expectToken(rbrace, 1, 6);
expectToken(eof, 1, 8);
scanVerifyVisualize();
}
@Test
public void caseSensitive2() {
initScannerCode(" {FOR} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "FOR");
expectToken(rbrace, 1, 6);
expectToken(eof, 1, 8);
scanVerifyVisualize();
}
@Test
public void simpleSingleLineComment() {
initScannerCode(" {/* Simple / single * line comment. */} ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 1, 40);
expectToken(eof, 1, 42);
scanVerifyVisualize();
}
@Test
public void simpleMultiLineComment() {
initScannerCode(" {" + LF + " /* Simple " + LF + " / multi * line " + LF //
+ " comment. */ " + LF + " } ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 5, 2);
expectToken(eof, 5, 4);
scanVerifyVisualize();
}
@Test
public void nestedSingleLineComment2() {
initScannerCode(" {/*//*///****/**/*/} ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 1, 21);
expectToken(eof, 1, 23);
scanVerifyVisualize();
}
@Test
public void nestedSingleLineComment() {
initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/} ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 1, 62);
expectToken(eof, 1, 64);
scanVerifyVisualize();
}
@Test
public void nestedMultiLineComment() {
initScannerCode(" {" + LF + " /* This / is * a " + LF + " /* nested " + LF //
+ " /* multi line */" + LF + " comment. " + LF + " */" + LF //
+ " */ " + LF + " } ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 8, 2);
expectToken(eof, 8, 4);
scanVerifyVisualize();
}
@Test
public void nestedMultiLineComment2() {
initScannerCode(" {" + LF + " /* This / is * a " + LF + " /* nested " + LF //
+ " /* multi /*/* double nestet */*/ line */" + LF + " comment. " + LF + " */" + LF //
+ " */ " + LF + " } ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 8, 2);
expectToken(eof, 8, 4);
scanVerifyVisualize();
}
@Test
public void commentAtEnd1() {
initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/ ");
expectToken(lbrace, 1, 2);
expectToken(eof, 1, 63);
scanVerifyVisualize();
}
@Test
public void commentAtEnd2() {
initScannerCode(" {/* This / is * a /* nested /* single line */ comment. */*/");
expectToken(lbrace, 1, 2);
expectToken(eof, 1, 62);
scanVerifyVisualize();
}
@Test
public void unclosedComment() {
initScannerCode(" {/* This / is * a nested unclosed comment. } ");
expectToken(lbrace, 1, 2);
expectError(1, 3, EOF_IN_COMMENT);
expectToken(eof, 1, 47);
scanVerifyVisualize();
}
@Test
public void unclosedComment2() {
initScannerCode(" {/*/");
expectToken(lbrace, 1, 2);
expectError(1, 3, EOF_IN_COMMENT);
expectToken(eof, 1, 6);
scanVerifyVisualize();
}
@Test
public void nestedUnclosedComment() {
initScannerCode(" {/* This / is * a /* nested /* unclosed comment. */} ");
expectToken(lbrace, 1, 2);
expectError(1, 3, EOF_IN_COMMENT);
expectToken(eof, 1, 55);
scanVerifyVisualize();
}
@Test
public void nestedUnclosedComment2() {
initScannerCode(" {/* This / is * a nested unclosed /* comment. } */");
expectToken(lbrace, 1, 2);
expectError(1, 3, EOF_IN_COMMENT);
expectToken(eof, 1, 52);
scanVerifyVisualize();
}
@Test
public void noLineComment() {
initScannerCode(" {This is // no comment} ");
expectToken(lbrace, 1, 2);
expectToken(ident, 1, 3, "This");
expectToken(ident, 1, 8, "is");
expectToken(slash, 1, 11);
expectToken(slash, 1, 12);
expectToken(ident, 1, 14, "no");
expectToken(ident, 1, 17, "comment");
expectToken(rbrace, 1, 24);
expectToken(eof, 1, 26);
scanVerifyVisualize();
}
@Test
public void multipleComments() {
initScannerCode(" {/*a*/ /*b*/ /*c*/ } ");
expectToken(lbrace, 1, 2);
expectToken(rbrace, 1, 23);
expectToken(eof, 1, 25);
scanVerifyVisualize();
}
// index from end tests
@Test
public void negativeIndexFromEnd() {
initScannerCode("~-7");
expectToken(tilde, 1, 1);
expectToken(minus, 1, 2);
expectToken(number, 1, 3, 7);
expectToken(eof, 1, 4);
scanVerifyVisualize();
}
@Test
public void singleTildeChar() {
initScannerCode("'~'");
expectToken(charConst, 1, 1, '~');
expectToken(eof, 1, 4);
scanVerifyVisualize();
}
@Test
public void commentInIndexFromEnd() {
initScannerCode("~/*comment*/7");
expectToken(tilde, 1, 1);
expectToken(number, 1, 13, 7);
expectToken(eof, 1,14);
scanVerifyVisualize();
}
@Test
public void doubleTilde() {
initScannerCode("~~");
expectToken(tilde, 1, 1);
expectToken(tilde, 1, 2);
expectToken(eof, 1,3);
scanVerifyVisualize();
}
@Test
public void allTokens() {
initScannerCode("anIdentifier 123 'c'" + LF //
+ "+ - * / % == != < <= > >= && || = += -= *= /= %= ++ -- ; , . ( ) [ ] { }" + LF //
+ "break class else final if new print program read return void while ~" + LF);
expectToken(ident, 1, 1, "anIdentifier");
expectToken(number, 1, 14, 123);
expectToken(charConst, 1, 18, 'c');
expectToken(plus, 2, 1);
expectToken(minus, 2, 3);
expectToken(times, 2, 5);
expectToken(slash, 2, 7);
expectToken(rem, 2, 9);
expectToken(eql, 2, 11);
expectToken(neq, 2, 14);
expectToken(lss, 2, 17);
expectToken(leq, 2, 19);
expectToken(gtr, 2, 22);
expectToken(geq, 2, 24);
expectToken(and, 2, 27);
expectToken(or, 2, 30);
expectToken(assign, 2, 33);
expectToken(plusas, 2, 35);
expectToken(minusas, 2, 38);
expectToken(timesas, 2, 41);
expectToken(slashas, 2, 44);
expectToken(remas, 2, 47);
expectToken(pplus, 2, 50);
expectToken(mminus, 2, 53);
expectToken(semicolon, 2, 56);
expectToken(comma, 2, 58);
expectToken(period, 2, 60);
expectToken(lpar, 2, 62);
expectToken(rpar, 2, 64);
expectToken(lbrack, 2, 66);
expectToken(rbrack, 2, 68);
expectToken(lbrace, 2, 70);
expectToken(rbrace, 2, 72);
expectToken(break_, 3, 1);
expectToken(class_, 3, 7);
expectToken(else_, 3, 13);
expectToken(final_, 3, 18);
expectToken(if_, 3, 24);
expectToken(new_, 3, 27);
expectToken(print, 3, 31);
expectToken(program, 3, 37);
expectToken(read, 3, 45);
expectToken(return_, 3, 50);
expectToken(void_, 3, 57);
expectToken(while_, 3, 62);
expectToken(tilde, 3, 68);
expectToken(eof, 4, 1);
scanVerifyVisualize();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,401 @@
package ssw.mj.test.support;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;
import ssw.mj.Errors;
import ssw.mj.Interpreter;
import ssw.mj.Visualizer;
import ssw.mj.codegen.Decoder;
import ssw.mj.impl.Parser;
import ssw.mj.impl.Scanner;
import ssw.mj.scanner.Token;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.StringReader;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Base class for test cases with utility methods used by all tests.
*/
@Timeout(value = Configuration.TIMEOUT, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
public abstract class BaseCompilerTestCase {
public static final String CR = "\r";
public static final String LF = "\n";
private List<String> expectedErrors;
private List<String> expectedTokens;
private List<Token> expectedTokensFull;
private List<String> expectedSymTab;
private List<String> expectedRuntimeErrors;
private String source;
private Scanner scanner;
protected Parser parser;
private String callingClassAndMethod;
private final List<String> runInputs = new ArrayList<>();
private final List<String> expectedOutputs = new ArrayList<>();
@BeforeEach
public void setUp() {
// initialize expected compiler output
expectedErrors = new ArrayList<>();
expectedTokens = new ArrayList<>();
expectedTokensFull = new ArrayList<>();
expectedSymTab = new ArrayList<>();
expectedRuntimeErrors = new ArrayList<>();
if (Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES) {
// print header for console output
System.out.println("--------------------------------------------------");
}
}
protected void initCode(String code) {
initScannerCode(code);
parser = new Parser(scanner);
}
protected void initFile(String filename) {
initScannerFile(filename);
parser = new Parser(scanner);
}
protected void initScannerCode(String code) {
source = code;
scanner = new Scanner(new StringReader(code));
}
protected void initScannerFile(String filename) {
try {
ClassLoader classLoader = getClass().getClassLoader();
URL resource = classLoader.getResource(filename);
if (resource == null) {
throw new RuntimeException("resource %s not found".formatted(filename));
}
String urlAsStr = resource.getFile();
// replaces %20 Urlencoding with " " (blank space), as e.g. Linux cannot handle url paths
String path = URLDecoder.decode(urlAsStr, StandardCharsets.UTF_8);
File file = new File(path);
scanner = new Scanner(new FileReader(file));
} catch (FileNotFoundException e) {
throw new RuntimeException(e.getMessage());
}
}
private List<String> splitString(String s) {
StringTokenizer st = new StringTokenizer(s, "\n");
List<String> result = new ArrayList<>();
while (st.hasMoreTokens()) {
result.add(st.nextToken());
}
return result;
}
private void print(String title, List<String> expected, List<String> actual) {
if (expected.isEmpty() && actual.isEmpty()) {
return;
}
System.out.format("%s - %s\n", callingClassAndMethod, title);
if (Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES || !expected.equals(actual)) {
System.out.format(" %-60s %s\n", "expected", "actual");
int lines = Math.max(expected.size(), actual.size());
for (int i = 0; i < lines; i++) {
String expectedLine = (i < expected.size() ? expected.get(i) : "");
String actualLine = (i < actual.size() ? actual.get(i) : "");
System.out.format("%s %-60s %s\n", (expectedLine.equals(actualLine) ? " " : "x"), expectedLine,
actualLine);
}
} else {
if (expected.equals(actual)) {
System.out.println(" correct (exact comparison hidden, enable via Configuration.ALSO_PRINT_SUCCESSFUL_TESTCASES)");
}
}
}
private void addRun(String input, String output, String error) {
runInputs.add(input);
expectedOutputs.add(output);
expectedRuntimeErrors.add(error);
}
protected void addExpectedRun(String output) {
addExpectedRun("", output);
}
protected void addExpectedRun(String input, String output) {
addRun(input, output, "");
}
protected void addFailingRun(String error) {
addFailingRun("", error);
}
protected void addFailingRun(String input, String error) {
addRun(input, "", error);
}
/**
* Scans the given code and checks the scanned tokens against the expected ones.
* Also checks that expected errors occur.
* Finally, the method creates a visualization of the scanned tokens if the test was run
* with @link ssw.mj.TracingClassLoader as system classloader.
*/
protected void scanVerifyVisualize() {
callingClassAndMethod = getCallingClassAndMethod(1);
List<Token> actualTokens = new ArrayList<>();
// scan only the expected number of tokens to prevent endless loops
for (int i = 0; i < getExpectedTokens().size(); i++) {
actualTokens.add(scanner.next());
}
List<String> actualTokenStrings = actualTokens.stream().map(Token::toString).toList();
Visualizer.createScannerVisualization(source, actualTokens, getExpectedTokensFull(), false);
printErrors();
printTokens(actualTokenStrings);
verifyErrors();
verifyTokens(actualTokenStrings);
}
/**
* Parses the given code and checks it for expected errors, matching sym tab and matching byte code.
* Then it executed the interpreter for all given inputs.
* Finally, the method creates a visualization of the parse tree if the test was run
* with @link ssw.mj.TracingClassLoader as system classloader.
*/
protected void parseVerifyVisualize() {
callingClassAndMethod = getCallingClassAndMethod(1);
try {
parser.parse();
assertEquals(Token.Kind.eof, scanner.next().kind, "Complete input should be scanned");
} catch (Errors.PanicMode error) {
// Ignore, nothing to do
}
printErrors();
printSymTab();
verifyErrors();
verifySymTab();
if (ByteCodeTestSupport.GENERATE_REFERENCE_BYTE_CODE && expectedErrors.isEmpty()) {
ByteCodeTestSupport.generateReferenceByteCode(callingClassAndMethod, parser);
} else {
printAndVerifyByteCode(callingClassAndMethod);
}
for (int i = 0; i < runInputs.size(); i++) {
run(i);
}
Visualizer.createParserVisualization(source, false);
}
private static String getCallingClassAndMethod(int up) {
// [0] getStackTrace -> [1] getCallingMethodName -> [2] caller of getCallingMethodName -> [3] ...
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
StackTraceElement e = stacktrace[2 + up];
String fullyQualifiedClassName = e.getClassName();
String className = fullyQualifiedClassName.substring(Math.max(fullyQualifiedClassName.lastIndexOf(".") + 1, 0));
return className + "." + e.getMethodName() + "()";
}
private void run(int i) {
Interpreter.BufferIO io = new Interpreter.BufferIO(runInputs.get(i));
Interpreter interpreter = new Interpreter(
parser.code.buf,
parser.code.mainpc,
parser.code.dataSize,
io,
Configuration.PRINT_INTERPRETER_DEBUG_OUTPUT);
try {
interpreter.run();
} catch (IllegalStateException e) {
verifyRuntimeError(i, e);
}
String output = io.getOutput();
verifyOutput(i, output);
}
private void printErrors() {
print("Errors", expectedErrors, getActualErrors());
}
private void printTokens(List<String> actualTokens) {
print("Tokens", getExpectedTokens(), actualTokens);
}
private void printSymTab() {
if (!expectedSymTab.isEmpty()) {
print("Symbol Table", getExpectedSymTab(), getActualSymTab());
}
}
private void verifyErrors() {
assertEquals(expectedErrors, getActualErrors(), "Errors");
}
private void verifyTokens(List<String> actualTokens) {
assertEquals(getExpectedTokens(), actualTokens, "Tokens");
assertTrue(scanner.next().toString().contains("end of file"), "Complete Input Scanned");
}
private void verifySymTab() {
if (!expectedSymTab.isEmpty()) {
assertEquals(getExpectedSymTab(), getActualSymTab(), "Symbol Table");
}
}
private void printAndVerifyByteCode(String callingClassAndMethod) {
if (ByteCodeTestSupport.BYTE_CODES.containsKey(callingClassAndMethod)) {
List<String> possibleByteCodes = ByteCodeTestSupport.BYTE_CODES.get(callingClassAndMethod);
if (possibleByteCodes.size() == 1) {
List<String> expected = getExpectedByteCodeLines(possibleByteCodes.get(0));
print("Bytecode", expected, getActualByteCodeLines());
// Verify that the bytecode is correct
assertEquals(expected, getActualByteCodeLines(), "Byte Code");
} else {
int matchIdx = -1;
for (int i = 0; i < possibleByteCodes.size(); i++) {
List<String> expected = getExpectedByteCodeLines(possibleByteCodes.get(i));
if (expected.equals(getActualByteCodeLines())) {
matchIdx = i;
break;
}
}
if (matchIdx < 0) {
// No bytecode matched
// print all
for (int i = 0; i < possibleByteCodes.size(); i++) {
List<String> expected = getExpectedByteCodeLines(possibleByteCodes.get(i));
print("Possible Bytecode %d".formatted(i + 1), expected, getActualByteCodeLines());
}
// fail assert on first
assertEquals(getExpectedByteCodeLines(possibleByteCodes.get(0)), getActualByteCodeLines(), "Byte Code");
} else {
// bytecode at idx matchIdx correctly generated
// print working bytecode
print("Bytecode", getExpectedByteCodeLines(possibleByteCodes.get(matchIdx)), getActualByteCodeLines());
// assert not really necessary since we already know we matched successfully
assertEquals(getExpectedByteCodeLines(possibleByteCodes.get(matchIdx)), getActualByteCodeLines(), "Byte Code");
}
}
}
}
private void verifyOutput(int runIdx, String actualOutput) {
assertEquals(expectedOutputs.get(runIdx), actualOutput, "Unexpected result when input is \"" + runInputs.get(runIdx) + "\": ");
}
private void verifyRuntimeError(int runIdx, IllegalStateException e) {
assertEquals(expectedRuntimeErrors.get(runIdx), e.getMessage(), "Unexpected runtime error message when input is \"" + runInputs.get(runIdx) + "\": ");
}
private List<String> getExpectedByteCodeLines(String bytecode) {
return Arrays.stream(bytecode.split("\n")).toList();
}
private List<String> getActualByteCodeLines() {
return Arrays.stream(new Decoder().decode(parser.code).split("\n")).toList();
}
private List<String> getActualErrors() {
return splitString(scanner.errors.dump());
}
private List<String> getExpectedTokens() {
return expectedTokens;
}
private List<Token> getExpectedTokensFull() {
return expectedTokensFull;
}
private List<String> getExpectedSymTab() {
return expectedSymTab;
}
private List<String> getActualSymTab() {
return splitString(SymTabDumper.dump(parser.tab));
}
protected void expectError(int line, int col, Errors.Message msg, Object... msgParams) {
expectedErrors.add("-- line " + line + " col " + col + ": " + msg.format(msgParams));
}
protected void expectToken(Token.Kind kind, int line, int col) {
expectedTokens.add("line " + line + ", col " + col + ", kind " + kind);
expectedTokensFull.add(new Token(kind, line, col));
}
protected void expectToken(Token.Kind kind, int line, int col, String val) {
expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + val);
Token token = new Token(kind, line, col);
token.val = val;
expectedTokensFull.add(token);
}
protected void expectToken(Token.Kind kind, int line, int col, int val) {
expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + val + ", numVal " + val);
Token token = new Token(kind, line, col);
token.val = String.valueOf(val);
token.numVal = val;
expectedTokensFull.add(token);
}
protected void expectToken(Token.Kind kind, int line, int col, char ch) {
expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val " + ch + ", numVal " + (int) ch);
Token token = new Token(kind, line, col);
token.val = String.valueOf(ch);
token.numVal = ch;
expectedTokensFull.add(token);
}
protected void expectInvalidToken(Token.Kind kind, int line, int col) {
expectedTokens.add("line " + line + ", col " + col + ", kind " + kind + ", val null, numVal 0");
Token token = new Token(kind, line, col);
token.val = null;
token.numVal = 0;
expectedTokensFull.add(token);
}
protected void expectSymTab(String line) {
expectedSymTab.add(line);
}
protected void expectSymTabUniverse() {
// first part of the symbol table (universe) that is equal for all
// programs
expectSymTab("-- begin scope (0 variables) --");
expectSymTab("Type int: int");
expectSymTab("Type char: char");
expectSymTab("Constant: class (0 fields) null = 0");
expectSymTab("Method: char chr (1 locals, 1 parameters)");
expectSymTab(" Local Variable 0: int i");
expectSymTab("Method: int ord (1 locals, 1 parameters)");
expectSymTab(" Local Variable 0: char ch");
expectSymTab("Method: int len (1 locals, 1 parameters)");
expectSymTab(" Local Variable 0: void[] arr");
}
}

View File

@@ -0,0 +1,103 @@
package ssw.mj.test.support;
import ssw.mj.codegen.Decoder;
import ssw.mj.impl.Parser;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;
public class ByteCodeTestSupport {
/**
* This flag is used by the lecturers to generate the reference solutions for
* the bytecodes generated during code generation.
* Students should not change this flag, it should stay false for the whole course.
*/
public static final boolean GENERATE_REFERENCE_BYTE_CODE = false;
// For each test, 0 to n correct byte codes can be added to bytecodes.txt
// If one of these codes is generated by the Parser, the test does not fail.
// This way, we can provide multiple correct solutions for the same test case.
//
// The keys of this map are in the format "TestClass.TestMethodName()"
public static final HashMap<String, List<String>> BYTE_CODES = new HashMap<>();
static {
File bytecodesFile = getBytecodesFile();
if (bytecodesFile.exists()) {
String[] lineArr;
try (Stream<String> lines = Files.lines(bytecodesFile.toPath())) {
lineArr = lines.toArray(String[]::new);
} catch (IOException e) {
throw new RuntimeException(e);
}
String currentlyReadClassAndMethod = null;
StringBuilder currentReadBytecode = null;
for (String line : lineArr) {
if (line.isBlank()) {
continue;
}
if (line.startsWith("#")) {
if (currentlyReadClassAndMethod != null) {
if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) {
BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>());
}
BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString());
}
currentlyReadClassAndMethod = line.substring(1);
currentReadBytecode = new StringBuilder();
} else {
currentReadBytecode.append(line).append("\n");
}
}
if (currentlyReadClassAndMethod != null) {
if (!BYTE_CODES.containsKey(currentlyReadClassAndMethod)) {
BYTE_CODES.put(currentlyReadClassAndMethod, new ArrayList<>());
}
BYTE_CODES.get(currentlyReadClassAndMethod).add(currentReadBytecode.toString());
}
}
}
public static File getBytecodesFile() {
String filename = "bytecodes.txt";
ClassLoader classLoader = BaseCompilerTestCase.class.getClassLoader();
URL resource = classLoader.getResource(filename);
if (resource == null) {
throw new RuntimeException("resource %s not found".formatted(filename));
}
String urlAsStr = resource.getFile();
// replaces %20 Urlencoding with " " (blank space), as e.g. Linux cannot handle url paths
String path = URLDecoder.decode(urlAsStr, StandardCharsets.UTF_8);
return new File(path);
}
public static void generateReferenceByteCode(String classAndMethod, Parser parser) {
// Generate and store bytecode for correct test programs
// Output is in the form:
// #TestClass.TestMethodName()
// ... output from Decoder.decode() ...
File bytecodesFile = ByteCodeTestSupport.getBytecodesFile();
try (BufferedWriter bw = Files.newBufferedWriter(bytecodesFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
String bytecode = new Decoder().decode(parser.code);
bw.write("#");
bw.write(classAndMethod);
bw.write("\n");
bw.write(bytecode);
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,27 @@
package ssw.mj.test.support;
public class Configuration {
/**
* set to true to print expected and actual values of all testcases,
* not only failing ones (prints expected errors, tokens, symbol table, code)
*/
public static final boolean ALSO_PRINT_SUCCESSFUL_TESTCASES = Boolean.getBoolean("microjava.testcaseOutput");
/**
* Set to true to print debug information of the interpreter. Equal to
* "-debug" on the command line. <br>
* Remark:<br>
* This is a lot of output, some test cases might time out, e.g.
* CodeGenerationTest.fib
*/
public static final boolean PRINT_INTERPRETER_DEBUG_OUTPUT = Boolean.getBoolean("microjava.interpreterOutput");
/**
* Determines the timeout after which a test case should fail automatically.
* Default: 10 seconds. The default should work for all test cases
* on most machines.<br>
* <em>Attention</em>: For most computers it is likely that there is an
* endless loop in the MicroJava compiler if a test fails for a timeout.
*/
public static final long TIMEOUT = 10;
}

View File

@@ -0,0 +1,119 @@
package ssw.mj.test.support;
import ssw.mj.impl.Tab;
import ssw.mj.symtab.Obj;
import ssw.mj.symtab.Scope;
import ssw.mj.symtab.Struct;
import java.util.Collection;
public class SymTabDumper {
public static String dump(Tab tab) {
StringBuilder sb = new StringBuilder();
if (tab.curScope != null) {
dump(tab.curScope, sb);
}
return sb.toString();
}
private static void dump(Scope scope, StringBuilder sb) {
sb.append("-- begin scope (").append(scope.nVars()).append(" variables) --\n");
if (!scope.locals().isEmpty()) {
dump(scope.locals().values(), sb, "");
}
if (scope.outer() != null) {
sb.append("\n");
dump(scope.outer(), sb);
}
}
private static void dump(Collection<Obj> objects, StringBuilder sb, String indent) {
for (Obj obj : objects) {
dump(obj, sb, indent);
}
}
private static void dump(Obj obj, StringBuilder sb, String indent) {
sb.append(indent);
switch (obj.kind) {
case Con -> dumpCon(obj, sb, indent);
case Var -> dumpVar(obj, sb, indent);
case Type -> dumpType(obj, sb, indent);
case Meth -> dumpMethod(obj, sb, indent);
case Prog -> dumpProgram(obj, sb);
}
if (obj.locals != null) {
sb.append("\n");
dump(obj.locals.values(), sb, indent + " ");
}
sb.append("\n");
}
private static void dumpCon(Obj obj, StringBuilder sb, String indent) {
sb.append("Constant: ");
if (obj.type != null) {
dump(obj.type, sb, indent, false);
}
sb.append(" ").append(obj.name).append(" = ");
if (obj.type == Tab.charType) {
sb.append("'").append((char) obj.val).append("'");
} else {
sb.append(obj.val);
}
}
private static void dumpVar(Obj obj, StringBuilder sb, String indent) {
if (obj.level == 0) {
sb.append("Global Variable ");
} else {
sb.append("Local Variable ");
}
sb.append(obj.adr).append(": ");
if (obj.type != null) {
dump(obj.type, sb, indent, false);
}
sb.append(" ").append(obj.name);
}
private static void dumpType(Obj type, StringBuilder sb, String indent) {
sb.append("Type ").append(type.name).append(": ");
if (type.type != null) {
dump(type.type, sb, indent + " ", true);
}
}
private static void dumpMethod(Obj meth, StringBuilder sb, String indent) {
sb.append("Method: ");
if (meth.type != null) {
dump(meth.type, sb, indent, false);
}
sb.append(" ").append(meth.name).append(" (").append(meth.locals.size()).append(" locals, ").append(meth.nPars).append(" parameters").append(")");
}
private static void dumpProgram(Obj obj, StringBuilder sb) {
sb.append("Program ").append(obj.name).append(":");
}
private static void dump(Struct struct, StringBuilder sb, String indent, boolean dumpFields) {
switch (struct.kind) {
case None -> sb.append("void");
case Int -> sb.append("int");
case Char -> sb.append("char");
case Arr -> {
if (struct.elemType != null) {
dump(struct.elemType, sb, indent, dumpFields);
}
sb.append("[]");
}
case Class -> {
sb.append("class (").append(struct.nrFields()).append(" fields)");
if (dumpFields && struct.fields != null) {
sb.append("\n");
dump(struct.fields.values(), sb, indent);
}
}
}
}
}

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1760524057,
"narHash": "sha256-EVAqOteLBFmd7pKkb0+FIUyzTF61VKi7YmvP1tw4nEw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "544961dfcce86422ba200ed9a0b00dd4b1486ec5",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

28
flake.nix Normal file
View File

@@ -0,0 +1,28 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs =
{
nixpkgs,
...
}:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config = {
allowUnfree = true;
};
};
in
{
devShells."${system}".default = pkgs.mkShell {
packages = [
pkgs.jetbrains.idea-ultimate
pkgs.jdk21
];
};
};
}

Binary file not shown.

BIN
lib/gson-2.10.1.jar Normal file

Binary file not shown.

BIN
lib/javassist.jar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
lib/opentest4j-1.3.0.jar Normal file

Binary file not shown.