initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
out
|
||||||
Generated
+10
@@ -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
|
||||||
Generated
+104
@@ -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>
|
||||||
Generated
+10
@@ -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>
|
||||||
Generated
+5
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
Generated
+9
@@ -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>
|
||||||
+8
@@ -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>
|
||||||
Generated
+17
@@ -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>
|
||||||
Generated
+5
@@ -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>
|
||||||
Generated
+8
@@ -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>
|
||||||
Generated
+19
@@ -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>
|
||||||
Generated
+16
@@ -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>
|
||||||
Generated
+17
@@ -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>
|
||||||
Generated
+18
@@ -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>
|
||||||
Generated
+19
@@ -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>
|
||||||
Generated
+13
@@ -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>
|
||||||
@@ -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>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -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.
@@ -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.
@@ -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
|
||||||
@@ -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.
@@ -0,0 +1,11 @@
|
|||||||
|
program Test
|
||||||
|
{
|
||||||
|
|
||||||
|
int Trap()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void main () {
|
||||||
|
print(Trap());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package ssw.mj;
|
||||||
|
|
||||||
|
import ssw.mj.impl.Parser;
|
||||||
|
import ssw.mj.impl.Scanner;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>Compiler</code> is the driver for the MicroJava-Compiler.
|
||||||
|
* <p>
|
||||||
|
* Execute<br>
|
||||||
|
* <code>java ssw.mj.Compiler <<i>MJ-Source-Filename</i>></code><br>
|
||||||
|
* to start compilation.
|
||||||
|
*/
|
||||||
|
public class Compiler {
|
||||||
|
|
||||||
|
private static String objFileName(String s) {
|
||||||
|
int i = s.lastIndexOf('.');
|
||||||
|
if (i < 0) {
|
||||||
|
return s + ".obj";
|
||||||
|
}
|
||||||
|
return s.substring(0, i) + ".obj";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// --- get the filename
|
||||||
|
if (args.length != 1) {
|
||||||
|
System.out.println("usage: java Compiler filename.mj");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String inFilename = args[0];
|
||||||
|
String outFilename = objFileName(inFilename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Scanner scanner = new Scanner(new BufferedReader(new FileReader(inFilename)));
|
||||||
|
|
||||||
|
System.out.println("-----------------------------------");
|
||||||
|
System.out.println("Parsing file " + inFilename);
|
||||||
|
|
||||||
|
Parser parser = new Parser(scanner);
|
||||||
|
parser.parse();
|
||||||
|
if (scanner.errors.numErrors() == 0) {
|
||||||
|
parser.code.write(new BufferedOutputStream(new FileOutputStream(outFilename)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scanner.errors.numErrors() > 0) {
|
||||||
|
System.out.println(scanner.errors.dump());
|
||||||
|
System.out.println(scanner.errors.numErrors() + " errors.");
|
||||||
|
} else {
|
||||||
|
System.out.println("No errors.");
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
System.out.println("I/O Error: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// =================================================
|
||||||
|
}
|
||||||
@@ -0,0 +1,561 @@
|
|||||||
|
package ssw.mj.impl;
|
||||||
|
|
||||||
|
import javassist.bytecode.analysis.ControlFlow;
|
||||||
|
import javassist.compiler.ast.MethodDecl;
|
||||||
|
import ssw.mj.Errors;
|
||||||
|
import ssw.mj.Errors.Message;
|
||||||
|
import ssw.mj.scanner.Token;
|
||||||
|
|
||||||
|
import javax.xml.stream.FactoryConfigurationError;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
import static ssw.mj.Errors.Message.*;
|
||||||
|
import static ssw.mj.scanner.Token.Kind.*;
|
||||||
|
|
||||||
|
public final class Parser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of global variables per program
|
||||||
|
*/
|
||||||
|
private static final int MAX_GLOBALS = 32767;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of fields per class
|
||||||
|
*/
|
||||||
|
private static final int MAX_FIELDS = 32767;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of local variables per method
|
||||||
|
*/
|
||||||
|
private static final int MAX_LOCALS = 127;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last recognized token;
|
||||||
|
*/
|
||||||
|
private Token t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookahead token (not recognized).)
|
||||||
|
*/
|
||||||
|
private Token la;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut to kind attribute of lookahead token (la).
|
||||||
|
*/
|
||||||
|
private Token.Kind sym;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According scanner
|
||||||
|
*/
|
||||||
|
public final Scanner scanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According code buffer
|
||||||
|
*/
|
||||||
|
public final Code code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According symbol table
|
||||||
|
*/
|
||||||
|
public final Tab tab;
|
||||||
|
|
||||||
|
private static int MIN_ERROR_DIST = 3;
|
||||||
|
|
||||||
|
private int errorDist = MIN_ERROR_DIST;
|
||||||
|
|
||||||
|
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;
|
||||||
|
errorDist++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
|
||||||
|
if (errorDist >= MIN_ERROR_DIST) {
|
||||||
|
scanner.errors.error(la.line, la.col, msg, msgParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorDist = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the analysis.
|
||||||
|
*/
|
||||||
|
public void parse() {
|
||||||
|
scan(); // scan first symbol, initializes look-ahead
|
||||||
|
Program(); // start analysis
|
||||||
|
check(eof);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ===============================================
|
||||||
|
// TODO Exercise UE-P-2: Implementation of parser
|
||||||
|
// TODO Exercise UE-P-3: Error recovery methods
|
||||||
|
// TODO Exercise UE-P-4: Symbol table handling
|
||||||
|
// TODO Exercise UE-P-5-6: Code generation
|
||||||
|
// ===============================================
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-3: Error distance
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-2 + Exercise 3: Sets to handle certain first, follow, and recover sets
|
||||||
|
|
||||||
|
// Initialize first and follow sets.
|
||||||
|
static final EnumSet<Token.Kind> firstConstDecl = EnumSet.of(Token.Kind.final_);
|
||||||
|
static final EnumSet<Token.Kind> firstVarDecl = EnumSet.of(Token.Kind.ident);
|
||||||
|
static final EnumSet<Token.Kind> firstClassDecl = EnumSet.of(Token.Kind.class_);
|
||||||
|
static final EnumSet<Token.Kind> breakDecl = EnumSet.of(lbrace, eof);
|
||||||
|
static final EnumSet<Token.Kind> firstStatement = EnumSet.of(ident, if_, while_, break_, return_, read, print, lbrace, semicolon);
|
||||||
|
static final EnumSet<Token.Kind> breakStatement = EnumSet.of(rbrace, else_, eof);
|
||||||
|
static final EnumSet<Token.Kind> firstAsignop = EnumSet.of(assign, plusas, minusas, timesas, slashas, remas);
|
||||||
|
static final EnumSet<Token.Kind> firstFactor = EnumSet.of(ident, number, charConst, new_, lpar);
|
||||||
|
static final EnumSet<Token.Kind> firstMulop = EnumSet.of(times, slash, rem);
|
||||||
|
static final EnumSet<Token.Kind> firstMethodDecl = EnumSet.of(ident, void_);
|
||||||
|
static final EnumSet<Token.Kind> breakMethodDecl = EnumSet.of(rbrace, eof);
|
||||||
|
|
||||||
|
|
||||||
|
static <T extends Enum<T>> EnumSet<T> enumUnion(EnumSet<T> first, EnumSet<T> ...sets) {
|
||||||
|
final var copy = EnumSet.copyOf(first);
|
||||||
|
for (final var set : sets) {
|
||||||
|
copy.addAll(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-2: One top-down parsing method per production
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Program = <br>
|
||||||
|
* "program" ident <br>
|
||||||
|
* { ConstDecl | VarDecl | ClassDecl } <br>
|
||||||
|
* "{" { MethodDecl } "}" .
|
||||||
|
*/
|
||||||
|
private void Program() {
|
||||||
|
// TODO Exercise UE-P-2
|
||||||
|
|
||||||
|
check(Token.Kind.program);
|
||||||
|
check(Token.Kind.ident);
|
||||||
|
|
||||||
|
while (!breakDecl.contains(sym)) {
|
||||||
|
if (firstVarDecl.contains(sym)) {
|
||||||
|
VarDecl();
|
||||||
|
} else if (firstClassDecl.contains(sym)) {
|
||||||
|
ClassDecl();
|
||||||
|
} else if (firstConstDecl.contains(sym)){
|
||||||
|
ConstDecl();
|
||||||
|
} else {
|
||||||
|
recoverDecl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(lbrace);
|
||||||
|
|
||||||
|
while (!breakMethodDecl.contains(sym)) {
|
||||||
|
if (firstMethodDecl.contains(sym)) {
|
||||||
|
MethodDecl();
|
||||||
|
} else {
|
||||||
|
recoverMethodDecl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(rbrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConstDecl() {
|
||||||
|
check(Token.Kind.final_);
|
||||||
|
Type();
|
||||||
|
check(Token.Kind.ident);
|
||||||
|
check(Token.Kind.assign);
|
||||||
|
|
||||||
|
if (sym == Token.Kind.number) {
|
||||||
|
scan();
|
||||||
|
} else if (sym == Token.Kind.charConst) {
|
||||||
|
scan();
|
||||||
|
} else {
|
||||||
|
error(TOKEN_EXPECTED, "number or character constant");
|
||||||
|
}
|
||||||
|
|
||||||
|
check(Token.Kind.semicolon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VarDecl() {
|
||||||
|
Type();
|
||||||
|
check(ident);
|
||||||
|
|
||||||
|
while (sym == Token.Kind.comma) {
|
||||||
|
scan();
|
||||||
|
check(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClassDecl() {
|
||||||
|
check(class_);
|
||||||
|
check(ident);
|
||||||
|
check(lbrace);
|
||||||
|
|
||||||
|
while (firstVarDecl.contains(sym)) {
|
||||||
|
VarDecl();
|
||||||
|
}
|
||||||
|
|
||||||
|
check(rbrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Type() {
|
||||||
|
check(ident);
|
||||||
|
|
||||||
|
if (sym == lbrack) {
|
||||||
|
scan();
|
||||||
|
check(rbrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MethodDecl() {
|
||||||
|
if (sym == ident) {
|
||||||
|
Type();
|
||||||
|
} else if (sym == void_) {
|
||||||
|
scan();
|
||||||
|
}
|
||||||
|
|
||||||
|
check(ident);
|
||||||
|
check(lpar);
|
||||||
|
|
||||||
|
if (sym == ident) {
|
||||||
|
FormPars();
|
||||||
|
}
|
||||||
|
|
||||||
|
check(rpar);
|
||||||
|
|
||||||
|
while (firstVarDecl.contains(sym)) {
|
||||||
|
VarDecl();
|
||||||
|
}
|
||||||
|
|
||||||
|
Block();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormPars() {
|
||||||
|
Type();
|
||||||
|
check(ident);
|
||||||
|
|
||||||
|
while (sym == comma) {
|
||||||
|
scan();
|
||||||
|
Type();
|
||||||
|
check(ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Block() {
|
||||||
|
check(lbrace);
|
||||||
|
Statement();
|
||||||
|
check(rbrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Statement() {
|
||||||
|
while (!breakStatement.contains(sym)) {
|
||||||
|
switch (sym) {
|
||||||
|
case ident -> {
|
||||||
|
Designator();
|
||||||
|
|
||||||
|
if (firstAsignop.contains(sym)) {
|
||||||
|
AssignOp();
|
||||||
|
Expr();
|
||||||
|
} else if (sym == lpar) {
|
||||||
|
ActPars();
|
||||||
|
} else if (sym == pplus) {
|
||||||
|
scan();
|
||||||
|
} else if (sym == mminus) {
|
||||||
|
scan();
|
||||||
|
} else {
|
||||||
|
error(TOKEN_EXPECTED, "unexpected token. assignment token (=, +=, -=, *=, /=, %=), method call (\"(\"), increment (++) or decrement (--)");
|
||||||
|
}
|
||||||
|
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
case if_ -> {
|
||||||
|
scan();
|
||||||
|
check(lpar);
|
||||||
|
Condition();
|
||||||
|
check(rpar);
|
||||||
|
Statement();
|
||||||
|
|
||||||
|
if (sym == else_) {
|
||||||
|
scan();
|
||||||
|
Statement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case while_ -> {
|
||||||
|
scan();
|
||||||
|
check(lpar);
|
||||||
|
Condition();
|
||||||
|
check(rpar);
|
||||||
|
Statement();
|
||||||
|
}
|
||||||
|
case break_-> {
|
||||||
|
scan();
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
case return_ -> {
|
||||||
|
scan();
|
||||||
|
|
||||||
|
if (sym == minus || firstFactor.contains(sym)) {
|
||||||
|
Expr();
|
||||||
|
}
|
||||||
|
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
case read -> {
|
||||||
|
scan();
|
||||||
|
check(lpar);
|
||||||
|
Designator();
|
||||||
|
check(rpar);
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
case print -> {
|
||||||
|
scan();
|
||||||
|
check(lpar);
|
||||||
|
Expr();
|
||||||
|
|
||||||
|
if (sym == comma) {
|
||||||
|
scan();
|
||||||
|
check(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
check(rpar);
|
||||||
|
check(semicolon);
|
||||||
|
}
|
||||||
|
case lbrace -> Block();
|
||||||
|
case semicolon -> scan();
|
||||||
|
default -> recoverStatement();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Designator() {
|
||||||
|
check(ident);
|
||||||
|
|
||||||
|
while (sym == period || sym == lbrack) {
|
||||||
|
if (sym == period) {
|
||||||
|
scan();
|
||||||
|
check(ident);
|
||||||
|
} else {
|
||||||
|
scan();
|
||||||
|
|
||||||
|
if (sym == tilde) {
|
||||||
|
scan();
|
||||||
|
}
|
||||||
|
|
||||||
|
Expr();
|
||||||
|
|
||||||
|
check(rbrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AssignOp() {
|
||||||
|
switch (sym) {
|
||||||
|
case assign -> scan();
|
||||||
|
case plusas -> scan();
|
||||||
|
case minusas -> scan();
|
||||||
|
case timesas -> scan();
|
||||||
|
case slashas -> scan();
|
||||||
|
case remas -> scan();
|
||||||
|
default -> error(TOKEN_EXPECTED, "unexpected token. assignment token (=, +=, -=, *=, /=, %=), method call (\"(\"), increment (++) or decrement (--)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Expr() {
|
||||||
|
if (sym == minus) {
|
||||||
|
scan();
|
||||||
|
}
|
||||||
|
|
||||||
|
Term();
|
||||||
|
|
||||||
|
while (sym == plus || sym == minus) {
|
||||||
|
Addop();
|
||||||
|
Term();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ActPars() {
|
||||||
|
|
||||||
|
check(lpar);
|
||||||
|
|
||||||
|
if (sym == minus || firstFactor.contains(sym)) {
|
||||||
|
Expr();
|
||||||
|
|
||||||
|
while (sym == comma) {
|
||||||
|
scan();
|
||||||
|
Expr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(rpar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Term() {
|
||||||
|
Factor();
|
||||||
|
|
||||||
|
while (firstMulop.contains(sym)) {
|
||||||
|
Mulop();
|
||||||
|
Factor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Addop() {
|
||||||
|
if (sym == plus) {
|
||||||
|
scan();
|
||||||
|
} else if (sym == minus) {
|
||||||
|
scan();
|
||||||
|
} else {
|
||||||
|
error(TOKEN_EXPECTED, "minus or plus expected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Factor() {
|
||||||
|
switch(sym) {
|
||||||
|
case ident -> Designator();
|
||||||
|
case number -> scan();
|
||||||
|
case charConst -> scan();
|
||||||
|
case new_ -> {
|
||||||
|
scan();
|
||||||
|
check(ident);
|
||||||
|
if (sym == lbrack) {
|
||||||
|
scan();
|
||||||
|
Expr();
|
||||||
|
check(rbrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case lpar -> {
|
||||||
|
scan();
|
||||||
|
Expr();
|
||||||
|
check(rpar);
|
||||||
|
}
|
||||||
|
default -> error(TOKEN_EXPECTED, "unexpected token. identifier, number, character constant, new or \"(\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Mulop() {
|
||||||
|
if (sym == times) {
|
||||||
|
scan();
|
||||||
|
} else if (sym == slash) {
|
||||||
|
scan();
|
||||||
|
} else if (sym == rem) {
|
||||||
|
scan();
|
||||||
|
} else {
|
||||||
|
error(TOKEN_EXPECTED, "expected *, /, %");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Condition() {
|
||||||
|
CondTerm();
|
||||||
|
|
||||||
|
while (sym == or) {
|
||||||
|
scan();
|
||||||
|
CondTerm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CondTerm() {
|
||||||
|
CondFact();
|
||||||
|
|
||||||
|
while (sym == and) {
|
||||||
|
scan();
|
||||||
|
CondFact();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CondFact() {
|
||||||
|
Expr();
|
||||||
|
Relop();
|
||||||
|
Expr();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Relop() {
|
||||||
|
switch (sym) {
|
||||||
|
case eql -> scan();
|
||||||
|
case neq -> scan();
|
||||||
|
case gtr -> scan();
|
||||||
|
case lss -> scan();
|
||||||
|
case leq -> scan();
|
||||||
|
case geq -> scan();
|
||||||
|
default -> error(TOKEN_EXPECTED, "unexpected token. ==, !=, >, >=, <, <=");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// ------------------------------------
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-3: Error recovery methods: recoverDecl, recoverMethodDecl and recoverStat (+ TODO Exercise UE-P-5: Check idents for Type kind)
|
||||||
|
|
||||||
|
private void recoverDecl() {
|
||||||
|
final var firstDeclarationUnion = enumUnion(firstConstDecl, firstVarDecl, firstClassDecl);
|
||||||
|
final var recoverDeclSet = enumUnion(breakDecl, firstDeclarationUnion);
|
||||||
|
error(DECLARATION_RECOVERY);
|
||||||
|
|
||||||
|
do {
|
||||||
|
scan();
|
||||||
|
} while (!recoverDeclSet.contains(sym));
|
||||||
|
errorDist = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recoverMethodDecl() {
|
||||||
|
error(METHOD_DECL_RECOVERY);
|
||||||
|
|
||||||
|
final var recoveryMethoDeclSet = enumUnion(breakMethodDecl, firstMethodDecl);
|
||||||
|
do {
|
||||||
|
scan();
|
||||||
|
} while (!recoveryMethoDeclSet.contains(sym));
|
||||||
|
errorDist = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recoverStatement() {
|
||||||
|
error(STATEMENT_RECOVERY);
|
||||||
|
|
||||||
|
var recoveryStatementSet = enumUnion(breakStatement, firstStatement);
|
||||||
|
recoveryStatementSet.remove(ident);
|
||||||
|
recoveryStatementSet.remove(lbrace);
|
||||||
|
|
||||||
|
do {
|
||||||
|
scan();
|
||||||
|
} while (!recoveryStatementSet.contains(sym));
|
||||||
|
errorDist = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================
|
||||||
|
// ====================================
|
||||||
|
}
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
package ssw.mj.impl;
|
||||||
|
|
||||||
|
import ssw.mj.Errors;
|
||||||
|
import ssw.mj.Interpreter;
|
||||||
|
import ssw.mj.scanner.Token;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static ssw.mj.scanner.Token.Kind.*;
|
||||||
|
import static ssw.mj.scanner.Token.Kind.number;
|
||||||
|
|
||||||
|
public class Scanner {
|
||||||
|
|
||||||
|
// Scanner Skeleton - do not rename fields / methods !
|
||||||
|
private static final char EOF = (char) -1;
|
||||||
|
private static final char LF = '\n';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input data to read from.
|
||||||
|
*/
|
||||||
|
private final Reader in;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookahead character. (= next (unhandled) character in the input stream)
|
||||||
|
*/
|
||||||
|
private char ch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current line in input stream.
|
||||||
|
*/
|
||||||
|
private int line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current column in input stream.
|
||||||
|
*/
|
||||||
|
private int col;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According errors object.
|
||||||
|
*/
|
||||||
|
public final Errors errors;
|
||||||
|
|
||||||
|
public Scanner(Reader r) {
|
||||||
|
// store reader
|
||||||
|
in = r;
|
||||||
|
|
||||||
|
// initialize error handling support
|
||||||
|
errors = new Errors();
|
||||||
|
|
||||||
|
line = 1;
|
||||||
|
col = 0;
|
||||||
|
nextCh(); // read 1st char into ch, incr col to 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds error message to the list of errors.
|
||||||
|
*/
|
||||||
|
public final void error(Token t, Errors.Message msg, Object... msgParams) {
|
||||||
|
errors.error(t.line, t.col, msg, msgParams);
|
||||||
|
|
||||||
|
// reset token content (consistent JUnit tests)
|
||||||
|
t.numVal = 0;
|
||||||
|
t.val = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// TODO Exercise UE-P-1: Implement Scanner (next() + private helper methods)
|
||||||
|
// ================================================
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-1: Keywords
|
||||||
|
/**
|
||||||
|
* Mapping from keyword names to appropriate token codes.
|
||||||
|
*/
|
||||||
|
private static final Map<String, Token.Kind> keywords;
|
||||||
|
|
||||||
|
static {
|
||||||
|
keywords = Map.ofEntries(
|
||||||
|
Map.entry("void", void_),
|
||||||
|
Map.entry("class", class_),
|
||||||
|
Map.entry("program", program),
|
||||||
|
Map.entry("if", if_),
|
||||||
|
Map.entry("else", else_),
|
||||||
|
Map.entry("while", while_),
|
||||||
|
Map.entry("read", read),
|
||||||
|
Map.entry("print", print),
|
||||||
|
Map.entry("return", return_),
|
||||||
|
Map.entry("break", break_),
|
||||||
|
Map.entry("final", final_),
|
||||||
|
Map.entry("new", new_)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns next token. To be used by parser.
|
||||||
|
*/
|
||||||
|
public Token next() {
|
||||||
|
// TODO Exercise UE-P-1: implementation of next method
|
||||||
|
while (Character.isWhitespace(ch)) {
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
final var ogLine = line;
|
||||||
|
final var ogCol = col;
|
||||||
|
|
||||||
|
if (isLetter(ch)) {
|
||||||
|
final var name = readName();
|
||||||
|
|
||||||
|
return getTokenByName(name, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDigit(ch)) {
|
||||||
|
final var stringNumber = readNumber();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final var number = Integer.parseInt(stringNumber);
|
||||||
|
|
||||||
|
var token = new Token(Token.Kind.number, ogLine, ogCol);
|
||||||
|
token.numVal = number;
|
||||||
|
token.val = stringNumber;
|
||||||
|
|
||||||
|
return token;
|
||||||
|
} catch (NumberFormatException exception) {
|
||||||
|
final var token = new Token(number, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.BIG_NUM, stringNumber);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '\'') {
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
if (ch == '\r') {
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
var charContent = ch;
|
||||||
|
var lfEscaped = false;
|
||||||
|
var tickEscaped = false;
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
nextCh();
|
||||||
|
charContent = switch (ch) {
|
||||||
|
case 'n' -> '\n';
|
||||||
|
case 'r' -> '\r';
|
||||||
|
case 't' -> '\t';
|
||||||
|
case '\\' -> '\\';
|
||||||
|
case '\'' -> '\'';
|
||||||
|
default -> '\0';
|
||||||
|
};
|
||||||
|
|
||||||
|
if (charContent == '\n') {
|
||||||
|
lfEscaped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charContent == '\'') {
|
||||||
|
tickEscaped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charContent == '\0') {
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.UNDEFINED_ESCAPE, ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charContent == LF && !lfEscaped || charContent == EOF || charContent == '\'' && !tickEscaped) {
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
final var message = switch (charContent) {
|
||||||
|
case LF -> Errors.Message.ILLEGAL_LINE_END;
|
||||||
|
case EOF -> Errors.Message.EOF_IN_CHAR;
|
||||||
|
case '\'' -> Errors.Message.EMPTY_CHARCONST;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
|
||||||
|
error(token, message);
|
||||||
|
token.val = '\0' + "";
|
||||||
|
token.numVal = 0;
|
||||||
|
|
||||||
|
if (charContent == '\'') {
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
if (ch != '\'') {
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.MISSING_QUOTE);
|
||||||
|
token.val = '\0' + "";
|
||||||
|
token.numVal = 0;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
token.val = charContent + "";
|
||||||
|
token.numVal = charContent;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var ogChar = ch;
|
||||||
|
nextCh();
|
||||||
|
return switch(ogChar) {
|
||||||
|
case ';' -> new Token(Token.Kind.semicolon, ogLine, ogCol);
|
||||||
|
case ',' -> new Token(Token.Kind.comma, ogLine, ogCol);
|
||||||
|
case '.' -> new Token(Token.Kind.period, ogLine, ogCol);
|
||||||
|
case '{' -> new Token(Token.Kind.lbrace, ogLine, ogCol);
|
||||||
|
case '}' -> new Token(Token.Kind.rbrace, ogLine, ogCol);
|
||||||
|
case '[' -> new Token(Token.Kind.lbrack, ogLine, ogCol);
|
||||||
|
case ']' -> new Token(Token.Kind.rbrack, ogLine, ogCol);
|
||||||
|
case '(' -> new Token(Token.Kind.lpar, ogLine, ogCol);
|
||||||
|
case ')' -> new Token(Token.Kind.rpar, ogLine, ogCol);
|
||||||
|
case (char) -1 -> new Token(Token.Kind.eof, ogLine, ogCol);
|
||||||
|
case '~' -> new Token(Token.Kind.tilde, ogLine, ogCol);
|
||||||
|
case '=' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.eql, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.assign, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '+' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.plusas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '+') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.pplus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.plus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '-' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.minusas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '-') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.mminus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.minus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '*' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.timesas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.times, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '/' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.slashas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '*') {
|
||||||
|
nextCh();
|
||||||
|
final var success = skipComment();
|
||||||
|
if (success) {
|
||||||
|
yield next();
|
||||||
|
} else {
|
||||||
|
final var token = new Token(eof, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.EOF_IN_COMMENT);
|
||||||
|
yield next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.slash, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '%' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.remas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.rem, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '!' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.neq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var token = new Token(none, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.INVALID_CHAR, ogChar);
|
||||||
|
yield token;
|
||||||
|
}
|
||||||
|
case '>' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.geq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.gtr, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '<' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.leq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.lss, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '&' -> {
|
||||||
|
if (ch == '&') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.and, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var token = new Token(none, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.INVALID_CHAR, ogChar);
|
||||||
|
yield token;
|
||||||
|
}
|
||||||
|
case '|' -> {
|
||||||
|
if (ch == '|') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.or, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
final var token = new Token(none, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.INVALID_CHAR, ogChar);
|
||||||
|
yield token;
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
final var token = new Token(none, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.INVALID_CHAR, ogChar);
|
||||||
|
yield token;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextCh() {
|
||||||
|
// TODO Exercise UE-P-1: implementation of nextCh method and other private helper methods
|
||||||
|
try {
|
||||||
|
final var intChar = in.read();
|
||||||
|
final var nextChar = (char) intChar;
|
||||||
|
|
||||||
|
if (ch == '\n') {
|
||||||
|
line++;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
col++;
|
||||||
|
ch = nextChar;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Could not read Stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-1: private helper methods used by next(), as discussed in the exercise
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
private boolean isLetter(char c) {
|
||||||
|
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDigit(char c) {
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readName() {
|
||||||
|
final var builder = new StringBuilder();
|
||||||
|
|
||||||
|
while (isLetter(ch) || ch == '_' || isDigit(ch)) {
|
||||||
|
builder.append(ch);
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readNumber() {
|
||||||
|
final var builder = new StringBuilder();
|
||||||
|
|
||||||
|
while (isDigit(ch)) {
|
||||||
|
builder.append(ch);
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token getTokenByName(String name, int line, int col) {
|
||||||
|
final var kind = keywords.getOrDefault(name, Token.Kind.ident);
|
||||||
|
|
||||||
|
var token = new Token(kind, line, col);
|
||||||
|
|
||||||
|
if (kind == Token.Kind.ident) {
|
||||||
|
token.val = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean skipComment() {
|
||||||
|
var commentCount = 1;
|
||||||
|
while (commentCount != 0) {
|
||||||
|
final var lastCh = ch;
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
if (lastCh == '/' && ch == '*') {
|
||||||
|
commentCount++;
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastCh == '*' && ch == '/') {
|
||||||
|
commentCount--;
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == EOF && commentCount != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// ================================================
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============================================
|
||||||
|
// ===============================================
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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(','); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+27
@@ -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
|
||||||
|
}
|
||||||
@@ -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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user