Finish Java Tram implementation

This commit is contained in:
Jan-Niclas Loosen
2025-10-25 17:14:37 +02:00
parent b0974af092
commit 59b6371966
5 changed files with 399 additions and 45 deletions

View File

@@ -1,68 +1,267 @@
package de.unitrier.st.uap.w25.tram;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.util.*;
public class AbstractMachine {
protected List<Integer> stack;
protected List<Instruction> prog;
protected ArrayList<Integer> stack;
protected ArrayList<Instruction> prog;
protected int FP = 0;
protected int PP = 0;
protected int PC = 0;
protected int TOP = 0;
protected boolean debug = false;
protected int TOP = -1;
public AbstractMachine(Instruction[] prog) {
this.prog = Arrays.asList(prog);
this.stack = new LinkedList<>();
this.prog = new ArrayList<>(Arrays.asList(prog));
this.stack = new ArrayList<>();
}
public AbstractMachine(Instruction[] prog, Integer[] stack) {
this(prog);
this.stack = Arrays.asList(stack);
this.TOP = stack.length;
this.stack = new ArrayList<>(Arrays.asList(stack));
this.TOP = stack.length - 1;
}
public void setDebug(boolean debug) {
this.debug = debug;
// Executes until halts and returns final configuration
public String execute() {
while(!isHalted()) {
executeStep();
}
return configuration();
}
public void execute() {
for (Instruction instruction : prog) {
switch (instruction.getOpcode()) {
case Instruction.CONST:
int k = instruction.getArg1();
this.stack.set(this.TOP + 1, k);
this.TOP += 1;
// Executes until halts and returns configurations of all steps
public ArrayList<String> executeDebug() {
ArrayList<String> out = new ArrayList<>();
while(!isHalted()) {
String conf = executeStep();
out.add(conf);
}
return out;
}
// Executes single step and returns configuration
public String executeStep() {
ensureCapacity(this.TOP);
Instruction inst = this.prog.get(this.PC);
int arg1 = inst.getArg1() != null ? inst.getArg1() : 0;
int arg2 = inst.getArg2() != null ? inst.getArg2() : 0;
int arg3 = inst.getArg3() != null ? inst.getArg3() : 0;
switch (inst.getOpcode()) {
case Instruction.CONST:
// Pg. 9
this.stack.set(this.TOP + 1, arg1);
this.TOP += 1;
this.PC += 1;
break;
case Instruction.LOAD:
// Pg. 25
int loadSpp = spp(arg2, this.PP, this.FP);
this.stack.set(this.TOP + 1, this.stack.get(loadSpp + arg1));
this.TOP += 1;
this.PC += 1;
break;
case Instruction.STORE:
// Pg. 25
int storeSpp = spp(arg2, this.PP, this.FP);
this.stack.set(storeSpp + arg1, this.stack.get(this.TOP));
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.ADD:
// Pg. 9
int addA = stack.get(this.TOP - 1);
int addB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, addA + addB);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.SUB:
// Pg. 9
int subA = stack.get(this.TOP - 1);
int subB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, subA - subB);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.MUL:
// Pg. 9
int mulA = stack.get(this.TOP - 1);
int mulB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, mulA * mulB);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.DIV:
// Pg. 9
int divA = stack.get(this.TOP - 1);
int divB = stack.get(this.TOP);
if (divB == 0) {
throw new ArithmeticException("Division by zero");
}
this.stack.set(this.TOP - 1, divA / divB);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.LT:
// Pg. 12
int ltA = stack.get(this.TOP - 1);
int ltB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, ltA < ltB ? 1 : 0);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.GT:
// Pg. 12
int gtA = stack.get(this.TOP - 1);
int gtB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, gtA > gtB ? 1 : 0);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.EQ:
// Pg. 12
int eqA = stack.get(this.TOP - 1);
int eqB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, eqA == eqB ? 1 : 0);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.NEQ:
// Pg. 12
int neqA = stack.get(this.TOP - 1);
int neqB = stack.get(this.TOP);
this.stack.set(this.TOP - 1, neqA != neqB ? 1 : 0);
this.TOP -= 1;
this.PC += 1;
break;
case Instruction.IFZERO:
// Pg. 13
if (this.stack.get(this.TOP) == 0) {
this.PC = arg1;
}
else {
this.PC += 1;
break;
case Instruction.LOAD: break;
case Instruction.STORE: break;
case Instruction.ADD: break;
case Instruction.SUB: break;
case Instruction.MUL: break;
case Instruction.DIV: break;
case Instruction.LT: break;
case Instruction.GT: break;
case Instruction.EQ: break;
case Instruction.NEQ: break;
case Instruction.IFZERO: break;
case Instruction.GOTO: break;
case Instruction.HALT: break;
case Instruction.NOP: break;
case Instruction.INVOKE: break;
case Instruction.RETURN: break;
case Instruction.POP: break;
default: break;
}
}
this.TOP -= 1;
break;
case Instruction.GOTO:
// Pg. 13
this.PC = arg1;
break;
case Instruction.HALT:
// Pg. 13
this.PC = -1;
break;
case Instruction.NOP:
// Pg. 13
this.PC += 1;
break;
case Instruction.INVOKE:
// Pg. 23
this.stack.set(this.TOP + 1, this.PC + 1);
this.stack.set(this.TOP + 2, this.PP);
this.stack.set(this.TOP + 3, this.FP);
this.stack.set(this.TOP + 4, spp(arg3, this.PP, this.FP));
this.stack.set(this.TOP + 5, sfp(arg3, this.PP, this.FP));
this.PP = this.TOP - arg1 + 1;
this.FP = this.TOP + 1;
this.TOP += 5;
this.PC = arg2;
break;
case Instruction.RETURN:
// Pg. 24
int res = this.stack.get(this.TOP);
this.TOP = this.PP;
this.PC = this.stack.get(this.FP);
this.PP = this.stack.get(this.FP + 1);
this.FP = this.stack.get(this.FP + 2);
this.stack.set(this.TOP, res);
break;
case Instruction.POP:
// Not in docs, see MaMa
this.TOP -= 1;
this.PC += 1;
break;
default: break;
}
return configuration();
}
public boolean isHalted() {
return this.PC < 0;
}
// PP of the predecessor
protected int spp(int d, int pp, int fp) {
if(d==0) {
return pp;
}
return spp(d-1, this.stack.get(fp+3), this.stack.get(fp+4));
}
// FP of the predecessor
protected int sfp(int d, int pp, int fp) {
if(d==0) {
return fp;
}
return sfp(d-1, this.stack.get(fp+3), this.stack.get(fp+4));
}
// Called before each instruction
protected void ensureCapacity(int currSize) {
int size = stack.size();
// Initialize stack with default capacity of 16
if (size == 0) {
for (int i = 0; i < 16; i++) stack.add(0);
return;
}
// Double list capacity when fewer than 8 free slots remain
// (largest instruction, INVOKE, needs 5 additional cells)
if (currSize >= size - 8) {
int newSize = size * 2;
for (int i = size; i < newSize; i++) stack.add(0);
}
}
protected String config() {
return "";
// Prints the machine's current configuration
public String configuration() {
StringBuilder sb = new StringBuilder();
sb.append("TOP=").append(this.TOP)
.append(" PC=").append(this.PC)
.append(" PP=").append(this.PP)
.append(" FP=").append(this.FP)
.append("\nSTACK: [");
int limit = Math.min(this.stack.size(), this.TOP + 1);
for (int i = 0; i < limit; i++) {
sb.append(this.stack.get(i));
if (i < limit - 1) sb.append(", ");
}
sb.append("]");
return sb.toString();
}
public int result() {
return this.TOP >= 0 ? this.stack.get(this.TOP) : 0;
}
public ArrayList<Integer> results() {
ArrayList<Integer> result = new ArrayList<>();
for (int i = 0; i <= this.TOP; i++) {
result.add(this.stack.get(i));
}
return result;
}
}

View File

@@ -0,0 +1,74 @@
package de.unitrier.st.uap.w25.tram;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class AbstractMachineTests {
@Test
void testExample1() {
Instruction[] code = Assembler.readTRAMCode("tramcode/example1.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{0, 0});
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt.");
int y = tram.results().get(1);
assertEquals(28, y, "Expected y = 6*3 + 5*2 = 28.");
}
@Test
void testExample2() {
Instruction[] code = Assembler.readTRAMCode("tramcode/example2.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{10});
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt.");
var results = tram.results();
assertEquals(200, results.get(results.size() - 2), "Expected value 200 since x!=0.");
assertEquals(3, results.get(results.size() - 1), "Expected final constant 3 on stack.");
}
@Test
void testExample3() {
Instruction[] code = Assembler.readTRAMCode("tramcode/example3.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{}); // no initial stack
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt.");
int result = tram.result();
assertEquals(15, result, "Expected return value 15 for f(2,3,4) because y==3.");
}
@Test
void testSquareFunction() {
Instruction[] code = Assembler.readTRAMCode("tramcode/square.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{}); // empty stack
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt.");
int result = tram.result();
assertEquals(100, result, "Expected result of square(10) = 100.");
}
@Test
void testFibonacci() {
Instruction[] code = Assembler.readTRAMCode("tramcode/test.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{}); // empty stack
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt.");
int result = tram.result();
assertEquals(8, result, "Expected fib(6) = 8.");
}
@Test
void testWrapperSquare() {
Instruction[] code = Assembler.readTRAMCode("tramcode/wrapper.tram");
AbstractMachine tram = new AbstractMachine(code, new Integer[]{}); // empty stack
tram.execute();
assertTrue(tram.isHalted(), "Machine should halt after executing wrapper(4,10).");
int result = tram.result();
assertEquals(4, result, "Expected result of wrapper(4,10) = 4.");
}
}

View File

@@ -0,0 +1,43 @@
package de.unitrier.st.uap.w25.tram;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LoggedMachine extends AbstractMachine {
private static final Logger logger = LogManager.getLogger(LoggedMachine.class);
public LoggedMachine(Instruction[] prog) {
super(prog);
}
public LoggedMachine(Instruction[] prog, Integer[] stack) {
super(prog, stack);
}
@Override
public String executeStep() {
String confBefore = configuration();
logger.debug("Before step:\n" + confBefore);
String confAfter = super.executeStep();
logger.debug("After step:\n" + confAfter);
return confAfter;
}
@Override
public String execute() {
logger.info("Execution started");
String result = super.execute();
logger.info("Execution finished:\n" + configuration());
return result;
}
@Override
public java.util.ArrayList<String> executeDebug() {
logger.info("Debug execution started");
var out = super.executeDebug();
logger.info("Debug execution finished");
return out;
}
}

View File

@@ -4,8 +4,7 @@ final class Main
{
private Main(){}
public static void main(String[] argv)
{
static void main(String[] argv) {
Instruction[] code = Assembler.readTRAMCode(
// "tramcode/square.tram"
// "tramcode/wrapper.tram"
@@ -24,5 +23,18 @@ final class Main
}
// TODO: Create an instance of the abstract machine with reasonable parameters
AbstractMachine tram = new AbstractMachine(code, new Integer[]{}); // empty stack or predefined args
System.out.println("Initial configuration:");
System.out.println(tram.configuration());
// Stepwise execution (debug)
System.out.println("\nExecution trace:");
for (String conf : tram.executeDebug()) {
System.out.println(conf);
}
System.out.println("\nFinal configuration:");
System.out.println(tram.configuration());
}
}

View File

@@ -7,5 +7,31 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module-library">
<library name="JUnit4">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.13.1/junit-4.13.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
<orderEntry type="module-library">
<library name="JUnit5.8.1">
<CLASSES>
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-5.8.1.jar!/" />
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-api-5.8.1.jar!/" />
<root url="jar://$MODULE_DIR$/lib/opentest4j-1.2.0.jar!/" />
<root url="jar://$MODULE_DIR$/lib/junit-platform-commons-1.8.1.jar!/" />
<root url="jar://$MODULE_DIR$/lib/apiguardian-api-1.1.2.jar!/" />
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-params-5.8.1.jar!/" />
<root url="jar://$MODULE_DIR$/lib/junit-jupiter-engine-5.8.1.jar!/" />
<root url="jar://$MODULE_DIR$/lib/junit-platform-engine-1.8.1.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</orderEntry>
</component>
</module>