diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 00b6d20..96d6674 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -14,17 +14,17 @@ jobs: contents: read steps: - - uses: actions/checkout@v4 - - name: Set up JDK 21 + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 25 uses: actions/setup-java@v4 with: - java-version: '21' + java-version: '25' distribution: 'temurin' - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - name: Setup Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + uses: gradle/actions/setup-gradle@v4 - name: Build with Gradle Wrapper run: ./gradlew build diff --git a/build.gradle b/build.gradle index 58c7532..85114e6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,10 @@ plugins { - id 'java' + id 'java-library' + id 'maven-publish' } -group = 'ee.ut.cs.sws' -version = '1.0-SNAPSHOT' +group = 'com.github.sandersirge' +version = 'v0.0.2-pre_release' repositories { mavenCentral() @@ -17,6 +18,30 @@ dependencies { // testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } +java { + sourceCompatibility = JavaVersion.toVersion(25) + targetCompatibility = JavaVersion.toVersion(25) + + withSourcesJar() +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release = 25 +} + //test { // useJUnitPlatform() //} + +publishing { + publications { + mavenJava(MavenPublication) { + from components.java + + groupId = project.group + artifactId = 'java-cma-bachelor-thesis' + version = project.version + } + } +} diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000..6cb9b16 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,3 @@ +before_install: + - sdk install java 25-tem + - sdk use java 25-tem diff --git a/src/main/java/ee/ut/cs/sws/cma/CMaInterpreter.java b/src/main/java/ee/ut/cs/sws/cma/CMaInterpreter.java index 0730939..f6e6617 100644 --- a/src/main/java/ee/ut/cs/sws/cma/CMaInterpreter.java +++ b/src/main/java/ee/ut/cs/sws/cma/CMaInterpreter.java @@ -5,12 +5,18 @@ import static ee.ut.cs.sws.cma.instruction.CMaBasicInstruction.Code.LOAD; import static ee.ut.cs.sws.cma.instruction.CMaBasicInstruction.Code.STORE; import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.LOADC; +import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.LOADM; +import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.STOREM; +import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.LOADRC; public class CMaInterpreter { private final CMaProgram program; - private int pc = 0; private final CMaStack stack; + private int pc = 0; + private int fp = 0; // Frame Pointer + private int ep = 0; // Extreme Pointer + private int hp = Integer.MAX_VALUE; // Heap Pointer private CMaInterpreter(CMaProgram program, CMaStack initialStack) { this.program = program; @@ -79,6 +85,18 @@ case CMaBasicInstruction(CMaBasicInstruction.Code code) -> { stack.set(arg, stack.peek()); } case HALT -> pc = -1; // out of range pc halts + case MARK -> { + // S[SP+1] = EP; S[SP+2] = FP; SP += 2 + stack.push(ep); + stack.push(fp); + } + case CALL -> { + // FP = SP; tmp = PC; PC = S[FP]; S[FP] = tmp + fp = stack.size() - 1; // SP = stack.size() - 1 + int tmp = pc; + pc = stack.get(fp); + stack.set(fp, tmp); + } } } case CMaIntInstruction(CMaIntInstruction.Code code, int arg) -> { @@ -92,16 +110,93 @@ case CMaIntInstruction(CMaIntInstruction.Code code, int arg) -> { execute(new CMaIntInstruction(LOADC, arg)); execute(new CMaBasicInstruction(STORE)); } + case ALLOC -> stack.allocate(arg); + case LOADRC -> stack.push(fp + arg); + case LOADM -> { + // LOADM m: S[SP+i] ← S[S[SP]+i] for i=m-1..0; SP ← SP + m - 1 + int sp = stack.size() - 1; + int target = stack.get(sp); // S[SP] = base address + stack.allocate(arg - 1); // expand: new SP = sp + arg - 1 + for (int i = arg - 1; i >= 0; i--) { + stack.set(sp + i, stack.get(target + i)); // S[SP+i] ← S[target+i] + } + } + case STOREM -> { + // STOREM m: S[S[SP]+i] ← S[SP-m+i] for i=0..m-1; eemalda aadress + int sp = stack.size() - 1; + int target = stack.get(sp); // S[SP] = destination address + for (int i = 0; i < arg; i++) { + stack.set(target + i, stack.get(sp - arg + i)); // S[target+i] ← S[SP-m+i] + } + } + case ENTER -> { + // EP = SP + m; kui EP >= HP, siis viga + ep = stack.size() - 1 + arg; + if (ep >= hp) + throw new CMaException("Stack Overflow: EP(%d) >= HP(%d)".formatted(ep, hp)); + } + case RETURN -> { + // PC = S[FP]; EP = S[FP-2]; kontrolli EP >= HP; + // SP = FP - q (truncate(FP - q + 1)); FP = S[FP-1] + pc = stack.get(fp); // taasta tagastusaadress + ep = stack.get(fp - 2); // taasta vana EP + if (ep >= hp) + throw new CMaException("Stack Overflow: EP(%d) >= HP(%d)".formatted(ep, hp)); + int newSp = fp - arg; // SP = FP - q + int newFp = stack.get(fp - 1); // taasta vana FP + stack.truncate(newSp + 1); // kärbi stack: size = SP + 1 + fp = newFp; + } } } + case CMaIntIntInstruction(CMaIntIntInstruction.Code code, int arg1, int arg2) -> { + switch (code) { + case SLIDE -> { + // SLIDE q m: nihuta m pealmist väärtust q positsiooni allapoole + // if (q > 0) + // if (m = 0) SP ← SP - q; + // else { SP ← SP-q-m; for (i←0; i 0) { + int sp = stack.size() - 1; + if (arg2 == 0) { + // m = 0: lihtsalt kärbi q pesa + stack.truncate(sp - arg1 + 1); // SP = SP - q, size = SP - q + 1 + } else { + // kopeeri m väärtust q positsiooni allapoole, siis kärbi + sp = sp - arg1 - arg2; + for (int i = 0; i < arg2; i++) { + sp++; + stack.set(sp, stack.get(sp + arg1)); // S[SP] ← S[SP+q] + } + stack.truncate(sp + 1); // SP = SP - q, size = SP - q + 1 + } + } + } + case LOADR -> { + // LOADR j m = LOADRC j; LOADM m + execute(new CMaIntInstruction(LOADRC, arg1)); + execute(new CMaIntInstruction(LOADM, arg2)); + } + case STORER -> { + // STORER j m = LOADRC j; STOREM m + execute(new CMaIntInstruction(LOADRC, arg1)); + execute(new CMaIntInstruction(STOREM, arg2)); + } + } + } case CMaLabelInstruction(CMaLabelInstruction.Code code, CMaLabel label) -> { switch (code) { + case LOADLC -> stack.push(getLabelTarget(label)); case JUMP -> pc = getLabelTarget(label); case JUMPZ -> { if (!CMaUtils.int2bool(stack.pop())) pc = getLabelTarget(label); } + case JUMPI -> { + // PC = target(label) + S[SP]; SP-- + pc = getLabelTarget(label) + stack.pop(); + } } } } diff --git a/src/main/java/ee/ut/cs/sws/cma/CMaProgramWriter.java b/src/main/java/ee/ut/cs/sws/cma/CMaProgramWriter.java index c7b560a..30e842a 100644 --- a/src/main/java/ee/ut/cs/sws/cma/CMaProgramWriter.java +++ b/src/main/java/ee/ut/cs/sws/cma/CMaProgramWriter.java @@ -3,6 +3,7 @@ import ee.ut.cs.sws.cma.instruction.CMaBasicInstruction; import ee.ut.cs.sws.cma.instruction.CMaInstruction; import ee.ut.cs.sws.cma.instruction.CMaIntInstruction; +import ee.ut.cs.sws.cma.instruction.CMaIntIntInstruction; import ee.ut.cs.sws.cma.instruction.CMaLabelInstruction; import java.util.ArrayList; @@ -27,6 +28,10 @@ public void visit(CMaIntInstruction.Code code, int arg) { visit(new CMaIntInstruction(code, arg)); } + public void visit(CMaIntIntInstruction.Code code, int arg1, int arg2) { + visit(new CMaIntIntInstruction(code, arg1, arg2)); + } + public void visit(CMaLabelInstruction.Code code, CMaLabel label) { visit(new CMaLabelInstruction(code, label)); } diff --git a/src/main/java/ee/ut/cs/sws/cma/CMaStack.java b/src/main/java/ee/ut/cs/sws/cma/CMaStack.java index 32ff142..002e35e 100644 --- a/src/main/java/ee/ut/cs/sws/cma/CMaStack.java +++ b/src/main/java/ee/ut/cs/sws/cma/CMaStack.java @@ -53,6 +53,17 @@ public int size() { return data.size(); } + /** Kasvata stacki m nulliga algväärtustatud pesa võrra. SP += m */ + public void allocate(int m) { + for (int i = 0; i < m; i++) + data.add(0); + } + + /** Kärbi stack uuele suurusele. SP = newSize - 1 */ + public void truncate(int newSize) { + data.subList(newSize, data.size()).clear(); + } + @Override public boolean equals(Object o) { if (this == o) diff --git a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaBasicInstruction.java b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaBasicInstruction.java index 6c9c32d..3d22fa9 100644 --- a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaBasicInstruction.java +++ b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaBasicInstruction.java @@ -37,6 +37,12 @@ public enum Code { /** seiska programm */ HALT, + + /** salvesta EP ja FP stackile (täitmisraami ettevalmistus) */ + MARK, + + /** kutsu funktsioon: FP=SP, vaheta PC ja S[FP] */ + CALL, //@formatter:on } diff --git a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaInstruction.java b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaInstruction.java index abaaf0c..7db0dc0 100644 --- a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaInstruction.java +++ b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaInstruction.java @@ -1,6 +1,6 @@ package ee.ut.cs.sws.cma.instruction; -public sealed interface CMaInstruction permits CMaBasicInstruction, CMaIntInstruction, CMaLabelInstruction { +public sealed interface CMaInstruction permits CMaBasicInstruction, CMaIntInstruction, CMaIntIntInstruction, CMaLabelInstruction { Code code(); diff --git a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntInstruction.java b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntInstruction.java index 659c2e6..0a1f469 100644 --- a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntInstruction.java +++ b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntInstruction.java @@ -12,6 +12,24 @@ public enum Code { /** salvesta väärtus indeksile */ STOREA, + + /** eralda m nulliga algväärtustatud pesa */ + ALLOC, + + /** lisa stackile FP + j (suhteline aadress) */ + LOADRC, + + /** loe m väärtust mälust aadressil S[SP], laienda stack m-1 pesa võrra */ + LOADM, + + /** kirjuta m väärtust S[SP-m..SP-1] mällu aadressile S[SP], eemalda aadress */ + STOREM, + + /** sea piirviit EP = SP + m, kontrolli EP >= HP */ + ENTER, + + /** taasta registrid ja puhasta täitmisraam, q = org. pesade + parameetrite arv */ + RETURN, //@formatter:on } diff --git a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntIntInstruction.java b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntIntInstruction.java new file mode 100644 index 0000000..69dca5d --- /dev/null +++ b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaIntIntInstruction.java @@ -0,0 +1,22 @@ +package ee.ut.cs.sws.cma.instruction; + +public record CMaIntIntInstruction(Code code, int arg1, int arg2) implements CMaInstruction { + + public enum Code { + //@formatter:off + /** nihuta m pealmist väärtust q positsiooni võrra allapoole */ + SLIDE, + + /** loe m väärtust FP-suhteliselt aadressilt j: LOADRC j; LOADM m */ + LOADR, + + /** salvesta m väärtust FP-suhtelisele aadressile j: LOADRC j; STOREM m */ + STORER, + //@formatter:on + } + + @Override + public String toString() { + return code.name() + " " + arg1 + " " + arg2; + } +} diff --git a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaLabelInstruction.java b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaLabelInstruction.java index 50f58bc..9a37879 100644 --- a/src/main/java/ee/ut/cs/sws/cma/instruction/CMaLabelInstruction.java +++ b/src/main/java/ee/ut/cs/sws/cma/instruction/CMaLabelInstruction.java @@ -6,11 +6,17 @@ public record CMaLabelInstruction(Code code, CMaLabel label) implements CMaInstr public enum Code { //@formatter:off + /** lae label väärtus pinule */ + LOADLC, + /** hüppa labelile */ JUMP, /** hüppa labelile kui stackipealne väärtus on 0 */ JUMPZ, + + /** indekseeritud hüpe: PC = target(label) + S[SP]; SP-- */ + JUMPI, //@formatter:on } diff --git a/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterIntegrationTest.java b/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterIntegrationTest.java new file mode 100644 index 0000000..78dfa92 --- /dev/null +++ b/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterIntegrationTest.java @@ -0,0 +1,674 @@ +package ee.ut.cs.sws.cma; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import static ee.ut.cs.sws.cma.instruction.CMaBasicInstruction.Code.*; +import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.*; +import static ee.ut.cs.sws.cma.instruction.CMaIntIntInstruction.Code.*; +import static ee.ut.cs.sws.cma.instruction.CMaLabelInstruction.Code.*; +import static org.junit.Assert.*; + +@FixMethodOrder(MethodSorters.JVM) +public class CMaInterpreterIntegrationTest { + + private CMaProgramWriter pw; + + @Before + public void setUp() { + pw = new CMaProgramWriter(); + } + + private void assertInterpreted(CMaStack expected) { + assertInterpreted(expected, new CMaStack()); + } + + private void assertInterpreted(CMaStack expected, CMaStack initial) { + CMaStack actual = CMaInterpreter.run(pw.toProgram(), initial); + assertEquals(expected, actual); + } + + // Funktsioonikutsed: Samm 9 — Integratsioonitestid kõikide käskude testimiseks erinevates töövoogudes + + /** + * Ehitab ja käivitab programmi, kus {@code run(n)} kutsub mitterekursiivset {@code inc(x)}. + * + *

C-kood:

+ *
{@code
+     * int inc(int x) { return x + 1; }
+     * int run(int n) {
+     *     int r = inc(n);
+     *     return r;
+     * }
+     * run() //  väärtustatakse igas testis erinevalt
+     * }
+ * + *

Oodatavad tulemused:

+ *
    + *
  • {@code n = 0} → {@code inc(0) = 1}
  • + *
  • {@code n = 9} → {@code inc(9) = 10}
  • + *
  • {@code n = 41} → {@code inc(41) = 42}
  • + *
  • {@code n = -1} → {@code inc(-1) = 0}
  • + *
+ */ + private void runNonRecursive(int n) { + pw = new CMaProgramWriter(); + + CMaLabel _run = new CMaLabel(); + CMaLabel _inc = new CMaLabel(); + + /* === Peaprogramm (indeksid 0-5): kutsub run(n) === */ + pw.visit(LOADC, n); // 0: muutuja 'n' väärtus, run parameeter + pw.visit(MARK); // 1: push EP, FP + pw.visit(LOADC, 6); // 2: _run aadress = 6 + pw.visit(CALL); // 3 + pw.visit(SLIDE, 0, 1); // 4: tõsta tulemus + pw.visit(HALT); // 5 + + /* + * === run(n) funktsioon (indeksid 6-17) === + * int run(int n) { int r = inc(n); return r; } + * Lokaalsed: muutuja 'r' on FP+1 + */ + pw.visit(_run); // label indeksil 6 + pw.visit(ENTER, 1); // 6: EP = SP + 1 (lokaalne muutuja 'r') + pw.visit(ALLOC, 1); // 7: eralda koht muutujale 'r' + + /* kutsu inc(muutuja 'n') */ + pw.visit(LOADR, -3, 1); // 8: laadi muutuja 'n' + pw.visit(MARK); // 9: push EP, FP + pw.visit(LOADC, 18); // 10: _inc aadress = 18 + pw.visit(CALL); // 11 + pw.visit(SLIDE, 0, 1); // 12: tõsta inc tulemus + + pw.visit(STORER, 1, 1); // 13: muutuja 'r' = inc(muutuja 'n') + pw.visit(POP); // 14: eemalda STORER duplikaat + + /* tagasta muutuja 'r' */ + pw.visit(LOADR, 1, 1); // 15: laadi muutuja 'r' + pw.visit(STORER, -3, 1); // 16: kirjuta tulemus parameetri pessa + pw.visit(RETURN, 3); // 17 + + /* + * === inc(x) funktsioon (indeksid 18-23) === + * int inc(int x) { return x + 1; } + */ + pw.visit(_inc); // label indeksil 18 + pw.visit(ENTER, 0); // 18: lokaalseid muutujaid pole + pw.visit(LOADR, -3, 1); // 19: laadi muutuja 'x' + pw.visit(LOADC, 1); // 20 + pw.visit(ADD); // 21: muutuja 'x' + 1 + pw.visit(STORER, -3, 1); // 22: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 23 + + assertInterpreted(new CMaStack(n + 1)); + } + + @Test + public void test_non_recursive_inc_0() { + /* run(0): inc(0) = 1 */ + runNonRecursive(0); + } + + @Test + public void test_non_recursive_inc_9() { + /* run(9): inc(9) = 10 */ + runNonRecursive(9); + } + + @Test + public void test_non_recursive_inc_41() { + /* run(41): inc(41) = 42 */ + runNonRecursive(41); + } + + @Test + public void test_non_recursive_inc_neg() { + /* run(-1): inc(-1) = 0 */ + runNonRecursive(-1); + } + + /** + * Rekursiivne {@code fac(n)} funktsioon, mida kutsub {@code run(n)}. + * + *

C-kood:

+ *
{@code
+     * int fac(int n) {
+     *     if (n <= 0) return 1;
+     *     return n * fac(n - 1);
+     * }
+     * int run(int n) {
+     *     int r = fac(n);
+     *     return r;
+     * }
+     * run() //  väärtustatakse igas testis erinevalt
+     * }
+ * + *

Oodatavad tulemused:

+ *
    + *
  • {@code n = 0} → {@code fac(0) = 1}
  • + *
  • {@code n = 1} → {@code fac(1) = 1}
  • + *
  • {@code n = 5} → {@code fac(5) = 120}
  • + *
  • {@code n = 10} → {@code fac(10) = 3628800}
  • + *
+ */ + private CMaStack runFac(int n) { + pw = new CMaProgramWriter(); + + CMaLabel _fac = new CMaLabel(); + CMaLabel _recurse = new CMaLabel(); + CMaLabel _run = new CMaLabel(); + + /* === Peaprogramm (indeksid 0-5): kutsub run(n) === */ + pw.visit(LOADC, n); // 0: muutuja 'n' väärtus, run parameetriks + pw.visit(MARK); // 1: push EP, FP + pw.visit(LOADC, 6); // 2: _run aadress = 6 + pw.visit(CALL); // 3: FP=3, PC=6, S[3]=4 + pw.visit(SLIDE, 0, 1); // 4: tõsta tulemus + pw.visit(HALT); // 5 + + /* + * === run(n) funktsioon (indeksid 6-17) === + * int run(int n) { int r = fac(n); return r; } + * Lokaalsed: muutuja 'r' on FP+1 + */ + pw.visit(_run); // label indeksil 6 + pw.visit(ENTER, 1); // 6: EP = SP + 1 (lokaalne muutuja 'r') + pw.visit(ALLOC, 1); // 7: eralda koht muutujale 'r' + + /* kutsu fac(muutuja 'n'): muutuja 'n' asub FP-3 */ + pw.visit(LOADR, -3, 1); // 8: laadi muutuja 'n' + pw.visit(MARK); // 9: push EP, FP + pw.visit(LOADC, 18); // 10: _fac aadress = 18 + pw.visit(CALL); // 11 + pw.visit(SLIDE, 0, 1); // 12: tõsta fac tulemus + + pw.visit(STORER, 1, 1); // 13: muutuja 'r' = fac(muutuja 'n') + pw.visit(POP); // 14: eemalda STORER duplikaat + + /* tagasta muutuja 'r': kopeeri muutuja 'r' parameetri pessa */ + pw.visit(LOADR, 1, 1); // 15: laadi muutuja 'r' + pw.visit(STORER, -3, 1); // 16: kirjuta tulemus parameetri pessa + pw.visit(RETURN, 3); // 17 + + /* === fac(n) funktsioon (indeksid 18-...) === */ + pw.visit(_fac); // label indeksil 18 + pw.visit(ENTER, 0); // 18: lokaalseid muutujaid pole + + /* kui muutuja 'n' <= 0, tagasta 1 */ + pw.visit(LOADR, -3, 1); // 19: laadi muutuja 'n' + pw.visit(LOADC, 0); // 20 + pw.visit(LEQ); // 21: muutuja 'n' <= 0? + pw.visit(JUMPZ, _recurse); // 22: kui n > 0, hüppa rekursiivsesse harusse + pw.visit(LOADC, 1); // 23: baassjuht: tulemus = 1 + pw.visit(STORER, -3, 1); // 24: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 25 + + /* rekursiivne juht: tagasta muutuja 'n' * fac(muutuja 'n' - 1) */ + pw.visit(_recurse); // label indeksil 26 + pw.visit(LOADR, -3, 1); // 26: laadi muutuja 'n' (korrutamiseks) + pw.visit(LOADR, -3, 1); // 27: laadi muutuja 'n' (fac argumendiks) + pw.visit(LOADC, 1); // 28 + pw.visit(SUB); // 29: muutuja 'n' - 1 + pw.visit(MARK); // 30: push EP, FP + pw.visit(LOADC, 18); // 31: _fac aadress = 18 + pw.visit(CALL); // 32 + pw.visit(SLIDE, 0, 1); // 33: tõsta fac tulemus + pw.visit(MUL); // 34: muutuja 'n' * fac(muutuja 'n' - 1) + pw.visit(STORER, -3, 1); // 35: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 36 + + return CMaInterpreter.run(pw.toProgram()); + } + + @Test + public void test_recursive_fac_0() { + /* run(0): fac(0) = 1 */ + assertEquals(new CMaStack(1), runFac(0)); + } + + @Test + public void test_recursive_fac_1() { + /* run(1): fac(1) = 1 */ + assertEquals(new CMaStack(1), runFac(1)); + } + + @Test + public void test_recursive_fac_5() { + /* run(5): fac(5) = 120 */ + assertEquals(new CMaStack(120), runFac(5)); + } + + @Test + public void test_recursive_fac_10() { + /* run(10): fac(10) = 3628800 */ + assertEquals(new CMaStack(3628800), runFac(10)); + } + + /** + * {@code inc(x, step)} funktsioon, mida kutsub lülituslausega {@code run(op)}. + * + *

C-kood:

+ *
{@code
+     * int inc(int x, int step) { return x + step; }
+     * int run(int op) {
+     *     int n = 5;
+     *     int r;
+     *     switch (op) {
+     *         case 0: r = inc(n, 1); break;  // LOADRC, MARK, CALL, SLIDE 0 1
+     *         case 1: r = n * 2;     break;  // LOADR, MUL, STORER
+     *     }
+     *     return r;
+     * }
+     * run() //  väärtustatakse igas testis erinevalt
+     * }
+ * + *

Oodatavad tulemused:

+ *
    + *
  • {@code op=0} → {@code inc(5, 1) = 6}
  • + *
  • {@code op=1} → {@code 5 * 2 = 10}
  • + *
+ */ + private CMaStack runDispatch(int op) { + pw = new CMaProgramWriter(); + + CMaLabel _inc = new CMaLabel(); + CMaLabel _run = new CMaLabel(); + CMaLabel _table = new CMaLabel(); + CMaLabel _case0 = new CMaLabel(); + CMaLabel _case1 = new CMaLabel(); + CMaLabel _end = new CMaLabel(); + + /* Peaprogramm (0–5): kutsub run(op). */ + pw.visit(LOADC, op); // 0: muutuja 'op' → parameeter run-ile + pw.visit(MARK); // 1: push EP(0), FP(0) + pw.visit(LOADC, 12); // 2: _run aadress = 12 + pw.visit(CALL); // 3: FP=3, PC=12, S[3]=4 + pw.visit(SLIDE, 0, 1); // 4: tõsta tulemus parameetri kohalt + pw.visit(HALT); // 5: peaprogramm lõpeb + + /* + * inc(muutuja 'x', muutuja 'step') (6–11): tagastab muutuja 'x' + muutuja 'step'. + * FP-4: muutuja 'x' (esimene arg), FP-3: muutuja 'step' (teine arg). + * RETURN 4 eemaldab muutuja 'step' pesa ja org-pesad (EP, FP, PC). + * Käsud: ENTER, LOADR, ADD, STORER, RETURN. + */ + pw.visit(_inc); + pw.visit(ENTER, 0); // 6: lokaalseid muutujaid pole; EP = SP+0 + pw.visit(LOADR, -4, 1); // 7: laadi muutuja 'x' (FP-4, esimene arg) + pw.visit(LOADR, -3, 1); // 8: laadi muutuja 'step' (FP-3, teine arg) + pw.visit(ADD); // 9: muutuja 'x' + muutuja 'step' + pw.visit(STORER, -4, 1); // 10: tulemus → muutuja 'x' pessa (FP-4) + pw.visit(RETURN, 4); // 11: eemaldab muutuja 'step' ja org-pesad + + /* + * run(muutuja 'op') (12–18): eraldab muutuja 'n'=5 ja muutuja 'r', valib haru JUMPI abil. + * Käsud: ENTER, ALLOC, STORER, LOADR, JUMPI. + */ + pw.visit(_run); + pw.visit(ENTER, 2); // 12: EP = SP+2 (2 lokaalset: n, r) + pw.visit(ALLOC, 2); // 13: eralda koht muutujatele 'n' ja 'r' + pw.visit(LOADC, 5); // 14: push 5 + pw.visit(STORER, 1, 1); // 15: muutuja 'n' = 5 (FP+1) + pw.visit(POP); // 16: eemalda STORER duplikaat + pw.visit(LOADR, -3, 1); // 17: laadi muutuja 'op' (FP-3) — JUMPI indeks + pw.visit(JUMPI, _table); // 18: PC = target(_table) + op + + /* + * Lülituslause harud (19–20): JUMPI sihtmärgid. + * muutuja 'op'=0 → _case0 (21), muutuja 'op'=1 → _case1 (31). + */ + pw.visit(_table); + pw.visit(JUMP, _case0); // 19: op=0 → kutsu inc(n, 1) + pw.visit(JUMP, _case1); // 20: op=1 → arvuta n*2 + + /* + * case 0 (21–30): muutuja 'r' = inc(muutuja 'n', 1). + * Laadi muutuja 'n' (LOADRC+LOAD) ja muutuja 'step'=1, kutsu inc. + * SLIDE 0 1 on no-op — tulemus on pärast RETURN 4 juba pinul. + * Käsud: LOADRC, LOAD, LOADC, MARK, CALL, SLIDE 0 1, STORER. + */ + pw.visit(_case0); + pw.visit(LOADRC, 1); // 21: laadi muutuja 'n' aadress (FP+1) — LOADRC otse + pw.visit(LOAD); // 22: loe muutuja 'n' väärtus + pw.visit(LOADC, 1); // 23: muutuja 'step'=1 (teine arg inc-ile) + pw.visit(MARK); // 24: push EP, FP + pw.visit(LOADC, 6); // 25: _inc aadress = 6 + pw.visit(CALL); // 26: FP=10, PC=6, S[10]=27 + pw.visit(SLIDE, 0, 1); // 27: tulemus juba pinul (no-op) + pw.visit(STORER, 2, 1); // 28: muutuja 'r' = tulemus (FP+2) + pw.visit(POP); // 29: eemalda STORER duplikaat + pw.visit(JUMP, _end); // 30: hüppa tagastusele + + /* + * case 1 (31–35): muutuja 'r' = muutuja 'n' * 2. + * Käsud: LOADR, LOADC, MUL, STORER. + */ + pw.visit(_case1); + pw.visit(LOADR, 1, 1); // 31: laadi muutuja 'n' (FP+1) + pw.visit(LOADC, 2); // 32: push 2 + pw.visit(MUL); // 33: muutuja 'n' * 2 = 10 + pw.visit(STORER, 2, 1); // 34: muutuja 'r' = muutuja 'n'*2 (FP+2) + pw.visit(POP); // 35: eemalda STORER duplikaat + + /* + * Tagastus (36–38): return r. + * Käsud: LOADR, STORER, RETURN. + */ + pw.visit(_end); + pw.visit(LOADR, 2, 1); // 36: laadi muutuja 'r' (FP+2) + pw.visit(STORER, -3, 1); // 37: tulemus → parameetri pessa (FP-3) + pw.visit(RETURN, 3); // 38: tagasta + + return CMaInterpreter.run(pw.toProgram()); + } + + @Test + public void test_dispatch_inc() { + /* run(0): inc(5, 1) = 6 — kasutab LOADRC, MARK, CALL, SLIDE 0 1 */ + assertEquals(new CMaStack(6), runDispatch(0)); + } + + @Test + public void test_dispatch_double() { + /* run(1): 5 * 2 = 10 — kasutab LOADR, MUL, STORER */ + assertEquals(new CMaStack(10), runDispatch(1)); + } + + /** + * {@code void compute(x)} funktsioon lokaalsete muutujatega, mida kutsub {@code run(n)}. + * + *

C-kood:

+ *
{@code
+     * void compute(int x) {
+     *     int a = x * 2;
+     *     int b = a + 1;
+     *     return;
+     * }
+     * int run(int n) {
+     *     compute(n);
+     *     return n * 2;
+     * }
+     * run() //  väärtustatakse igas testis erinevalt
+     * }
+ * + *

Oodatavad tulemused:

+ *
    + *
  • {@code n = 0} → {@code 0 * 2 = 0}
  • + *
  • {@code n = 3} → {@code 3 * 2 = 6}
  • + *
  • {@code n = -4} → {@code -4 * 2 = -8}
  • + *
+ * + *

Programmi aadressid:

+ *
    + *
  • {@code 0–5}: peaprogramm
  • + *
  • {@code 6–18}: {@code compute(x)}
  • + *
  • {@code 19–28}: {@code run(n)}
  • + *
+ */ + private void runVoidReturn(int n) { + pw = new CMaProgramWriter(); + + CMaLabel _compute = new CMaLabel(); + CMaLabel _run = new CMaLabel(); + + /* === Peaprogramm (indeksid 0-5): kutsub run(n) === */ + pw.visit(LOADC, n); // 0: muutuja 'n' väärtus, run parameeter + pw.visit(MARK); // 1: push EP, FP + pw.visit(LOADC, 19); // 2: _run aadress = 19 + pw.visit(CALL); // 3 + pw.visit(SLIDE, 0, 1); // 4: tõsta tulemus + pw.visit(HALT); // 5 + + /* + * === compute(x) funktsioon (indeksid 6-18) === + * void compute(int x) { + * int a = x * 2; + * int b = a + 1; + * return; + * } + * Lokaalsed: muutuja 'a' on FP+1, muutuja 'b' on FP+2. + * Tagastustüüp void: RETURN 4 (q = 1 + 3 - 0 = 4). + */ + pw.visit(_compute); // label indeksil 6 + pw.visit(ENTER, 2); // 6: EP = SP + 2 (2 lokaalset: a, b) + pw.visit(ALLOC, 2); // 7: eralda koht muutujatele 'a' ja 'b' + + /* muutuja 'a' = muutuja 'x' * 2 */ + pw.visit(LOADR, -3, 1); // 8: laadi muutuja 'x' (FP-3) + pw.visit(LOADC, 2); // 9 + pw.visit(MUL); // 10: muutuja 'x' * 2 + pw.visit(STORER, 1, 1); // 11: muutuja 'a' = muutuja 'x' * 2 (FP+1) + pw.visit(POP); // 12: eemalda STORER duplikaat + + /* muutuja 'b' = muutuja 'a' + 1 */ + pw.visit(LOADR, 1, 1); // 13: laadi muutuja 'a' (FP+1) + pw.visit(LOADC, 1); // 14 + pw.visit(ADD); // 15: muutuja 'a' + 1 + pw.visit(STORER, 2, 1); // 16: muutuja 'b' = muutuja 'a' + 1 (FP+2) + pw.visit(POP); // 17: eemalda STORER duplikaat + + pw.visit(RETURN, 4); // 18: void tagastus, eemaldab param + lokaalsed + org-pesad + + /* + * === run(n) funktsioon (indeksid 19-28) === + * int run(int n) { compute(n); return n * 2; } + * Lokaalseid muutujaid pole. + */ + pw.visit(_run); // label indeksil 19 + pw.visit(ENTER, 0); // 19: lokaalseid muutujaid pole + + /* kutsu compute(muutuja 'n') */ + pw.visit(LOADR, -3, 1); // 20: laadi muutuja 'n' + pw.visit(MARK); // 21: push EP, FP + pw.visit(LOADC, 6); // 22: _compute aadress = 6 + pw.visit(CALL); // 23 + /* void tagastus: pinu on taastatud, SLIDE pole vaja */ + + /* tagasta muutuja 'n' * 2 */ + pw.visit(LOADR, -3, 1); // 24: laadi muutuja 'n' + pw.visit(LOADC, 2); // 25 + pw.visit(MUL); // 26: muutuja 'n' * 2 + pw.visit(STORER, -3, 1); // 27: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 28 + + assertInterpreted(new CMaStack(n * 2)); + } + + @Test + public void test_void_return_0() { + /* run(0): compute(0); return 0*2 = 0 */ + runVoidReturn(0); + } + + @Test + public void test_void_return_3() { + /* run(3): compute(3); return 3*2 = 6 */ + runVoidReturn(3); + } + + @Test + public void test_void_return_neg() { + /* run(-4): compute(-4); return -4*2 = -8 */ + runVoidReturn(-4); + } + + private static int fibExpected(int n) { + if (n < 0) return -1; + if (n <= 1) return n; + int a = 0, b = 1; + for (int i = 2; i <= n; i++) { int c = a + b; a = b; b = c; } + return b; + } + + /** + * Rekursiivne {@code fibo(n)} funktsioon koos lülituslausega, mida kutsub {@code run(n)}. + * + *

C-kood (vt joonis 2.x):

+ *
{@code
+     * int fibo(int n) {
+     *     if (n < 0) return -1;
+     *     switch (n) {
+     *         case 0:  return 0; break;
+     *         case 1:  return 1; break;
+     *         default: return fibo(n - 1) + fibo(n - 2);
+     *     }
+     * }
+     * int run(int n) {
+     *     int r = fibo(n);
+     *     return r;
+     * }
+     * run() //  väärtustatakse igas testis erinevalt
+     * }
+ * + *

Oodatavad tulemused:

+ *
    + *
  • {@code n < 0} → {@code -1}
  • + *
  • {@code n = 0} → {@code 0}
  • + *
  • {@code n = 1} → {@code 1}
  • + *
  • {@code n = 5} → {@code 5}
  • + *
  • {@code n = 7} → {@code 13}
  • + *
+ */ + private void runFibo(int n) { + pw = new CMaProgramWriter(); + + CMaLabel _fibo = new CMaLabel(); + CMaLabel _run = new CMaLabel(); + CMaLabel _non_neg = new CMaLabel(); + CMaLabel _table = new CMaLabel(); + CMaLabel _case0 = new CMaLabel(); + CMaLabel _case1 = new CMaLabel(); + CMaLabel _recurse = new CMaLabel(); + + /* === Peaprogramm (indeksid 0-5): kutsub run(n) === */ + pw.visit(LOADC, n); // 0: muutuja 'n' väärtus, run parameeter + pw.visit(MARK); // 1: push EP, FP + pw.visit(LOADC, 6); // 2: _run aadress = 6 + pw.visit(CALL); // 3 + pw.visit(SLIDE, 0, 1); // 4: tõsta tulemus + pw.visit(HALT); // 5 + + /* + * === run(n) funktsioon (indeksid 6-17) === + * int run(int n) { int r = fibo(n); return r; } + * Lokaalsed: muutuja 'r' on FP+1 + */ + pw.visit(_run); // label indeksil 6 + pw.visit(ENTER, 1); // 6: EP = SP + 1 (lokaalne muutuja 'r') + pw.visit(ALLOC, 1); // 7: eralda koht muutujale 'r' + + /* kutsu fibo(muutuja 'n') */ + pw.visit(LOADR, -3, 1); // 8: laadi muutuja 'n' + pw.visit(MARK); // 9: push EP, FP + pw.visit(LOADC, 18); // 10: _fibo aadress = 18 + pw.visit(CALL); // 11 + pw.visit(SLIDE, 0, 1); // 12: tõsta fibo tulemus + + pw.visit(STORER, 1, 1); // 13: muutuja 'r' = fibo(muutuja 'n') + pw.visit(POP); // 14: eemalda STORER duplikaat + + /* tagasta muutuja 'r' */ + pw.visit(LOADR, 1, 1); // 15: laadi muutuja 'r' + pw.visit(STORER, -3, 1); // 16: kirjuta tulemus parameetri pessa + pw.visit(RETURN, 3); // 17 + + /* + * === fibo(n) funktsioon (indeksid 18-56) === + * Baassjuhud n<0, n=0, n=1 — rekursiivne juht: fibo(n-1) + fibo(n-2). + */ + pw.visit(_fibo); // label indeksil 18 + pw.visit(ENTER, 0); // 18: lokaalseid muutujaid pole + + /* kui n < 0, tagasta -1 */ + pw.visit(LOADC, 0); // 19: push 0 + pw.visit(LOADR, -3, 1); // 20: laadi muutuja 'n' + pw.visit(GR); // 21: 0 > n, ehk n < 0? + pw.visit(JUMPZ, _non_neg); // 22: kui n >= 0, hüppa edasi + pw.visit(LOADC, -1); // 23: tulemus = -1 + pw.visit(STORER, -3, 1); // 24: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 25 + + /* lülituslause: n=0, n=1 → baassjuhud; n>1 → rekursiivne juht */ + pw.visit(_non_neg); // label indeksil 26 + pw.visit(LOADR, -3, 1); // 26: laadi muutuja 'n' + pw.visit(LOADC, 1); // 27 + pw.visit(LEQ); // 28: n <= 1? + pw.visit(JUMPZ, _recurse); // 29: kui n > 1, hüppa rekursiivsesse harusse + + /* JUMPI lülituslause harud: n=0 → _case0, n=1 → _case1 */ + pw.visit(LOADR, -3, 1); // 30: laadi muutuja 'n' (JUMPI indeks) + pw.visit(JUMPI, _table); // 31: PC = target(_table) + n + + pw.visit(_table); // label indeksil 32 + pw.visit(JUMP, _case0); // 32: n=0 → tagasta 0 + pw.visit(JUMP, _case1); // 33: n=1 → tagasta 1 + + pw.visit(_case0); // label indeksil 34 + pw.visit(LOADC, 0); // 34: tulemus = 0 + pw.visit(STORER, -3, 1); // 35: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 36 + + pw.visit(_case1); // label indeksil 37 + pw.visit(LOADC, 1); // 37: tulemus = 1 + pw.visit(STORER, -3, 1); // 38: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 39 + + /* rekursiivne juht: fibo(n-1) + fibo(n-2) */ + pw.visit(_recurse); // label indeksil 40 + pw.visit(LOADR, -3, 1); // 40: laadi muutuja 'n' (fibo(n-1) argumendiks) + pw.visit(LOADC, 1); // 41 + pw.visit(SUB); // 42: muutuja 'n' - 1 + pw.visit(MARK); // 43: push EP, FP + pw.visit(LOADC, 18); // 44: _fibo aadress = 18 + pw.visit(CALL); // 45 + pw.visit(SLIDE, 0, 1); // 46: fibo(n-1) tulemus pinul + + /* fibo(n-1) on pinul — arvuta fibo(n-2) */ + pw.visit(LOADR, -3, 1); // 47: laadi muutuja 'n' (FP taastatud) + pw.visit(LOADC, 2); // 48 + pw.visit(SUB); // 49: muutuja 'n' - 2 + pw.visit(MARK); // 50: push EP, FP + pw.visit(LOADC, 18); // 51: _fibo aadress = 18 + pw.visit(CALL); // 52 + pw.visit(SLIDE, 0, 1); // 53: fibo(n-2) tulemus pinul + + pw.visit(ADD); // 54: fibo(n-1) + fibo(n-2) + pw.visit(STORER, -3, 1); // 55: tulemus → parameetri pessa + pw.visit(RETURN, 3); // 56 + + assertInterpreted(new CMaStack(fibExpected(n))); + } + + @Test + public void test_fibo_neg() { + /* run(-1): fibo(-1) = -1 */ + runFibo(-1); + } + + @Test + public void test_fibo_0() { + /* run(0): fibo(0) = 0 */ + runFibo(0); + } + + @Test + public void test_fibo_1() { + /* run(1): fibo(1) = 1 */ + runFibo(1); + } + + @Test + public void test_fibo_5() { + /* run(5): fibo(5) = 5 */ + runFibo(5); + } + + @Test + public void test_fibo_7() { + /* run(7): fibo(7) = 13 */ + runFibo(7); + } +} diff --git a/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterTest.java b/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterTest.java index 4fc6223..c4c946d 100644 --- a/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterTest.java +++ b/src/test/java/ee/ut/cs/sws/cma/CMaInterpreterTest.java @@ -7,6 +7,7 @@ import static ee.ut.cs.sws.cma.instruction.CMaBasicInstruction.Code.*; import static ee.ut.cs.sws.cma.instruction.CMaIntInstruction.Code.*; +import static ee.ut.cs.sws.cma.instruction.CMaIntIntInstruction.Code.*; import static ee.ut.cs.sws.cma.instruction.CMaLabelInstruction.Code.*; import static org.junit.Assert.*; @@ -216,4 +217,388 @@ public void test_multiple_label() { pw.visit(JUMP, _label); pw.visit(_label); } + + // Funktsioonikutsed: Samm 1 — ALLOC käsu testimine + + @Test + public void test_alloc_empty() { + pw.visit(ALLOC, 3); + + assertInterpreted(new CMaStack(0, 0, 0)); + } + + @Test + public void test_alloc_after_loadc() { + pw.visit(LOADC, 5); + pw.visit(ALLOC, 2); + + assertInterpreted(new CMaStack(5, 0, 0)); + } + + // Funktsioonikutsed: Samm 3 — LOADRC, LOADR, STORER, LOADM ja STOREM käskude testimine + + @Test + public void test_loadrc() { + /* fp = 0 (vaikeväärtus), LOADRC 3 → push(0 + 3) = 3 */ + pw.visit(LOADRC, 3); + + assertInterpreted(new CMaStack(3)); + } + + @Test + public void test_loadr() { + /* + * stack: [10, 20, 30], fp = 0 (vaikeväärtus) + * LOADR 2 1 → LOADRC 2; LOADM 1 → push(fp + 2) = push(2), load 1 väärtus: push(stack[2]) = 30 + */ + pw.visit(LOADR, 2, 1); + + assertInterpreted(new CMaStack(10, 20, 30, 30), new CMaStack(10, 20, 30)); + } + + @Test + public void test_storer() { + /* + * stack: [10, 20, 30], fp = 0 (vaikeväärtus) + * LOADC 99; STORER 1 1 → LOADRC 1; STOREM 1 + * LOADRC 1: push(fp + 1) = push(1) → [10, 20, 30, 99, 1] + * STOREM 1: stack[1] = stack[SP-1] = 99, SP ei muutu → [10, 99, 30, 99, 1] + */ + pw.visit(LOADC, 99); + pw.visit(STORER, 1, 1); + + assertInterpreted(new CMaStack(10, 99, 30, 99, 1), new CMaStack(10, 20, 30)); + } + + @Test + public void test_loadm() { + /* + * LOADM 2: lae 2 väärtust aadressilt S[SP] + * stack enne: [10, 20, 30, 1] (addr = 1, loeb stack[1] ja stack[2]) + * Tsükkel tagurpidi: + * i=1: S[SP+1] ← S[S[SP]+1] → S[4] ← S[2] = 30 (laiendab stacki) + * i=0: S[SP+0] ← S[S[SP]+0] → S[3] ← S[1] = 20 (kirjutab üle aadressi) + * SP ← SP + 2 - 1 = SP + 1 + * stack pärast: [10, 20, 30, 20, 30] + */ + pw.visit(LOADC, 10); + pw.visit(LOADC, 20); + pw.visit(LOADC, 30); + pw.visit(LOADC, 1); // aadress: stack[1..2] = [20, 30] + pw.visit(LOADM, 2); + + assertInterpreted(new CMaStack(10, 20, 30, 20, 30)); + } + + @Test + public void test_storem() { + /* + * STOREM 2: kirjuta 2 väärtust aadressile S[SP], SP ei muutu + * stack enne: [0, 0, 7, 8, 0] (addr = 0, kirjutab stack[2] ja stack[3] → stack[0] ja stack[1]) + * i=0: S[S[SP]+0] ← S[SP-2+0] → S[0] ← S[2] = 7 + * i=1: S[S[SP]+1] ← S[SP-2+1] → S[1] ← S[3] = 8 + * aadress (0) jääb pinule → SP ei muutu + * stack pärast: [7, 8, 7, 8, 0] + */ + pw.visit(STOREM, 2); + + assertInterpreted(new CMaStack(7, 8, 7, 8, 0), new CMaStack(0, 0, 7, 8, 0)); + } + + // Funktsioonikutsed: Samm 4 — MARK ja CALL käskude testimine + + @Test + public void test_mark_call() { + /* + * Stack enne: [] + * LOADC 42 → [42] (parameeter) + * MARK → [42, 0, 0] (push EP=0, push FP=0) + * LOADC 5 → [42, 0, 0, 5] (funktsiooni aadress) + * CALL → FP=3, PC=5, S[3]=4 → stack: [42, 0, 0, 4] + * indeks 4: HALT (tagastuspunkt, siia ei jõua) + * indeks 5: HALT (funktsioon peatub kohe) + */ + CMaLabel _func = new CMaLabel(); + + pw.visit(LOADC, 42); // 0: parameeter + pw.visit(MARK); // 1: push EP(0), push FP(0) + pw.visit(LOADC, 5); // 2: funktsiooni aadress (indeks 5) + pw.visit(CALL); // 3: FP=3, PC=5, S[3]=4 (tagastusaadress) + pw.visit(HALT); // 4: tagastuspunkt + pw.visit(_func); // 5: funktsiooni algus + pw.visit(HALT); // 5: funktsioon peatub kohe + + /* Stack pärast: [42, 0, 0, 4] — parameeter, salv. EP, FP, tagastusaadress */ + assertInterpreted(new CMaStack(42, 0, 0, 4)); + } + + // Funktsioonikutsed: Samm 5 — ENTER käsu testimine + + @Test + public void test_enter() { + /* + * stack: [10, 20], ENTER 5 → EP = (2-1) + 5 = 6 + * MARK → push EP(6), push FP(0) → stack: [10, 20, 6, 0] + * Kontrollime EP väärtust läbi MARK käsu + */ + pw.visit(ENTER, 5); + pw.visit(MARK); + + assertInterpreted(new CMaStack(10, 20, 6, 0), new CMaStack(10, 20)); + } + + @Test + public void test_enter_in_function() { + /* + * MARK/CALL/ENTER tsükkel: funktsioon kutsub ja ENTER seab EP + * LOADC 42 → [42] + * MARK → [42, 0, 0] + * LOADC 5 → [42, 0, 0, 5] (funktsiooni aadress, _func label indeksil 5) + * CALL → FP=3, PC=5, S[3]=4 → [42, 0, 0, 4] + * HALT (tagastuspunkt, indeks 4) + * _func (indeks 5): ENTER 3 → EP = (4-1) + 3 = 6 + * MARK → push EP(6), push FP(3) → [42, 0, 0, 4, 6, 3] + * HALT + */ + CMaLabel _func = new CMaLabel(); + + pw.visit(LOADC, 42); // 0: parameeter + pw.visit(MARK); // 1: push EP(0), push FP(0) + pw.visit(LOADC, 5); // 2: funktsiooni aadress (indeks 5) + pw.visit(CALL); // 3: FP=3, PC=5, S[3]=4 + pw.visit(HALT); // 4: tagastuspunkt + + pw.visit(_func); // label indeksil 5 + pw.visit(ENTER, 3); // 5: EP = (4-1) + 3 = 6 + pw.visit(MARK); // 6: push EP(6), push FP(3) + pw.visit(HALT); // 7: funktsioon peatub + + /* Stack pärast: [42, 0, 0, 4, 6, 3] */ + assertInterpreted(new CMaStack(42, 0, 0, 4, 6, 3)); + } + + // Funktsioonikutsed: Samm 6 — RETURN käsu testimine + + @Test + public void test_call_return() { + /* + * Täielik kutse-tagastus tsükkel: + * Peaprogramm kutsub funktsiooni, mis kohe tagastab. + * + * 0: LOADC 42 → [42] (parameeter) + * 1: MARK → [42, 0, 0] (push EP=0, FP=0) + * 2: LOADC 6 → [42, 0, 0, 6] (funktsiooni aadress) + * 3: CALL → FP=3, PC=6, S[3]=4 → [42, 0, 0, 4] + * 4: HALT (tagastuspunkt) + * + * _func (indeks 5): + * 5: ENTER 0 → EP = (4-1)+0 = 3 + * 6: RETURN 3 → PC=S[3]=4, EP=S[1]=0, FP=S[2]=0 + * SP = 3-3 = 0, truncate(1) → [42] + * PC=4 → jõuab HALT-i + */ + CMaLabel _func = new CMaLabel(); + + pw.visit(LOADC, 42); // 0: parameeter + pw.visit(MARK); // 1: push EP(0), push FP(0) + pw.visit(LOADC, 6); // 2: funktsiooni aadress + pw.visit(CALL); // 3: FP=3, PC=5, S[3]=4 + pw.visit(HALT); // 4: tagastuspunkt + + pw.visit(_func); // label indeksil 5 + pw.visit(ENTER, 0); // 5: EP = 3 + pw.visit(RETURN, 3); // 6: taasta ja kärbi, q=3 (EP+FP+PC) + + /* Pärast RETURN: stack = [42], PC=4 → HALT */ + assertInterpreted(new CMaStack(42)); + } + + @Test + public void test_call_return_with_locals() { + /* + * Funktsioon eraldab lokaalse muutuja, kirjutab sinna ja tagastab. + * + * 0: LOADC 10 → [10] (parameeter) + * 1: MARK → [10, 0, 0] + * 2: LOADC 5 → [10, 0, 0, 5] + * 3: CALL → FP=3, PC=5, S[3]=4 → [10, 0, 0, 4] + * 4: HALT + * + * _func (indeks 5): + * 5: ENTER 1 → EP = (4-1)+1 = 4 + * 6: ALLOC 1 → [10, 0, 0, 4, 0] (lokaalne muutuja indeksil 4) + * 7: LOADC 99 + * 8: STORER 1 1 → stack[FP+1]=stack[4]=99 → [10, 0, 0, 4, 99, 99] + * 9: POP → [10, 0, 0, 4, 99] + * 10: RETURN 3 → PC=4, EP=0, SP=3-3=0, FP=0, truncate(1) → [10] + */ + CMaLabel _func = new CMaLabel(); + + pw.visit(LOADC, 10); // 0: parameeter + pw.visit(MARK); // 1 + pw.visit(LOADC, 5); // 2: funktsiooni aadress + pw.visit(CALL); // 3 + pw.visit(HALT); // 4 + + pw.visit(_func); // label indeksil 5 + pw.visit(ENTER, 1); // 5 + pw.visit(ALLOC, 1); // 6: lokaalne muutuja + pw.visit(LOADC, 99); // 7 + pw.visit(STORER, 1, 1); // 8: kirjuta lokaalsesse muutujasse + pw.visit(POP); // 9 + pw.visit(RETURN, 3); // 10: tagasta, q=3 + + /* Pärast RETURN: stack = [10], PC=4 → HALT */ + assertInterpreted(new CMaStack(10)); + } + + // Funktsioonikutsed: Samm 7 — SLIDE käsu testimine + + @Test + public void test_slide() { + /* + * stack: [10, 20, 30], SLIDE 2 1 → kopeeri 1 väärtus (30) 2 positsiooni allapoole + * S[SP-2-1+1] = S[SP-1+1] → S[0] = S[2] = 30 → stack: [30, 20, 30] + * truncate(SP-2+1) = truncate(1) → stack: [30] + */ + pw.visit(LOADC, 10); + pw.visit(LOADC, 20); + pw.visit(LOADC, 30); + pw.visit(SLIDE, 2, 1); + + assertInterpreted(new CMaStack(30)); + } + + @Test + public void test_slide_multiple_values() { + /* + * stack: [1, 2, 3, 4, 5], SLIDE 2 2 → kopeeri 2 väärtust (4, 5) 2 positsiooni allapoole + * S[SP-2-2+1] = S[SP-2+1] → S[1] = S[3] = 4 + * S[SP-2-2+2] = S[SP-2+2] → S[2] = S[4] = 5 + * truncate(SP-2+1) = truncate(3) → stack: [1, 4, 5] + */ + pw.visit(LOADC, 1); + pw.visit(LOADC, 2); + pw.visit(LOADC, 3); + pw.visit(LOADC, 4); + pw.visit(LOADC, 5); + pw.visit(SLIDE, 2, 2); + + assertInterpreted(new CMaStack(1, 4, 5)); + } + + @Test + public void test_slide_zero() { + /* stack: [10, 20], SLIDE 0 1 → q=0, nihkumist pole, truncate(SP+1) → muutumatu */ + pw.visit(LOADC, 10); + pw.visit(LOADC, 20); + pw.visit(SLIDE, 0, 1); + + assertInterpreted(new CMaStack(10, 20)); + } + + @Test + public void test_slide_m_zero() { + /* + * stack: [10, 20, 30], SLIDE 2 0 → m=0, tagastusväärtust pole, kärbi q pesa + * SP = SP - q = 2 - 2 = 0 → truncate(1) → [10] + */ + pw.visit(LOADC, 10); + pw.visit(LOADC, 20); + pw.visit(LOADC, 30); + pw.visit(SLIDE, 2, 0); + + assertInterpreted(new CMaStack(10)); + } + + // Funktsioonikutsed: Samm 8 — JUMPI käsu testimine + + @Test + public void test_jumpi() { + /* + * Lülituslause harud: + * väärtus 0 → case0 + * väärtus 1 → case1 + * väärtus 2 → case2 + * Igale lülituslause harule vastavad väärtused laetakse stackile. + */ + CMaLabel _table = new CMaLabel(); + CMaLabel _case0 = new CMaLabel(); + CMaLabel _case1 = new CMaLabel(); + CMaLabel _case2 = new CMaLabel(); + + /* Programm: LOADC index; JUMPI _table; _table: JUMP _case0; JUMP _case1; JUMP _case2 */ + pw.visit(LOADC, 0); // 0: indeks + pw.visit(JUMPI, _table); // 1: PC = target(_table) + 0 = 2 + pw.visit(_table); // indeks 2 + pw.visit(JUMP, _case0); // 2: hüppa case0-le + pw.visit(JUMP, _case1); // 3: hüppa case1-le + pw.visit(JUMP, _case2); // 4: hüppa case2-le + pw.visit(_case0); + pw.visit(LOADC, 100); + pw.visit(HALT); + pw.visit(_case1); + pw.visit(LOADC, 200); + pw.visit(HALT); + pw.visit(_case2); + pw.visit(LOADC, 300); + pw.visit(HALT); + + /* Indeks 0 → case0 → stack: [100] */ + assertInterpreted(new CMaStack(100)); + } + + @Test + public void test_jumpi_index1() { + CMaLabel _table = new CMaLabel(); + CMaLabel _case0 = new CMaLabel(); + CMaLabel _case1 = new CMaLabel(); + CMaLabel _case2 = new CMaLabel(); + + pw.visit(LOADC, 1); // 0: indeks + pw.visit(JUMPI, _table); // 1: PC = target(_table) + 1 = 3 + pw.visit(_table); + pw.visit(JUMP, _case0); // 2: hüppa case0-le + pw.visit(JUMP, _case1); // 3: hüppa case1-le + pw.visit(JUMP, _case2); // 4: hüppa case2-le + pw.visit(_case0); + pw.visit(LOADC, 100); + pw.visit(HALT); + pw.visit(_case1); + pw.visit(LOADC, 200); + pw.visit(HALT); + pw.visit(_case2); + pw.visit(LOADC, 300); + pw.visit(HALT); + + /* Indeks 1 → case1 → stack: [200] */ + assertInterpreted(new CMaStack(200)); + } + + @Test + public void test_jumpi_index2() { + CMaLabel _table = new CMaLabel(); + CMaLabel _case0 = new CMaLabel(); + CMaLabel _case1 = new CMaLabel(); + CMaLabel _case2 = new CMaLabel(); + + pw.visit(LOADC, 2); // 0: indeks + pw.visit(JUMPI, _table); // 1: PC = target(_table) + 2 = 4 + pw.visit(_table); + pw.visit(JUMP, _case0); // 2: hüppa case0-le + pw.visit(JUMP, _case1); // 3: hüppa case1-le + pw.visit(JUMP, _case2); // 4: hüppa case2-le + pw.visit(_case0); + pw.visit(LOADC, 100); + pw.visit(HALT); + pw.visit(_case1); + pw.visit(LOADC, 200); + pw.visit(HALT); + pw.visit(_case2); + pw.visit(LOADC, 300); + pw.visit(HALT); + + /* Indeks 2 → case2 → stack: [300] */ + assertInterpreted(new CMaStack(300)); + } }