From e1c004bd387b257167acb60aeb6228098d1d3e22 Mon Sep 17 00:00:00 2001 From: zeroflag Date: Sat, 7 Aug 2021 16:03:12 +0200 Subject: [PATCH] forth wizard / stack juggler --- src/main/java/com/vectron/fcl/Juggler.java | 325 ++++++++++++++++++ src/main/res/raw/core.forth | 2 +- src/test/java/com/vectron/fcl/FclTest.java | 1 + .../java/com/vectron/fcl/JugglerTest.java | 199 +++++++++++ 4 files changed, 526 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/vectron/fcl/Juggler.java create mode 100644 src/test/java/com/vectron/fcl/JugglerTest.java diff --git a/src/main/java/com/vectron/fcl/Juggler.java b/src/main/java/com/vectron/fcl/Juggler.java new file mode 100644 index 0000000..0a40b5b --- /dev/null +++ b/src/main/java/com/vectron/fcl/Juggler.java @@ -0,0 +1,325 @@ +package com.vectron.fcl; + +import com.vectron.fcl.types.Obj; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +public class Juggler { + private final List input; + private final List output; + private final Stack stack; + private final Stack rstack; + private final Set uniqueOutput; + private final List code; + private final List availableWords; + private final int maxSteps; + + private interface Code { + boolean eval(Juggler juggler); + } + + private enum Word { + DUP("dup", Juggler::dup), + DROP("drop", Juggler::drop), + OVER("over", Juggler::over), + SWAP("swap", Juggler::swap), + NIP("nip", Juggler::nip), + TUCK("tuck", Juggler::tuck), + ROT("rot", Juggler::rot), + MROT("-rot", Juggler::mrot), + DUP2("2dup", Juggler::dup2), + DROP2("2drop", Juggler::drop2), + SWAP2("2swap", Juggler::swap2), + OVER2("2over", Juggler::over2), + RTO2("2rot", Juggler::rot2), + MRTO2("-2rot", Juggler::mrot2), + RTO(">r", Juggler::rto), + RFROM("r>", Juggler::rfrom); + + private final String name; + private final Code code; + + Word(String name, Code code) { + this.name = name; + this.code = code; + } + + public boolean eval(Juggler juggler) { + return code.eval(juggler); + } + } + + public static List solve(List input, List output, Set excluded, int maxSteps) { + Juggler juggler = new Juggler(input, output, excluded, maxSteps); + return juggler.solve(); + } + + private Juggler(List input, List output, Set excluded, int maxSteps) { + this.input = input; + this.output = output; + this.maxSteps = maxSteps; + this.uniqueOutput = new HashSet<>(output); + this.stack = new Stack<>(); + this.rstack = new Stack<>(); + this.code = new ArrayList<>(); + this.availableWords = populateWords(excluded); + } + + private List populateWords(Set excluded) { + List result = new ArrayList<>(); + for (Word each : Word.values()) { + if (!excluded.contains(each.name)) + result.add(each); + } + return result; + } + + private List solve() { + if (input.isEmpty() && output.isEmpty() || input.equals(output)) + return Collections.emptyList(); + + code.clear(); + code.add(0); + + while (code.size() <= maxSteps) { + if (goodCode(code)) + return result(code); + next(code); + } + return null; + } + + private void next(List code) { + int i = code.size() -1; + int max = availableWords.size() - 1; + code.set(i, code.get(i) +1); + while (code.get(i) > max) { + code.set(i, 0); + if (i > 0) { + i--; + code.set(i, code.get(i) +1); + } else { + code.add(0, 0); + } + } + } + + private boolean goodCode(List code) { + stack.clear(); + stack.addAll(input); + rstack.clear(); + List> stackHistory = new ArrayList<>(); + List> rstackHistory = new ArrayList<>(); + + for (int i = 0; i < code.size(); i++) { + if (!nthWord(code.get(i)).eval(this) || nop() || cycle(stackHistory, rstackHistory)) { + skip(i, code); + return false; + } + stackHistory.add(copy(stack)); + rstackHistory.add(copy(rstack)); + } + return rstack.isEmpty() && stack.equals(output); + } + + private Word nthWord(int n) { + return availableWords.get(n); + } + + private void skip(int n, List code) { + int max = availableWords.size() -1; + for (int i = n +1; i < code.size(); i++) { + code.set(i, max); + } + } + + private boolean cycle(List> stackHistory, List> rstackHistory) { + for (int i = 0; i < stackHistory.size(); i++) { + if (stackHistory.get(i).equals(stack) && rstackHistory.get(i).equals(rstack)) + return true; + } + return false; + } + + private boolean nop() { + return rstack.isEmpty() && stack.equals(input); + } + + private Stack copy(Stack stack) { + Stack result = new Stack<>(); + result.addAll(stack); + return result; + } + + private List result(List code) { + List result = new ArrayList<>(); + for (Integer each : code) + result.add(nthWord(each).name); + return result; + } + + private Obj pick(int i) { + return stack.get(stack.size() - i); + } + + private boolean dup() { + if (stack.empty()) return false; + stack.push(pick(1)); + return true; + } + + private boolean dup2() { + if (stack.size() < 2) return false; + stack.push(pick(2)); + stack.push(pick(2)); + return true; + } + + private boolean drop2() { + if (stack.size() < 2) return false; + stack.pop(); + stack.pop(); + return !missing(); + } + + private boolean drop() { + if (stack.empty()) return false; + stack.pop(); + return !missing(); + } + + private boolean swap() { + if (stack.size() < 2) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + stack.push(n1); + stack.push(n2); + return true; + } + + private boolean swap2() { + if (stack.size() < 4) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + Obj n3 = stack.pop(); + Obj n4 = stack.pop(); + stack.push(n2); + stack.push(n1); + stack.push(n4); + stack.push(n3); + return true; + } + + private boolean over() { + if (stack.size() < 2) return false; + stack.push(pick(2)); + return true; + } + + private boolean over2() { + if (stack.size() < 4) return false; + stack.push(pick(4)); + stack.push(pick(4)); + return true; + } + + private boolean nip() { + if (stack.size() < 2) return false; + Obj n = stack.pop(); + stack.pop(); + stack.push(n); + return !missing(); + } + + private boolean tuck() { + if (stack.size() < 2) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + stack.push(n1); + stack.push(n2); + stack.push(n1); + return true; + } + + private boolean rot() { + if (stack.size() < 3) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + Obj n3 = stack.pop(); + stack.push(n2); + stack.push(n1); + stack.push(n3); + return true; + } + + private boolean mrot() { + if (stack.size() < 3) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + Obj n3 = stack.pop(); + stack.push(n1); + stack.push(n3); + stack.push(n2); + return true; + } + + private boolean rot2() { + if (stack.size() < 6) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + Obj n3 = stack.pop(); + Obj n4 = stack.pop(); + Obj n5 = stack.pop(); + Obj n6 = stack.pop(); + stack.push(n4); + stack.push(n3); + stack.push(n2); + stack.push(n1); + stack.push(n6); + stack.push(n5); + return true; + } + + private boolean mrot2() { + if (stack.size() < 6) return false; + Obj n1 = stack.pop(); + Obj n2 = stack.pop(); + Obj n3 = stack.pop(); + Obj n4 = stack.pop(); + Obj n5 = stack.pop(); + Obj n6 = stack.pop(); + stack.push(n2); + stack.push(n1); + stack.push(n6); + stack.push(n5); + stack.push(n4); + stack.push(n3); + return true; + } + + private boolean rfrom() { + if (rstack.size() < 1) return false; + stack.push(rstack.pop()); + return true; + } + + private boolean rto() { + if (stack.size() < 1) return false; + rstack.push(stack.pop()); + return true; + } + + private boolean missing() { + for (Obj each : uniqueOutput) { + if (!stack.contains(each) && !stack.contains(each)) + return true; + } + return false; + } +} + diff --git a/src/main/res/raw/core.forth b/src/main/res/raw/core.forth index de7b0e0..12238b0 100644 --- a/src/main/res/raw/core.forth +++ b/src/main/res/raw/core.forth @@ -7,7 +7,7 @@ : 2swap rot >r rot r> ; : 2rot swap >r >r 2swap r> r> swap 2swap ; : -2rot 2rot 2rot ; -: 2over 2swap 2dup 2rot 2swap ; +: 2over 2swap 2dup -2rot ; : tuck swap over ; : != = not ; : >= < not ; diff --git a/src/test/java/com/vectron/fcl/FclTest.java b/src/test/java/com/vectron/fcl/FclTest.java index 26a86b1..4c508a8 100644 --- a/src/test/java/com/vectron/fcl/FclTest.java +++ b/src/test/java/com/vectron/fcl/FclTest.java @@ -130,6 +130,7 @@ public class FclTest { public void testJuggling() { // http://sovietov.com/app/forthwiz.html assertEquals(asList(2l, 1l), evalGetStack("1 2 swap")); assertEquals(asList(1l, 2l, 1l), evalGetStack("1 2 over")); + assertEquals(asList(1l, 2l, 3l, 4l, 1l, 2l), evalGetStack("1 2 3 4 2over")); assertEquals(asList(3l, 3l), evalGetStack("3 dup")); assertEquals(asList(3l, 4l, 3l, 4l), evalGetStack("3 4 2dup")); assertEquals(asList(6l), evalGetStack("5 6 nip")); diff --git a/src/test/java/com/vectron/fcl/JugglerTest.java b/src/test/java/com/vectron/fcl/JugglerTest.java new file mode 100644 index 0000000..50fd3cc --- /dev/null +++ b/src/test/java/com/vectron/fcl/JugglerTest.java @@ -0,0 +1,199 @@ +package com.vectron.fcl; + +import com.vectron.fcl.types.Num; +import com.vectron.fcl.types.Obj; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.vectron.fcl.types.Num.ONE; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class JugglerTest { + private final Set excluded = new HashSet<>(); + + @Test + public void testEmpty() { + assertTrue(solve(emptyList(), emptyList()).isEmpty()); + } + + @Test + public void testSame() { + assertTrue(solve(asList(ONE), asList(ONE)).isEmpty()); + } + + @Test + public void testSingleDrop() { + assertSolution("drop", "1", ""); + } + + @Test + public void testSingleDup() { + assertSolution("dup", "0", "0 0"); + } + + @Test + public void testSingleSwap() { + assertSolution("swap", "0 1", "1 0"); + } + + @Test + public void testSingle2Swap() { + assertSolution("2swap", "0 1 2 3", "2 3 0 1"); + } + + @Test + public void testSingleOver() { + assertSolution("over", "0 1", "0 1 0"); + } + + @Test + public void testSingle2Over() { + assertSolution("2over", "0 1 2 3", "0 1 2 3 0 1"); + } + + @Test + public void testSingleNip() { + assertSolution("nip", "0 1", "1"); + } + + @Test + public void testSingleTuck() { + assertSolution("tuck", "0 1", "1 0 1"); + } + + @Test + public void testSingleRot() { + assertSolution("rot", "0 1 2", "1 2 0"); + } + + @Test + public void testSingle2Rot() { + assertSolution("2rot", "0 1 2 3 4 5", "2 3 4 5 0 1"); + } + + @Test + public void testSingle2mRot() { + assertSolution("-2rot", "0 1 2 3 4 5", "4 5 0 1 2 3"); + } + + @Test + public void testSingleMRot() { + assertSolution("-rot", "0 1 2", "2 0 1"); + } + + @Test + public void testSingle2dup() { + assertSolution("2dup", "0 1", "0 1 0 1"); + } + + @Test + public void testReverse3() { + assertSolution("swap rot", "0 1 2", "2 1 0"); + } + + @Test + public void testReverse4() { + assertSolution("swap 2swap swap", "0 1 2 3", "3 2 1 0"); + excluded.add("2swap"); + assertSolution("over tuck -2rot 2drop swap", "0 1 2 3", "3 2 1 0"); + excluded.add("-2rot"); + assertSolution("swap 2over swap 2rot 2drop", "0 1 2 3", "3 2 1 0"); + excluded.add("2over"); + assertSolution("rot >r -rot r> rot", "0 1 2 3", "3 2 1 0"); + } + + @Test + public void testReverse5() { + assertSolution("over 2swap 2rot -rot nip", "0 1 2 3 4", "4 3 2 1 0"); + } + + @Test + public void testOverOver() { + excluded.add("2dup"); + assertSolution("over over", "0 1", "0 1 0 1"); + } + + @Test + public void testSingle2drop() { + assertSolution("2drop", "0 1", ""); + } + + @Test + public void testComplex1() { + assertSolution("drop over swap 2swap", "0 1 2 3", "1 2 0 1"); + excluded.add("2swap"); + assertSolution("drop over >r rot r>", "0 1 2 3", "1 2 0 1"); + } + + @Test + public void testComplex2() { + assertSolution("drop tuck 2swap", "0 1 2 3", "1 2 0 2"); + excluded.add("2swap"); + assertSolution("drop rot over", "0 1 2 3", "1 2 0 2"); + } + + @Test + public void testComplex3() { + assertSolution("drop rot dup", "0 1 2 3", "1 2 0 0"); + } + + @Test + public void testComplex4() { + assertSolution("swap 2over drop 2swap nip", "0 1 2 3", "0 2 0 3"); + excluded.add("2over"); + assertSolution("rot drop >r over r>", "0 1 2 3", "0 2 0 3"); + } + + @Test + public void testComplex5() { + assertSolution("drop rot drop -rot", "0 1 2 3 4", "3 0 2"); + } + + @Test + public void testComplex6() { + assertSolution("drop 2drop nip over swap", "0 1 2 3 4 5", "0 0 2"); + } + + @Test + public void testComplex7() { + assertSolution("drop 2drop nip 2dup rot", "0 1 2 3 4 5", "0 0 2 2"); + } + + @Test + public void testComplex8() { + assertSolution("drop 2drop swap -rot dup", "0 1 2 3 4 5", "1 0 2 2"); + } + + @Test + public void testNoSolution1() { + assertSolution(null, "0 1 2 3 4 5", "0 0 2 1"); + } + + private List solve(List input, List output) { + return Juggler.solve(input, output, excluded, 5); + } + + private void assertSolution(String expected, String input, String output) { + if (expected == null) + assertEquals(null, solve(parse(input), parse(output))); + else + assertEquals(Arrays.asList(expected.split(" ")), solve(parse(input), parse(output))); + } + + private List parse(String str) { + List result = new ArrayList<>(); + if (str.equals("")) return result; + for (String each : str.split(" ")) + result.add(Num.parse(each)); + return result; + } +} \ No newline at end of file