mirror of
https://github.com/zeroflag/fcl.git
synced 2025-01-11 20:01:10 +01:00
forth wizard / stack juggler
This commit is contained in:
parent
390fce921c
commit
e1c004bd38
4 changed files with 526 additions and 1 deletions
325
src/main/java/com/vectron/fcl/Juggler.java
Normal file
325
src/main/java/com/vectron/fcl/Juggler.java
Normal file
|
@ -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<Obj> input;
|
||||
private final List<Obj> output;
|
||||
private final Stack<Obj> stack;
|
||||
private final Stack<Obj> rstack;
|
||||
private final Set<Obj> uniqueOutput;
|
||||
private final List<Integer> code;
|
||||
private final List<Word> 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<String> solve(List<Obj> input, List<Obj> output, Set<String> excluded, int maxSteps) {
|
||||
Juggler juggler = new Juggler(input, output, excluded, maxSteps);
|
||||
return juggler.solve();
|
||||
}
|
||||
|
||||
private Juggler(List<Obj> input, List<Obj> output, Set<String> 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<Word> populateWords(Set<String> excluded) {
|
||||
List<Word> result = new ArrayList<>();
|
||||
for (Word each : Word.values()) {
|
||||
if (!excluded.contains(each.name))
|
||||
result.add(each);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> 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<Integer> 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<Integer> code) {
|
||||
stack.clear();
|
||||
stack.addAll(input);
|
||||
rstack.clear();
|
||||
List<Stack<Obj>> stackHistory = new ArrayList<>();
|
||||
List<Stack<Obj>> 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<Integer> code) {
|
||||
int max = availableWords.size() -1;
|
||||
for (int i = n +1; i < code.size(); i++) {
|
||||
code.set(i, max);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean cycle(List<Stack<Obj>> stackHistory, List<Stack<Obj>> 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<Obj> copy(Stack<Obj> stack) {
|
||||
Stack<Obj> result = new Stack<>();
|
||||
result.addAll(stack);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> result(List<Integer> code) {
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ;
|
||||
|
|
|
@ -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"));
|
||||
|
|
199
src/test/java/com/vectron/fcl/JugglerTest.java
Normal file
199
src/test/java/com/vectron/fcl/JugglerTest.java
Normal file
|
@ -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<String> 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<String> solve(List<Obj> input, List<Obj> 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<Obj> parse(String str) {
|
||||
List<Obj> result = new ArrayList<>();
|
||||
if (str.equals("")) return result;
|
||||
for (String each : str.split(" "))
|
||||
result.add(Num.parse(each));
|
||||
return result;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue