From 59b6371966b8dd352c0d6f5291600e635ec428e9 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Loosen Date: Sat, 25 Oct 2025 17:14:37 +0200 Subject: [PATCH] Finish Java Tram implementation --- .../st/uap/w25/tram/AbstractMachine.java | 285 +++++++++++++++--- .../st/uap/w25/tram/AbstractMachineTests.java | 74 +++++ .../st/uap/w25/tram/LoggedMachine.java | 43 +++ .../src/de/unitrier/st/uap/w25/tram/Main.java | 16 +- .../uap25-pro01-tram/uap25-pro01-tram.iml | 26 ++ 5 files changed, 399 insertions(+), 45 deletions(-) create mode 100644 Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachineTests.java create mode 100644 Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/LoggedMachine.java diff --git a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachine.java b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachine.java index 9dc83ce..be564a7 100644 --- a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachine.java +++ b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachine.java @@ -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 stack; - protected List prog; + protected ArrayList stack; + protected ArrayList 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 executeDebug() { + ArrayList 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 results() { + ArrayList result = new ArrayList<>(); + for (int i = 0; i <= this.TOP; i++) { + result.add(this.stack.get(i)); + } + return result; } } diff --git a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachineTests.java b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachineTests.java new file mode 100644 index 0000000..1c024ad --- /dev/null +++ b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/AbstractMachineTests.java @@ -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."); + } +} diff --git a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/LoggedMachine.java b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/LoggedMachine.java new file mode 100644 index 0000000..dd622c7 --- /dev/null +++ b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/LoggedMachine.java @@ -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 executeDebug() { + logger.info("Debug execution started"); + var out = super.executeDebug(); + logger.info("Debug execution finished"); + return out; + } +} diff --git a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/Main.java b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/Main.java index c1d152d..f777331 100644 --- a/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/Main.java +++ b/Project-01/uap25-pro01-tram/src/de/unitrier/st/uap/w25/tram/Main.java @@ -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()); } } \ No newline at end of file diff --git a/Project-01/uap25-pro01-tram/uap25-pro01-tram.iml b/Project-01/uap25-pro01-tram/uap25-pro01-tram.iml index c90834f..5abe807 100644 --- a/Project-01/uap25-pro01-tram/uap25-pro01-tram.iml +++ b/Project-01/uap25-pro01-tram/uap25-pro01-tram.iml @@ -7,5 +7,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file