Finish Java Tram implementation
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user