initial commit, template added
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
MicroJava Tests/tests/ssw/mj/test/support/Configuration.java
Normal file
27
MicroJava Tests/tests/ssw/mj/test/support/Configuration.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package ssw.mj.test.support;
|
||||
|
||||
public class Configuration {
|
||||
/**
|
||||
* set to true to print expected and actual values of all testcases,
|
||||
* not only failing ones (prints expected errors, tokens, symbol table, code)
|
||||
*/
|
||||
public static final boolean ALSO_PRINT_SUCCESSFUL_TESTCASES = Boolean.getBoolean("microjava.testcaseOutput");
|
||||
|
||||
/**
|
||||
* Set to true to print debug information of the interpreter. Equal to
|
||||
* "-debug" on the command line. <br>
|
||||
* Remark:<br>
|
||||
* This is a lot of output, some test cases might time out, e.g.
|
||||
* CodeGenerationTest.fib
|
||||
*/
|
||||
public static final boolean PRINT_INTERPRETER_DEBUG_OUTPUT = Boolean.getBoolean("microjava.interpreterOutput");
|
||||
|
||||
/**
|
||||
* Determines the timeout after which a test case should fail automatically.
|
||||
* Default: 10 seconds. The default should work for all test cases
|
||||
* on most machines.<br>
|
||||
* <em>Attention</em>: For most computers it is likely that there is an
|
||||
* endless loop in the MicroJava compiler if a test fails for a timeout.
|
||||
*/
|
||||
public static final long TIMEOUT = 10;
|
||||
}
|
||||
119
MicroJava Tests/tests/ssw/mj/test/support/SymTabDumper.java
Normal file
119
MicroJava Tests/tests/ssw/mj/test/support/SymTabDumper.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package ssw.mj.test.support;
|
||||
|
||||
import ssw.mj.impl.Tab;
|
||||
import ssw.mj.symtab.Obj;
|
||||
import ssw.mj.symtab.Scope;
|
||||
import ssw.mj.symtab.Struct;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class SymTabDumper {
|
||||
public static String dump(Tab tab) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (tab.curScope != null) {
|
||||
dump(tab.curScope, sb);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static void dump(Scope scope, StringBuilder sb) {
|
||||
sb.append("-- begin scope (").append(scope.nVars()).append(" variables) --\n");
|
||||
if (!scope.locals().isEmpty()) {
|
||||
dump(scope.locals().values(), sb, "");
|
||||
}
|
||||
if (scope.outer() != null) {
|
||||
sb.append("\n");
|
||||
dump(scope.outer(), sb);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dump(Collection<Obj> objects, StringBuilder sb, String indent) {
|
||||
for (Obj obj : objects) {
|
||||
dump(obj, sb, indent);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dump(Obj obj, StringBuilder sb, String indent) {
|
||||
sb.append(indent);
|
||||
|
||||
switch (obj.kind) {
|
||||
case Con -> dumpCon(obj, sb, indent);
|
||||
case Var -> dumpVar(obj, sb, indent);
|
||||
case Type -> dumpType(obj, sb, indent);
|
||||
case Meth -> dumpMethod(obj, sb, indent);
|
||||
case Prog -> dumpProgram(obj, sb);
|
||||
}
|
||||
|
||||
if (obj.locals != null) {
|
||||
sb.append("\n");
|
||||
dump(obj.locals.values(), sb, indent + " ");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
private static void dumpCon(Obj obj, StringBuilder sb, String indent) {
|
||||
sb.append("Constant: ");
|
||||
if (obj.type != null) {
|
||||
dump(obj.type, sb, indent, false);
|
||||
}
|
||||
sb.append(" ").append(obj.name).append(" = ");
|
||||
if (obj.type == Tab.charType) {
|
||||
sb.append("'").append((char) obj.val).append("'");
|
||||
} else {
|
||||
sb.append(obj.val);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpVar(Obj obj, StringBuilder sb, String indent) {
|
||||
if (obj.level == 0) {
|
||||
sb.append("Global Variable ");
|
||||
} else {
|
||||
sb.append("Local Variable ");
|
||||
}
|
||||
sb.append(obj.adr).append(": ");
|
||||
if (obj.type != null) {
|
||||
dump(obj.type, sb, indent, false);
|
||||
}
|
||||
sb.append(" ").append(obj.name);
|
||||
}
|
||||
|
||||
private static void dumpType(Obj type, StringBuilder sb, String indent) {
|
||||
sb.append("Type ").append(type.name).append(": ");
|
||||
if (type.type != null) {
|
||||
dump(type.type, sb, indent + " ", true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpMethod(Obj meth, StringBuilder sb, String indent) {
|
||||
sb.append("Method: ");
|
||||
if (meth.type != null) {
|
||||
dump(meth.type, sb, indent, false);
|
||||
}
|
||||
sb.append(" ").append(meth.name).append(" (").append(meth.locals.size()).append(" locals, ").append(meth.nPars).append(" parameters").append(")");
|
||||
}
|
||||
|
||||
private static void dumpProgram(Obj obj, StringBuilder sb) {
|
||||
sb.append("Program ").append(obj.name).append(":");
|
||||
}
|
||||
|
||||
private static void dump(Struct struct, StringBuilder sb, String indent, boolean dumpFields) {
|
||||
switch (struct.kind) {
|
||||
case None -> sb.append("void");
|
||||
case Int -> sb.append("int");
|
||||
case Char -> sb.append("char");
|
||||
case Arr -> {
|
||||
if (struct.elemType != null) {
|
||||
dump(struct.elemType, sb, indent, dumpFields);
|
||||
}
|
||||
sb.append("[]");
|
||||
}
|
||||
case Class -> {
|
||||
sb.append("class (").append(struct.nrFields()).append(" fields)");
|
||||
if (dumpFields && struct.fields != null) {
|
||||
sb.append("\n");
|
||||
dump(struct.fields.values(), sb, indent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user