summaryrefslogtreecommitdiff
path: root/src/main/java/bjc/esodata
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/bjc/esodata')
-rw-r--r--src/main/java/bjc/esodata/AbbrevMap.java207
-rw-r--r--src/main/java/bjc/esodata/DefaultList.java116
-rw-r--r--src/main/java/bjc/esodata/Directory.java107
-rw-r--r--src/main/java/bjc/esodata/DoubleSided.java24
-rw-r--r--src/main/java/bjc/esodata/DoubleTape.java189
-rw-r--r--src/main/java/bjc/esodata/MapSet.java178
-rw-r--r--src/main/java/bjc/esodata/PushdownMap.java158
-rw-r--r--src/main/java/bjc/esodata/QueueStack.java89
-rw-r--r--src/main/java/bjc/esodata/SimpleDirectory.java95
-rw-r--r--src/main/java/bjc/esodata/SimpleStack.java87
-rw-r--r--src/main/java/bjc/esodata/SingleTape.java212
-rw-r--r--src/main/java/bjc/esodata/SpaghettiStack.java102
-rw-r--r--src/main/java/bjc/esodata/Stack.java456
-rw-r--r--src/main/java/bjc/esodata/Tape.java135
-rw-r--r--src/main/java/bjc/esodata/ThresholdSet.java238
-rw-r--r--src/main/java/bjc/esodata/UnifiedDirectory.java105
-rw-r--r--src/main/java/bjc/esodata/todos.txt3
17 files changed, 2501 insertions, 0 deletions
diff --git a/src/main/java/bjc/esodata/AbbrevMap.java b/src/main/java/bjc/esodata/AbbrevMap.java
new file mode 100644
index 0000000..048577d
--- /dev/null
+++ b/src/main/java/bjc/esodata/AbbrevMap.java
@@ -0,0 +1,207 @@
+package bjc.esodata;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import bjc.funcdata.FunctionalMap;
+import bjc.funcdata.IMap;
+
+// /**
+// * Represents a mapping from a set of strings to a mapping of all unambiguous
+// * prefixes of their respective strings.
+// *
+// * This works the same as Ruby's Abbrev module.
+// *
+// * @author EVE
+// */
+// public class AbbrevMap {
+// /* All of the words we have abbreviations for. */
+// private final Set<String> wrds;
+
+// /* Maps abbreviations to their strings. */
+// private IMap<String, String> abbrevMap;
+
+// /* Counts how many times we've seen a substring. */
+// private Set<String> seen;
+
+// /* Maps ambiguous abbreviations to the strings they could be. */
+// private SetMultimap<String, String> ambMap;
+
+// /**
+// * Create a new abbreviation map.
+// *
+// * @param words
+// * The initial set of words to put in the map.
+// */
+// public AbbrevMap(final String... words) {
+// wrds = new HashSet<>(Arrays.asList(words));
+
+// recalculate();
+// }
+
+// /**
+// * Recalculate all the abbreviations in this map.
+// *
+// * This may be needed after certain operations to ensure that all of the
+// * results are correct.
+// */
+// public void recalculate() {
+// abbrevMap = new FunctionalMap<>();
+
+// ambMap = HashMultimap.create();
+
+// seen = new HashSet<>();
+
+// for(final String word : wrds) {
+// intAddWord(word);
+// }
+// }
+
+// /**
+// * Adds words to the abbreviation map.
+// *
+// * @param words
+// * The words to add to the abbreviation map.
+// */
+// public void addWords(final String... words) {
+// wrds.addAll(Arrays.asList(words));
+
+// for(final String word : words) {
+// intAddWord(word);
+// }
+// }
+
+// /* Actually add abbreviations of a word. */
+// private void intAddWord(final String word) {
+// /* A word always abbreviates to itself. */
+// abbrevMap.put(word, word);
+
+// /* Skip blank words. */
+// if(word.equals("")) return;
+
+// /* Handle each possible abbreviation. */
+// for(int i = word.length(); i > 0; i--) {
+// final String subword = word.substring(0, i);
+
+// if(seen.contains(subword)) {
+// /*
+// * Remove a mapping if its ambiguous and not a
+// * whole word.
+// */
+// if(abbrevMap.containsKey(subword) && !wrds.contains(subword)) {
+// final String oldword = abbrevMap.remove(subword);
+
+// ambMap.put(subword, oldword);
+// ambMap.put(subword, word);
+// } else if(!wrds.contains(subword)) {
+// ambMap.put(subword, word);
+// }
+// } else {
+// seen.add(subword);
+
+// abbrevMap.put(subword, word);
+// }
+// }
+// }
+
+// /**
+// * Removes words from the abbreviation map.
+// *
+// * NOTE: There may be inconsistent behavior after removing a word from
+// * the map. Use {@link AbbrevMap#recalculate()} to fix it if it occurs.
+// *
+// * @param words
+// * The words to remove.
+// */
+// public void removeWords(final String... words) {
+// wrds.removeAll(Arrays.asList(words));
+
+// for(final String word : words) {
+// intRemoveWord(word);
+// }
+// }
+
+// /* Actually remove a word. */
+// private void intRemoveWord(final String word) {
+// /* Skip blank words. */
+// if(word.equals("")) return;
+
+// /* Handle each possible abbreviation. */
+// for(int i = word.length(); i > 0; i--) {
+// final String subword = word.substring(0, i);
+
+// if(abbrevMap.containsKey(subword)) {
+// abbrevMap.remove(subword);
+// } else {
+// ambMap.remove(subword, word);
+
+// final Set<String> possWords = ambMap.get(subword);
+
+// if(possWords.size() == 0) {
+// seen.remove(subword);
+// } else if(possWords.size() == 1) {
+// /*
+// * An abbreviation went from ambiguous
+// * to non-ambiguous.
+// */
+// final String newWord = possWords.iterator().next();
+
+// abbrevMap.put(subword, newWord);
+// ambMap.remove(subword, newWord);
+// }
+// }
+// }
+// }
+
+// /**
+// * Convert an abbreviation into all the strings it could abbreviate
+// * into.
+// *
+// * @param abbrev
+// * The abbreviation to convert.
+// *
+// * @return All the expansions for the provided abbreviation.
+// */
+// public String[] deabbrev(final String abbrev) {
+// if(abbrevMap.containsKey(abbrev)) {
+// return new String[] {
+// abbrevMap.get(abbrev)
+// };
+// }
+
+// return ambMap.get(abbrev).toArray(new String[0]);
+// }
+
+// @Override
+// public int hashCode() {
+// final int prime = 31;
+
+// int result = 1;
+// result = prime * result + (wrds == null ? 0 : wrds.hashCode());
+
+// return result;
+// }
+
+// @Override
+// public boolean equals(final Object obj) {
+// if(this == obj) return true;
+// if(obj == null) return false;
+// if(!(obj instanceof AbbrevMap)) return false;
+
+// final AbbrevMap other = (AbbrevMap) obj;
+
+// if(wrds == null) {
+// if(other.wrds != null) return false;
+// } else if(!wrds.equals(other.wrds)) return false;
+
+// return true;
+// }
+
+// @Override
+// public String toString() {
+// final String fmt = "AbbrevMap [wrds=%s, abbrevMap=%s, seen=%s, ambMap=%s]";
+
+// return String.format(fmt, wrds, abbrevMap, seen, ambMap);
+// }
+// }
diff --git a/src/main/java/bjc/esodata/DefaultList.java b/src/main/java/bjc/esodata/DefaultList.java
new file mode 100644
index 0000000..e4b2806
--- /dev/null
+++ b/src/main/java/bjc/esodata/DefaultList.java
@@ -0,0 +1,116 @@
+package bjc.esodata;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A list that has a default value that out-of-bounds accesses return.
+ *
+ * @author Ben Culkin
+ * @param <ValueType>
+ * The type of the values contained in the list.
+ */
+public class DefaultList<ValueType> extends AbstractList<ValueType> {
+ /*
+ * @NOTE 9/17/18
+ *
+ * A possible feature to add would be the ability to set a 'default
+ * index', so that the default value is 'whatever is at that list index'
+ *
+ * Of course, this would cause an exception if that index was out of
+ * bounds, but what are you going to do?
+ */
+
+ private ValueType defVal;
+
+ private List<ValueType> backing;
+
+ /**
+ * Create a new DefaultList.
+ */
+ public DefaultList() {
+ this(new ArrayList<>(), null);
+ }
+
+ /**
+ * Create a new DefaultList, with a set default value.
+ *
+ * @param defVal
+ * The default value for the list.
+ */
+ public DefaultList(ValueType defVal) {
+ this(new ArrayList<>(), defVal);
+ }
+
+ /**
+ * Create a new DefaultList, with a specific backing list.
+ *
+ * @param backer
+ * The backing list to use.
+ *
+ */
+ public DefaultList(List<ValueType> backer) {
+ this(backer, null);
+ }
+
+ /**
+ * Create a new DefaultList, with a set default value.
+ *
+ * @param backer
+ * The backing list to use.
+ *
+ * @param defVal
+ * The default value for the list.
+ */
+ public DefaultList(List<ValueType> backer, ValueType defVal) {
+ this.defVal = defVal;
+
+ this.backing = backer;
+ }
+
+ /**
+ * Get the default value.
+ *
+ * @return The default value.
+ */
+ public ValueType getDefault() {
+ return defVal;
+ }
+
+ /**
+ * Set the default value.
+ *
+ * @param defVal The default value.
+ */
+ public void setDefault(ValueType defVal) {
+ this.defVal = defVal;
+ }
+
+ @Override
+ public ValueType get(int idx) {
+ if (idx < 0 || idx >= backing.size()) return defVal;
+
+ return backing.get(idx);
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public ValueType set(int idx, ValueType val) {
+ return backing.set(idx, val);
+ }
+
+ @Override
+ public void add(int idx, ValueType val) {
+ backing.add(idx, val);
+ }
+
+ @Override
+ public ValueType remove(int idx) {
+ return backing.remove(idx);
+ }
+}
diff --git a/src/main/java/bjc/esodata/Directory.java b/src/main/java/bjc/esodata/Directory.java
new file mode 100644
index 0000000..4147e17
--- /dev/null
+++ b/src/main/java/bjc/esodata/Directory.java
@@ -0,0 +1,107 @@
+package bjc.esodata;
+
+/**
+ * Represents a hierarchical map.
+ *
+ * What's useful about this is that you can hand sub-directories to people and
+ * be able to ensure that they can't write outside of it.
+ *
+ * @param <K>
+ * The key type of the map.
+ * @param <V>
+ * The value type of the map.
+ */
+public interface Directory<K, V> {
+ /**
+ * Retrieves a given sub-directory.
+ *
+ * @param key
+ * The key to retrieve the sub-directory for.
+ *
+ * @return The sub-directory under that name.
+ *
+ * @throws IllegalArgumentException
+ * If the given sub-directory doesn't exist.
+ */
+ Directory<K, V> getSubdirectory(K key);
+
+ /**
+ * Check if a given sub-directory exists.
+ *
+ * @param key
+ * The key to look for the sub-directory under.
+ *
+ * @return Whether or not a sub-directory of that name exists.
+ */
+ boolean hasSubdirectory(K key);
+
+ /**
+ * Insert a sub-directory into the dictionary.
+ *
+ * @param key
+ * The name of the new sub-directory
+ * @param value
+ * The sub-directory to insert
+ *
+ * @return The old sub-directory attached to this key, or null if such a
+ * sub-directory didn't exist
+ */
+ Directory<K, V> putSubdirectory(K key, Directory<K, V> value);
+
+ /**
+ * Create a new sub-directory.
+ *
+ * Will fail if a sub-directory of that name already exists.
+ *
+ * @param key
+ * The name of the new sub-directory.
+ *
+ * @return The new sub-directory, or null if one by that name already
+ * exists.
+ */
+ default Directory<K, V> newSubdirectory(final K key) {
+ if(hasSubdirectory(key)) return null;
+
+ final Directory<K, V> dir = new SimpleDirectory<>();
+
+ putSubdirectory(key, dir);
+
+ return dir;
+ }
+
+ /**
+ * Check if the directory contains a data-item under the given key.
+ *
+ * @param key
+ * The key to check for.
+ *
+ * @return Whether or not there is a data item for the given key.
+ */
+ boolean containsKey(K key);
+
+ /**
+ * Retrieve a given data-item from the directory.
+ *
+ * @param key
+ * The key to retrieve data for.
+ *
+ * @return The value for the given key.
+ *
+ * @throws IllegalArgumentException
+ * If no value exists for the given key.
+ */
+ V getKey(K key);
+
+ /**
+ * Insert a data-item into the directory.
+ *
+ * @param key
+ * The key to insert into.
+ *
+ * @param val
+ * The value to insert.
+ *
+ * @return The old value of key, or null if such a value didn't exist.
+ */
+ V putKey(K key, V val);
+}
diff --git a/src/main/java/bjc/esodata/DoubleSided.java b/src/main/java/bjc/esodata/DoubleSided.java
new file mode 100644
index 0000000..6ecbdcf
--- /dev/null
+++ b/src/main/java/bjc/esodata/DoubleSided.java
@@ -0,0 +1,24 @@
+package bjc.esodata;
+
+/**
+ * Interface for a double-sided object.
+ *
+ * @author bjculkin
+ *
+ */
+public interface DoubleSided {
+ /**
+ * Flips the object.
+ *
+ * The active side becomes inactive, and the inactive side becomes
+ * active.
+ */
+ void flip();
+
+ /**
+ * Check which side of the object is active;
+ *
+ * @return True if the front side is active, false otherwise.
+ */
+ boolean currentSide();
+}
diff --git a/src/main/java/bjc/esodata/DoubleTape.java b/src/main/java/bjc/esodata/DoubleTape.java
new file mode 100644
index 0000000..c36fcff
--- /dev/null
+++ b/src/main/java/bjc/esodata/DoubleTape.java
@@ -0,0 +1,189 @@
+package bjc.esodata;
+
+/**
+ * Double-sided tape is essentially two tapes stuck together with a shared
+ * cursor.
+ *
+ * The main way a double-sided tape differs is that it can be flipped, allowing
+ * access to another set of data.
+ *
+ * However, there is only one cursor, and the position of the cursor on one side
+ * is the inverse of the position on the other side.
+ *
+ * When one side is extended, a null will be inserted into the inactive side
+ * regardless of the auto-extension policy of the tape. The policy will still be
+ * respected for the active side.
+ *
+ * All operations that refer to the tape refer to the currently active side of
+ * the tape, except for flip.
+ *
+ * Flip refers to the entire tape for 'obvious' reasons.
+ *
+ * @param <T>
+ * The element type of the tape.
+ *
+ * @author bjculkin
+ */
+public class DoubleTape<T> implements Tape<T>, DoubleSided {
+ private boolean frontActive;
+
+ /* The front-side of the tape. */
+ private Tape<T> front;
+ /* The back-side of the tape. */
+ private Tape<T> back;
+
+ /** Create a new empty double-sided tape that doesn't autoextend. */
+ public DoubleTape() {
+ this(false);
+ }
+
+ /**
+ * Create a new empty double-sided tape that follows the specified
+ * auto-extension policy.
+ *
+ * @param autoExtnd
+ * Whether or not to auto-extend the tape to the right w/ nulls.
+ */
+ public DoubleTape(final boolean autoExtnd) {
+ front = new SingleTape<>(autoExtnd);
+ back = new SingleTape<>(autoExtnd);
+
+ frontActive = true;
+ }
+
+ @Override
+ public T item() {
+ return front.item();
+ }
+
+ @Override
+ public void item(final T itm) {
+ front.item(itm);
+ }
+
+ @Override
+ public int size() {
+ return front.size();
+ }
+
+ @Override
+ public int position() {
+ return front.position();
+ }
+
+ @Override
+ public void insertBefore(final T itm) {
+ front.insertBefore(itm);
+ back.insertAfter(null);
+ }
+
+ @Override
+ public void insertAfter(final T itm) {
+ front.insertAfter(itm);
+ back.insertBefore(itm);
+ }
+
+ @Override
+ public T remove() {
+ back.remove();
+
+ return front.remove();
+ }
+
+ @Override
+ public void first() {
+ front.first();
+ back.last();
+ }
+
+ @Override
+ public void last() {
+ front.last();
+ back.first();
+ }
+
+ @Override
+ public boolean left() {
+ return left(1);
+ }
+
+ @Override
+ public boolean left(final int amt) {
+ final boolean succ = front.left(amt);
+
+ if(succ) {
+ back.right(amt);
+ }
+
+ return succ;
+ }
+
+ @Override
+ public boolean right() {
+ return right(1);
+ }
+
+ @Override
+ public boolean right(final int amt) {
+ final boolean succ = front.right(amt);
+
+ if(succ) {
+ back.left(amt);
+ }
+
+ return succ;
+ }
+
+ public boolean seekTo(int tgtPos) {
+ return front.seekTo(tgtPos);
+ }
+
+ @Override
+ public void flip() {
+ final Tape<T> tmp = front;
+
+ front = back;
+
+ back = tmp;
+
+ frontActive = !frontActive;
+ }
+
+ @Override
+ public boolean currentSide() {
+ return frontActive;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (back == null ? 0 : back.hashCode());
+ result = prime * result + (front == null ? 0 : front.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof DoubleTape<?>)) return false;
+
+ final DoubleTape<?> other = (DoubleTape<?>) obj;
+
+ if(back == null) {
+ if(other.back != null) return false;
+ } else if(!back.equals(other.back)) return false;
+
+ if(front == null) {
+ if(other.front != null) return false;
+ } else if(!front.equals(other.front)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("DoubleTape [front=%s, back=%s]", front, back);
+ }
+}
diff --git a/src/main/java/bjc/esodata/MapSet.java b/src/main/java/bjc/esodata/MapSet.java
new file mode 100644
index 0000000..cbb5d34
--- /dev/null
+++ b/src/main/java/bjc/esodata/MapSet.java
@@ -0,0 +1,178 @@
+package bjc.esodata;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A string-keyed set of maps.
+ *
+ * @author bjculkin
+ *
+ * @param <KeyType>
+ * The key type of the maps.
+ * @param <ValueType>
+ * The value type of the maps.
+ */
+public class MapSet<KeyType, ValueType> extends AbstractMap<KeyType, ValueType> {
+ private Map<String, Map<KeyType, ValueType>> backing;
+
+ private Map<KeyType, ValueType> currentMap = null;
+
+ /**
+ * Create a new set of maps.
+ */
+ public MapSet() {
+ backing = new HashMap<>();
+ }
+
+ /**
+ * Create a new set of maps, with the specified set of maps.
+ *
+ * @param back
+ * The set of maps to use.
+ */
+ public MapSet(Map<String, Map<KeyType, ValueType>> back) {
+ backing = back;
+ }
+
+ /**
+ * Add a keyed map.
+ *
+ * @param key
+ * The key for the map.
+ * @param map
+ * The map itself.
+ */
+ public void addMap(String key, Map<KeyType, ValueType> map) {
+ backing.put(key, map);
+ }
+
+ /**
+ * Clear out the contents of the set
+ */
+ public void clearMap() {
+ currentMap = null;
+
+ backing.clear();
+ }
+
+ /**
+ * Check if there is a map attached to the specified key.
+ *
+ * @param key
+ * The key to look for.
+ * @return Whether or not there is anything attached to the key.
+ */
+ public boolean containsMap(String key) {
+ return backing.containsKey(key);
+ }
+
+ /**
+ * Get the map attached to a specified key.
+ *
+ * @param key
+ * The key to look for.
+ * @return The map attached to the key.
+ */
+ public Map<KeyType, ValueType> getMap(String key) {
+ return backing.get(key);
+ }
+
+ /**
+ * Get all of the backing entries.
+ *
+ * @return The backing entries.
+ */
+ public Set<Map.Entry<String, Map<KeyType, ValueType>>> getMapEntries() {
+ return backing.entrySet();
+ }
+
+ /**
+ * Get all of the keys.
+ *
+ * @return The keys currently in use.
+ */
+ public Set<String> getMapKeys() {
+ return backing.keySet();
+ }
+
+ /**
+ * Get all of the keyed maps.
+ *
+ * @return The keyed maps.
+ */
+ public Collection<Map<KeyType, ValueType>> getMapValues() {
+ return backing.values();
+ }
+
+ /**
+ * Set the current map.
+ *
+ * @param key
+ * The key to use as the current map.
+ * @return False if there is no map attached to the key, true otherwise.
+ */
+ public boolean setMap(String key) {
+ if (!backing.containsKey(key)) return false;
+
+ currentMap = backing.get(key);
+
+ return true;
+ }
+
+ /**
+ * Sets the current map, or creates a new one if there isn't one
+ * attached to that key.
+ *
+ * @param key
+ * The key to use as the current map.
+ */
+ public void setCreateMap(String key) {
+ if (!backing.containsKey(key)) {
+ currentMap = new HashMap<>();
+
+ backing.put(key, currentMap);
+
+ return;
+ }
+
+ currentMap = backing.get(key);
+ }
+
+ /**
+ * Set the current map, or bind a map to it.
+ *
+ * @param key
+ * The key to set or bind.
+ * @param map
+ * The map to bind to the key if it isn't present.
+ */
+ public void setPutMap(String key, Map<KeyType, ValueType> map) {
+ if (!backing.containsKey(key)) {
+ currentMap = map;
+
+ backing.put(key, map);
+
+ return;
+ }
+
+ currentMap = backing.get(key);
+ }
+
+ @Override
+ public Set<Map.Entry<KeyType, ValueType>> entrySet() {
+ if (currentMap == null) throw new NullPointerException("Current map is not set");
+
+ return currentMap.entrySet();
+ }
+
+ @Override
+ public ValueType put(KeyType key, ValueType value) {
+ if (currentMap == null) throw new NullPointerException("Current map is not set");
+
+ return currentMap.put(key, value);
+ }
+}
diff --git a/src/main/java/bjc/esodata/PushdownMap.java b/src/main/java/bjc/esodata/PushdownMap.java
new file mode 100644
index 0000000..5db5f05
--- /dev/null
+++ b/src/main/java/bjc/esodata/PushdownMap.java
@@ -0,0 +1,158 @@
+package bjc.esodata;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import bjc.funcdata.FunctionalMap;
+import bjc.funcdata.IList;
+import bjc.funcdata.IMap;
+
+/**
+ * A variant of a map where inserting a duplicate key shadows the existing value
+ * instead of replacing it.
+ *
+ * This could be useful for things like variable scopes.
+ *
+ * @author EVE
+ *
+ * @param <KeyType>
+ * The key of the map.
+ *
+ * @param <ValueType>
+ * The values in the map.
+ */
+public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> {
+ /* Our backing storage. */
+ private final IMap<KeyType, Stack<ValueType>> backing;
+
+ /** Create a new empty stack-based map. */
+ public PushdownMap() {
+ backing = new FunctionalMap<>();
+ }
+
+ /** Create a new empty stack-based map using the specified backing. */
+ private PushdownMap(final IMap<KeyType, Stack<ValueType>> back) {
+ backing = back;
+ }
+
+ @Override
+ public void clear() {
+ backing.clear();
+ }
+
+ @Override
+ public boolean containsKey(final KeyType key) {
+ return backing.containsKey(key);
+ }
+
+ @Override
+ public IMap<KeyType, ValueType> extend() {
+ return new PushdownMap<>(backing.extend());
+ }
+
+ @Override
+ public void forEach(final BiConsumer<KeyType, ValueType> action) {
+ backing.forEach((key, stk) -> action.accept(key, stk.top()));
+ }
+
+ @Override
+ public void forEachKey(final Consumer<KeyType> action) {
+ backing.forEachKey(action);
+ }
+
+ @Override
+ public void forEachValue(final Consumer<ValueType> action) {
+ backing.forEachValue(stk -> action.accept(stk.top()));
+ }
+
+ @Override
+ public ValueType get(final KeyType key) {
+ return backing.get(key).top();
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public IList<KeyType> keyList() {
+ return backing.keyList();
+ }
+
+ @Override
+ public <V2> IMap<KeyType, V2> transform(final Function<ValueType, V2> transformer) {
+ /*
+ * @NOTE Can and should we support this? More to the point,
+ * maybe this should be a map sub-type that does what it needs
+ * to?
+ */
+ throw new UnsupportedOperationException("Cannot transform pushdown maps.");
+ }
+
+ @Override
+ public ValueType put(final KeyType key, final ValueType val) {
+ if(backing.containsKey(key)) {
+ final Stack<ValueType> stk = backing.get(key);
+
+ final ValueType vl = stk.top();
+
+ stk.push(val);
+
+ return vl;
+ }
+
+ final Stack<ValueType> stk = new SimpleStack<>();
+
+ stk.push(val);
+
+ return null;
+ }
+
+ @Override
+ public ValueType remove(final KeyType key) {
+ final Stack<ValueType> stk = backing.get(key);
+
+ if(stk.size() > 1) {
+ return stk.pop();
+ }
+
+ return backing.remove(key).top();
+ }
+
+ @Override
+ public IList<ValueType> valueList() {
+ return backing.valueList().map(Stack::top);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + (backing == null ? 0 : backing.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof PushdownMap<?, ?>)) return false;
+
+ final PushdownMap<?, ?> other = (PushdownMap<?, ?>) obj;
+
+ if(backing == null) {
+ if(other.backing != null) return false;
+ } else if(!backing.equals(other.backing)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PushdownMap [backing=%s]", backing);
+ }
+}
diff --git a/src/main/java/bjc/esodata/QueueStack.java b/src/main/java/bjc/esodata/QueueStack.java
new file mode 100644
index 0000000..49476f0
--- /dev/null
+++ b/src/main/java/bjc/esodata/QueueStack.java
@@ -0,0 +1,89 @@
+package bjc.esodata;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * A FIFO implementation of a stack.
+ *
+ * Basically, a stack that actually acts like a queue.
+ *
+ * @param <T>
+ * The datatype stored in the stack.
+ *
+ * @author Ben Culkin
+ */
+public class QueueStack<T> extends Stack<T> {
+ /* Our backing queue. */
+ private final Deque<T> backing;
+
+ /** Create a new empty stack queue. */
+ public QueueStack() {
+ backing = new LinkedList<>();
+ }
+
+ @Override
+ public void push(final T elm) {
+ backing.add(elm);
+ }
+
+ @Override
+ public T pop() {
+ if(backing.isEmpty()) throw new StackUnderflowException();
+
+ return backing.remove();
+ }
+
+ @Override
+ public T top() {
+ if(backing.isEmpty()) throw new StackUnderflowException();
+
+ return backing.peek();
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean empty() {
+ return backing.size() == 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T[] toArray() {
+ return (T[]) backing.toArray();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("QueueStack [backing=%s]", backing);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+
+ result = prime * result + (backing == null ? 0 : backing.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof QueueStack<?>)) return false;
+
+ final QueueStack<?> other = (QueueStack<?>) obj;
+
+ if(backing == null) {
+ if(other.backing != null) return false;
+ } else if(!backing.equals(other.backing)) return false;
+
+ return true;
+ }
+}
diff --git a/src/main/java/bjc/esodata/SimpleDirectory.java b/src/main/java/bjc/esodata/SimpleDirectory.java
new file mode 100644
index 0000000..8ac19cf
--- /dev/null
+++ b/src/main/java/bjc/esodata/SimpleDirectory.java
@@ -0,0 +1,95 @@
+package bjc.esodata;
+
+import bjc.funcdata.FunctionalMap;
+import bjc.funcdata.IMap;
+
+/**
+ * Simple implementation of {@link Directory}.
+ *
+ * Has a split namespace for data and children.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the directory.
+ *
+ * @param <V>
+ * The value type of the directory.
+ */
+public class SimpleDirectory<K, V> implements Directory<K, V> {
+ /* Our sub-directories. */
+ private final IMap<K, Directory<K, V>> children;
+ /* Our data. */
+ private final IMap<K, V> data;
+
+ /** Create a new directory. */
+ public SimpleDirectory() {
+ children = new FunctionalMap<>();
+ data = new FunctionalMap<>();
+ }
+
+ @Override
+ public Directory<K, V> getSubdirectory(final K key) {
+ return children.get(key);
+ }
+
+ @Override
+ public boolean hasSubdirectory(final K key) {
+ return children.containsKey(key);
+ }
+
+ @Override
+ public Directory<K, V> putSubdirectory(final K key, final Directory<K, V> val) {
+ return children.put(key, val);
+ }
+
+ @Override
+ public boolean containsKey(final K key) {
+ return data.containsKey(key);
+ }
+
+ @Override
+ public V getKey(final K key) {
+ return data.get(key);
+ }
+
+ @Override
+ public V putKey(final K key, final V val) {
+ return data.put(key, val);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + (children == null ? 0 : children.hashCode());
+ result = prime * result + (data == null ? 0 : data.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof SimpleDirectory<?, ?>)) return false;
+
+ final SimpleDirectory<?, ?> other = (SimpleDirectory<?, ?>) obj;
+
+ if(children == null) {
+ if(other.children != null) return false;
+ } else if(!children.equals(other.children)) return false;
+
+ if(data == null) {
+ if(other.data != null) return false;
+ } else if(!data.equals(other.data)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SimpleDirectory [children=%s, data=%s]", children, data);
+ }
+}
diff --git a/src/main/java/bjc/esodata/SimpleStack.java b/src/main/java/bjc/esodata/SimpleStack.java
new file mode 100644
index 0000000..41b217d
--- /dev/null
+++ b/src/main/java/bjc/esodata/SimpleStack.java
@@ -0,0 +1,87 @@
+package bjc.esodata;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * Simple implementation of a stack.
+ *
+ * @param <T>
+ * The datatype stored in the stack.
+ *
+ * @author Ben Culkin
+ */
+public class SimpleStack<T> extends Stack<T> {
+ /* Our backing stack. */
+ private final Deque<T> backing;
+
+ /** Create a new empty stack. */
+ public SimpleStack() {
+ backing = new LinkedList<>();
+ }
+
+ @Override
+ public void push(final T elm) {
+ backing.push(elm);
+ }
+
+ @Override
+ public T pop() {
+ if(backing.isEmpty()) throw new StackUnderflowException();
+
+ return backing.pop();
+ }
+
+ @Override
+ public T top() {
+ if(backing.isEmpty()) throw new StackUnderflowException();
+
+ return backing.peek();
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean empty() {
+ return backing.size() == 0;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T[] toArray() {
+ return (T[]) backing.toArray();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + (backing == null ? 0 : backing.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof SimpleStack<?>)) return false;
+
+ final SimpleStack<?> other = (SimpleStack<?>) obj;
+
+ if(backing == null) {
+ if(other.backing != null) return false;
+ } else if(!backing.equals(other.backing)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SimpleStack [backing=%s]", backing);
+ }
+}
diff --git a/src/main/java/bjc/esodata/SingleTape.java b/src/main/java/bjc/esodata/SingleTape.java
new file mode 100644
index 0000000..28dca2a
--- /dev/null
+++ b/src/main/java/bjc/esodata/SingleTape.java
@@ -0,0 +1,212 @@
+package bjc.esodata;
+
+import java.util.ArrayList;
+
+/**
+ * A tape is a one-dimensional array that can only be accessed in one position
+ * at a time.
+ *
+ * A tape is essentially a 1D array with a cursor attached to it, and you can
+ * only affect elements at that cursor. The size of the array is theoretically
+ * unbounded to the right, but in practice bounded by available memory.
+ *
+ * You can choose whether or not you want the tape to automatically extend
+ * itself to the right with null elements by specifying its auto-extension
+ * policy.
+ *
+ * @param <T>
+ * The element type of the tape.
+ *
+ * @author bjculkin
+ */
+public class SingleTape<T> implements Tape<T> {
+ /* Our backing store. */
+ protected ArrayList<T> backing;
+ /* Our position in the list. */
+ protected int pos;
+ /* Whether to auto-extend the list on the left with nulls. */
+ protected boolean autoExtend;
+
+ /**
+ * Create a new tape with the specified contents that doesn't
+ * autoextend.
+ *
+ * @param vals
+ * The values to put on the tape.
+ */
+ @SafeVarargs
+ public SingleTape(T... vals) {
+ autoExtend = false;
+
+ backing = new ArrayList<>(vals.length);
+
+ for(T val : vals) {
+ backing.add(val);
+ }
+ }
+
+ /** Create a new empty tape that doesn't autoextend. */
+ public SingleTape() {
+ this(false);
+ }
+
+ /**
+ * Create a new tape with values taken from an iterable.
+ *
+ * @param itr
+ * The iterable to get values from.
+ */
+ public SingleTape(Iterable<T> itr) {
+ this(false);
+
+ for(T itm : itr) {
+ backing.add(itm);
+ }
+ }
+
+ /**
+ * Create a new empty tape that follows the specified auto-extension
+ * policy.
+ *
+ * @param autoExtnd
+ * Whether or not to auto-extend the tape to the right w/ nulls.
+ */
+ public SingleTape(final boolean autoExtnd) {
+ autoExtend = autoExtnd;
+
+ backing = new ArrayList<>();
+ }
+
+ @Override
+ public T item() {
+ if (pos < 0 || pos >= backing.size()) return null;
+
+ return backing.get(pos);
+ }
+
+ @Override
+ public void item(final T itm) {
+ backing.set(pos, itm);
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public int position() {
+ return pos;
+ }
+
+ @Override
+ public void insertBefore(final T itm) {
+ backing.add(pos, itm);
+ }
+
+ @Override
+ public void insertAfter(final T itm) {
+ if(pos == backing.size() - 1) {
+ backing.add(itm);
+ } else {
+ backing.add(pos + 1, itm);
+ }
+ }
+
+ @Override
+ public T remove() {
+ final T res = backing.remove(pos);
+ if(pos != 0) {
+ pos -= 1;
+ }
+ return res;
+ }
+
+ @Override
+ public void first() {
+ pos = 0;
+ }
+
+ @Override
+ public void last() {
+ pos = backing.size() - 1;
+ }
+
+ @Override
+ public boolean left() {
+ return left(1);
+ }
+
+ @Override
+ public boolean left(final int amt) {
+ if(pos - amt < 0) return false;
+
+ pos -= amt;
+ return true;
+ }
+
+ @Override
+ public boolean right() {
+ return right(1);
+ }
+
+ @Override
+ public boolean right(final int amt) {
+ if(pos + amt > backing.size()) {
+ if(autoExtend) {
+ while(pos + amt >= backing.size() - 1) {
+ backing.add(null);
+ }
+ } else return false;
+ }
+
+ pos += amt;
+ return true;
+ }
+
+ public boolean seekTo(int tgtPos) {
+ if(tgtPos < 0)
+ return false;
+
+ if(tgtPos >= backing.size() - 1)
+ if(autoExtend)
+ while(tgtPos >= backing.size() - 1)
+ backing.add(null);
+ else
+ return false;
+
+ pos = tgtPos;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + (backing == null ? 0 : backing.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof SingleTape<?>)) return false;
+
+ final SingleTape<?> other = (SingleTape<?>) obj;
+
+ if(backing == null) {
+ if(other.backing != null) return false;
+ } else if(!backing.equals(other.backing)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SingleTape [backing=%s, pos=%s, autoExtend=%s]", backing, pos, autoExtend);
+ }
+}
diff --git a/src/main/java/bjc/esodata/SpaghettiStack.java b/src/main/java/bjc/esodata/SpaghettiStack.java
new file mode 100644
index 0000000..9280c28
--- /dev/null
+++ b/src/main/java/bjc/esodata/SpaghettiStack.java
@@ -0,0 +1,102 @@
+package bjc.esodata;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+/**
+ * Implements a spaghetti stack, which is a stack that is branched off of a
+ * parent stack.
+ *
+ * @param <T>
+ * The datatype stored in the stack.
+ *
+ * @author Ben Culkin
+ */
+class SpaghettiStack<T> extends Stack<T> {
+ /* Our backing stack. */
+ private final Stack<T> backing;
+ /* The stack we branched off of. */
+ private final Stack<T> parent;
+
+ /**
+ * Create a new empty spaghetti stack, off of the specified parent.
+ *
+ * @param par
+ * The parent stack
+ */
+ public SpaghettiStack(final Stack<T> par) {
+ backing = new SimpleStack<>();
+
+ parent = par;
+ }
+
+ @Override
+ public void push(final T elm) {
+ backing.push(elm);
+ }
+
+ @Override
+ public T pop() {
+ if(backing.empty()) return parent.pop();
+
+ return backing.pop();
+ }
+
+ @Override
+ public T top() {
+ if(backing.empty()) return parent.top();
+
+ return backing.top();
+ }
+
+ @Override
+ public int size() {
+ return parent.size() + backing.size();
+ }
+
+ @Override
+ public boolean empty() {
+ return backing.empty() && parent.empty();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T[] toArray() {
+ return (T[]) Stream.concat(Arrays.stream(parent.toArray()), Arrays.stream(backing.toArray())).toArray();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + (backing == null ? 0 : backing.hashCode());
+ result = prime * result + (parent == null ? 0 : parent.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof SpaghettiStack<?>)) return false;
+
+ final SpaghettiStack<?> other = (SpaghettiStack<?>) obj;
+
+ if(backing == null) {
+ if(other.backing != null) return false;
+ } else if(!backing.equals(other.backing)) return false;
+
+ if(parent == null) {
+ if(other.parent != null) return false;
+ } else if(!parent.equals(other.parent)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("SpaghettiStack [backing=%s, parent=%s]", backing, parent);
+ }
+}
diff --git a/src/main/java/bjc/esodata/Stack.java b/src/main/java/bjc/esodata/Stack.java
new file mode 100644
index 0000000..13480a3
--- /dev/null
+++ b/src/main/java/bjc/esodata/Stack.java
@@ -0,0 +1,456 @@
+package bjc.esodata;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/*
+ * @TODO 10/11/17 Ben Culkin :StackCombinators
+ * Implement more combinators for the stack.
+ */
+/**
+ * A stack, with support for forth/factor style stack combinators.
+ *
+ * <p>
+ * <h2>Stack underflow</h2>
+ * <p>
+ * NOTE: In general, using any operation that attempts to remove more data from
+ * the stack than exists will cause a {@link StackUnderflowException} to be
+ * thrown. Check the size of the stack if you want to avoid this.
+ * <p>
+ * </p>
+ *
+ * @param <T>
+ * The datatype stored in the stack.
+ *
+ * @author Ben Culkin
+ */
+public abstract class Stack<T> {
+ /**
+ * The exception thrown when attempting to access an element from the
+ * stack that isn't there.
+ *
+ * @author EVE
+ */
+ public static class StackUnderflowException extends RuntimeException {
+ /* The ID of the exception */
+ private static final long serialVersionUID = 1423867176204571539L;
+ }
+
+ /**
+ * Push an element onto the stack.
+ *
+ * @param elm
+ * The element to insert.
+ */
+ public abstract void push(T elm);
+
+ /**
+ * Pop an element off of the stack.
+ *
+ * @return The element on top of the stack.
+ */
+ public abstract T pop();
+
+ /**
+ * Retrieve the top element of this stack without removing it from the
+ * stack.
+ *
+ * @return The top element of this stack.
+ */
+ public abstract T top();
+
+ /**
+ * Get the number of elements in the stack.
+ *
+ * @return the number of elements in the stack.
+ */
+ public abstract int size();
+
+ /**
+ * Check if the stack is empty.
+ *
+ * @return Whether or not the stack is empty.
+ */
+ public abstract boolean empty();
+
+ /**
+ * Create a spaghetti stack branching off of this one.
+ *
+ * @return A spaghetti stack with this stack as a parent.
+ */
+ public Stack<T> spaghettify() {
+ return new SpaghettiStack<>(this);
+ }
+
+ /*
+ * Basic combinators
+ */
+
+ /**
+ * Drop n items from the stack.
+ *
+ * @param n
+ * The number of items to drop.
+ */
+ public void drop(final int n) {
+ for(int i = 0; i < n; i++) {
+ pop();
+ }
+ }
+
+ /** Drop one item from the stack. */
+ public void drop() {
+ drop(1);
+ }
+
+ /**
+ * Delete n items below the current one.
+ *
+ * @param n
+ * The number of items below the top to delete.
+ */
+ public void nip(final int n) {
+ final T elm = pop();
+
+ drop(n);
+
+ push(elm);
+ }
+
+ /** Delete the second element in the stack. */
+ public void nip() {
+ nip(1);
+ }
+
+ /**
+ * Replicate the top n items of the stack m times.
+ *
+ * @param n
+ * The number of items to duplicate.
+ *
+ * @param m
+ * The number of times to duplicate items.
+ */
+ public void multidup(final int n, final int m) {
+ final List<T> lst = new ArrayList<>(n);
+
+ for(int i = n; i > 0; i--) {
+ lst.set(i - 1, pop());
+ }
+
+ for(int i = 0; i < m; i++) {
+ for(final T elm : lst) {
+ push(elm);
+ }
+ }
+ }
+
+ /**
+ * Duplicate the top n items of the stack.
+ *
+ * @param n
+ * The number of items to duplicate.
+ */
+ public void dup(final int n) {
+ multidup(n, 2);
+ }
+
+ /** Duplicate the top item on the stack. */
+ public void dup() {
+ dup(1);
+ }
+
+ /**
+ * Replicate the n elements below the top one m times.
+ *
+ * @param n
+ * The number of items to duplicate.
+ *
+ * @param m
+ * The number of times to duplicate items.
+ */
+ public void multiover(final int n, final int m) {
+ final List<T> lst = new ArrayList<>(n);
+
+ final T elm = pop();
+
+ for(int i = n; i > 0; i--) {
+ lst.set(i - 1, pop());
+ }
+
+ for(final T nelm : lst) {
+ push(nelm);
+ }
+ push(elm);
+
+ for(int i = 1; i < m; i++) {
+ for(final T nelm : lst) {
+ push(nelm);
+ }
+ }
+ }
+
+ /**
+ * Duplicate the n elements below the top one.
+ *
+ * @param n
+ * The number of items to duplicate.
+ */
+ public void over(final int n) {
+ multiover(n, 2);
+ }
+
+ /** Duplicate the second item in the stack. */
+ public void over() {
+ over(1);
+ }
+
+ /** Duplicate the third item in the stack. */
+ public void pick() {
+ final T z = pop();
+ final T y = pop();
+ final T x = pop();
+
+ push(x);
+ push(y);
+ push(z);
+ push(x);
+ }
+
+ /** Swap the top two items on the stack. */
+ public void swap() {
+ final T y = pop();
+ final T x = pop();
+
+ push(y);
+ push(x);
+ }
+
+ /** Duplicate the second item below the first item. */
+ public void deepdup() {
+ final T y = pop();
+ final T x = pop();
+
+ push(x);
+ push(x);
+ push(y);
+ }
+
+ /** Swap the second and third items in the stack. */
+ public void deepswap() {
+ final T z = pop();
+ final T y = pop();
+ final T x = pop();
+
+ push(y);
+ push(x);
+ push(z);
+ }
+
+ /** Rotate the top three items on the stack */
+ public void rot() {
+ final T z = pop();
+ final T y = pop();
+ final T x = pop();
+
+ push(y);
+ push(z);
+ push(x);
+ }
+
+ /** Inversely rotate the top three items on the stack */
+ public void invrot() {
+ final T z = pop();
+ final T y = pop();
+ final T x = pop();
+
+ push(z);
+ push(x);
+ push(y);
+ }
+
+ /*
+ * :StackCombinators Add a general rotate/roll operator.
+ */
+
+ /*
+ * Dataflow Combinators
+ */
+
+ /**
+ * Hides the top n elements on the stack from an action.
+ *
+ * @param n
+ * The number of elements to hide.
+ *
+ * @param action
+ * The action to hide the elements from
+ */
+ public void dip(final int n, final Consumer<Stack<T>> action) {
+ final List<T> elms = new ArrayList<>(n);
+
+ for(int i = n; i > 0; i--) {
+ elms.set(i - 1, pop());
+ }
+
+ action.accept(this);
+
+ for(final T elm : elms) {
+ push(elm);
+ }
+ }
+
+ /**
+ * Hide the top element of the stack from an action.
+ *
+ * @param action
+ * The action to hide the top from
+ */
+ public void dip(final Consumer<Stack<T>> action) {
+ dip(1, action);
+ }
+
+ /**
+ * Copy the top n elements on the stack, replacing them once an action
+ * is done.
+ *
+ * @param n
+ * The number of elements to copy.
+ *
+ * @param action
+ * The action to execute.
+ */
+ public void keep(final int n, final Consumer<Stack<T>> action) {
+ /*
+ * @NOTE Is this correct?
+ */
+ dup(n);
+ dip(n, action);
+ }
+
+ /**
+ * Apply all the actions in a list to the top n elements of the stack.
+ *
+ * @param n
+ * The number of elements to give to cons.
+ *
+ * @param actions
+ * The actions to execute.
+ */
+ public void multicleave(final int n, final List<Consumer<Stack<T>>> actions) {
+ final List<T> elms = new ArrayList<>(n);
+
+ for(int i = n; i > 0; i--) {
+ elms.set(i - 1, pop());
+ }
+
+ for(final Consumer<Stack<T>> action : actions) {
+ for(final T elm : elms) {
+ push(elm);
+ }
+
+ action.accept(this);
+ }
+ }
+
+ /**
+ * Apply all the actions in a list to the top element of the stack.
+ *
+ * @param actions
+ * The actions to execute.
+ */
+ public void cleave(final List<Consumer<Stack<T>>> actions) {
+ multicleave(1, actions);
+ }
+
+ /**
+ * Apply every action in a list of actions to n arguments.
+ *
+ * @param n
+ * The number of parameters each action takes.
+ *
+ * @param actions
+ * The actions to execute.
+ */
+ public void multispread(final int n, final List<Consumer<Stack<T>>> actions) {
+ final List<List<T>> nelms = new ArrayList<>(actions.size());
+
+ for(int i = actions.size(); i > 0; i--) {
+ final List<T> elms = new ArrayList<>(n);
+
+ for(int j = n; j > 0; j--) {
+ elms.set(j, pop());
+ }
+
+ nelms.set(i, elms);
+ }
+
+ int i = 0;
+ for(final List<T> elms : nelms) {
+ for(final T elm : elms) {
+ push(elm);
+ }
+
+ actions.get(i).accept(this);
+ i += 1;
+ }
+ }
+
+ /**
+ * Apply the actions in a list of actions to corresponding elements from
+ * the stack.
+ *
+ * @param conses
+ * The actions to execute.
+ */
+ public void spread(final List<Consumer<Stack<T>>> conses) {
+ multispread(1, conses);
+ }
+
+ /**
+ * Apply an action to the first m groups of n arguments.
+ *
+ * @param n
+ * The number of arguments cons takes.
+ *
+ * @param m
+ * The number of time to call cons.
+ *
+ * @param action
+ * The action to execute.
+ */
+ public void multiapply(final int n, final int m, final Consumer<Stack<T>> action) {
+ final List<Consumer<Stack<T>>> actions = new ArrayList<>(m);
+
+ for(int i = 0; i < m; i++) {
+ actions.add(action);
+ }
+
+ multispread(n, actions);
+ }
+
+ /**
+ * Apply an action n times to the corresponding elements in the stack.
+ *
+ * @param n
+ * The number of times to execute cons.
+ *
+ * @param action
+ * The action to execute.
+ */
+ public void apply(final int n, final Consumer<Stack<T>> action) {
+ multiapply(1, n, action);
+ }
+
+ /*
+ * Misc. functions
+ */
+
+ /**
+ * Get an array representing this stack.
+ *
+ * @return The stack as an array.
+ */
+ public abstract T[] toArray();
+}
diff --git a/src/main/java/bjc/esodata/Tape.java b/src/main/java/bjc/esodata/Tape.java
new file mode 100644
index 0000000..080e216
--- /dev/null
+++ b/src/main/java/bjc/esodata/Tape.java
@@ -0,0 +1,135 @@
+package bjc.esodata;
+
+/**
+ * Interface for something that acts like a tape.
+ *
+ * A tape is essentially a 1D array with a cursor attached to it, and you can
+ * only affect elements at that cursor. The size of the array is theoretically
+ * unbounded to the right, but in practice bounded by available memory.
+ *
+ * @param <T>
+ * The element type of the tape.
+ *
+ * @author bjculkin
+ */
+public interface Tape<T> {
+ /**
+ * Get the item the tape is currently on.
+ *
+ * @return The item the tape is on.
+ */
+ T item();
+
+ /**
+ * Set the item the tape is currently on.
+ *
+ * @param itm
+ * The new value for the tape item.
+ */
+ void item(T itm);
+
+ /**
+ * Get the current number of elements in the tape.
+ *
+ * @return The current number of elements in the tape.
+ */
+ int size();
+
+ /**
+ * Get the position of the current item.
+ *
+ * @return The position of the current item.
+ */
+ int position();
+
+ /**
+ * Insert an element before the current item.
+ *
+ * @param itm
+ * The item to add.
+ */
+ void insertBefore(T itm);
+
+ /**
+ * Insert an element after the current item.
+ *
+ * @param itm
+ * The item to insert.
+ */
+ void insertAfter(T itm);
+
+ /**
+ * Remove the current element.
+ *
+ * Also moves the cursor back one step if possible to maintain relative
+ * position.
+ *
+ * @return The removed item.
+ */
+ T remove();
+
+ /** Move the cursor to the left-most position. */
+ void first();
+
+ /** Move the cursor to the right-most position. */
+ void last();
+
+ /**
+ * Move the cursor one space left.
+ *
+ * The cursor can't go past zero.
+ *
+ * @return True if the cursor was moved left.
+ */
+ boolean left();
+
+ /**
+ * Move the cursor the specified amount left.
+ *
+ * The cursor can't go past zero. Attempts to move the cursor by amounts
+ * that would exceed zero don't move the cursor at all.
+ *
+ * @param amt
+ * The amount to attempt to move the cursor left.
+ *
+ * @return True if the cursor was moved left.
+ */
+ boolean left(int amt);
+
+ /**
+ * Move the cursor one space right.
+ *
+ * @return Whether the cursor was moved right.
+ */
+ boolean right();
+
+ /**
+ * Move the cursor the specified amount right.
+ *
+ * @param amt
+ * The amount to move the cursor right by.
+ *
+ * @return Whether the cursor was moved right.
+ */
+ boolean right(int amt);
+
+ /**
+ * Seek to an absolute position on the tape.
+ *
+ * @param pos
+ * The position to seek to.
+ * @return Whether or not the tape successfully seeked to that position.
+ */
+ boolean seekTo(int pos);
+
+ /**
+ * Check if this tape is at its end.
+ *
+ * Equivalent to checking if position() == size().
+ *
+ * @return Whether or not the tape is at its end.
+ */
+ default boolean atEnd() {
+ return position() == size();
+ }
+}
diff --git a/src/main/java/bjc/esodata/ThresholdSet.java b/src/main/java/bjc/esodata/ThresholdSet.java
new file mode 100644
index 0000000..b6f677e
--- /dev/null
+++ b/src/main/java/bjc/esodata/ThresholdSet.java
@@ -0,0 +1,238 @@
+package bjc.esodata;
+
+import java.util.*;
+
+/**
+ * Represents a counted set, that overflows to a map.
+ *
+ * More specifically, this is a set/map combo type.
+ *
+ * Initially, when you add an item, it will go into the set. Attempting to add a duplicate item to
+ * that set will cause the entry to be removed from the set, and added to the map, which will count
+ * the number of times that particular item has been added to the set. If you remove enough copies
+ * of that item to put it back down to 1 copy, that copy will be removed from the map, and readded
+ * to the set.
+ *
+ * The iterator that this type gives by default is an iterator over all of the values in the set,
+ * not including any of those in the map.
+ *
+ * @param KeyType The value being counted.
+ *
+ * @author Ben Culkin
+ */
+public class ThresholdSet<KeyType> {
+ // View of this class as a java.util.Set
+ private class SetView extends AbstractSet<KeyType> {
+ /*
+ * This is technically not a valid implementation of add, because it does not guarantee that
+ * the set will contain key after it returns (as a matter of fact, attempting to add the
+ * component might actually cause it to be removed from the collection).
+ */
+ public boolean add(KeyType key) {
+ // Qualified-this; allows us to reference the 'this' of our enclosing type.
+ int ret = ThresholdSet.this.add(key);
+
+ // No change to set contents
+ if (ret > 2) return false;
+
+ return true;
+ }
+
+ public boolean remove(Object o) {
+ // Will throw a ClassCastException if you give us something bad.
+ KeyType k = (KeyType)o;
+
+ int ret = ThresholdSet.this.remove(k);
+
+ // We removed the element.
+ if (ret == 0) return true;
+
+ return false;
+ }
+
+ public boolean contains(Object o) {
+ // Will throw a ClassCastException if you give us something bad.
+ KeyType k = (KeyType)o;
+
+ int ret = ThresholdSet.this.remove(k);
+
+ // The object is set-visible
+ if (ret == 1) return true;
+
+ return false;
+ }
+
+ public int size() {
+ return ThresholdSet.this.setSize();
+ }
+
+ public Iterator<KeyType> iterator() {
+ return ThresholdSet.this.setIterator();
+ }
+ }
+
+ private Set<KeyType> keySet;
+ // @TODO :CountMap Ben Culkin 6/19/2019
+ // Replace this with a CountSet or some equivalent concept, whenever that gets written
+ private Map<KeyType, Integer> keyMap;
+
+ /**
+ * Create a new empty threshold set.
+ */
+ public ThresholdSet() {
+ keySet = new HashSet<>();
+ keyMap = new HashMap<>();
+ }
+
+ /**
+ * Add multiple keys at once to the map.
+ *
+ * @param keys
+ * The keys to add.
+ * @return An array containing the results of adding the keys.
+ */
+ public int[] addAll(KeyType... keys) {
+ int[] ret = new int[keys.length];
+
+ for (int i = 0; i < keys.length; i++) {
+ ret[i] = add(keys[i]);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Add a key to the collection.
+ *
+ * @param key
+ * The key to add to the collection.
+ * @return The number of times that key now exists in the collection. Should always be &lt; 0.
+ */
+ public int add(KeyType key) {
+ if (keySet.contains(key)) {
+ // Promote to counted item
+ keySet.remove(key);
+
+ keyMap.put(key, 2);
+
+ return 2;
+ } else if (keyMap.containsKey(key)) {
+ // Increment count
+ int cnt = keyMap.get(key) + 1;
+
+ keyMap.put(key, cnt);
+
+ return cnt;
+ } else {
+ // New key
+ keySet.add(key);
+
+ return 1;
+ }
+ }
+
+ /**
+ * Remove a bunch of keys from the collection.
+ *
+ * @param keys
+ * The keys to remove from the collection.
+ *
+ * @return The results from removing the keys.
+ */
+ public int[] removeAll(KeyType... keys) {
+ int[] ret = new int[keys.length];
+
+ for (int i = 0; i < keys.length; i++) {
+ ret[i] = remove(keys[i]);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Remove a key from the collection.
+ *
+ * @param key
+ * The key to remove from the collection.
+ *
+ * @return The number of times that key now exists in the collection. Returns -1 if that key
+ * wasn't in the collection beforehand.
+ */
+ public int remove(KeyType key) {
+ if (keySet.contains(key)) {
+ // No more occurances
+ keySet.remove(key);
+
+ return 0;
+ } else if (keyMap.containsKey(key)) {
+ // Decrement count
+ int cnt = keyMap.get(key) - 1;
+
+ if (cnt == 1) {
+ // Move key to set
+ keyMap.remove(key);
+
+ keySet.add(key);
+
+ return 1;
+ } else {
+ keyMap.put(key, cnt);
+
+ return cnt;
+ }
+ } else {
+ // We don't know about that key
+ return -1;
+ }
+ }
+
+ /**
+ * Get the number of times the set contains a set of given keys.
+ *
+ * @param key
+ * The keys to look for.
+ *
+ * @return The containment counts for each key.
+ */
+ public int[] containsAll(KeyType... keys) {
+ int[] ret = new int[keys.length];
+
+ for (int i = 0; i < keys.length; i++) {
+ ret[i] = contains(keys[i]);
+ }
+
+ return ret;
+ }
+
+ /**
+ * Get the number of times the set contains a given key.
+ *
+ * @param key
+ * The key to look for.
+ *
+ * @return The number of times the key occurs; -1 if it doesn't occur.
+ */
+ public int contains(KeyType key) {
+ if (keySet.contains(key)) return 1;
+ if (!keyMap.containsKey(key)) return -1;
+
+ return keyMap.get(key);
+ }
+
+ /**
+ * Get a view of this collection as a java.util.Set.
+ *
+ * @return A view of the collection as a set.
+ */
+ public Set<KeyType> setView() {
+ return new SetView();
+ }
+
+ int setSize() {
+ return keySet.size();
+ }
+
+ Iterator<KeyType> setIterator() {
+ return keySet.iterator();
+ }
+}
diff --git a/src/main/java/bjc/esodata/UnifiedDirectory.java b/src/main/java/bjc/esodata/UnifiedDirectory.java
new file mode 100644
index 0000000..dec940f
--- /dev/null
+++ b/src/main/java/bjc/esodata/UnifiedDirectory.java
@@ -0,0 +1,105 @@
+package bjc.esodata;
+
+import bjc.funcdata.FunctionalMap;
+import bjc.funcdata.IMap;
+
+/**
+ * Simple implementation of {@link Directory}.
+ *
+ * Has a unified namespace for data and children.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the directory.
+ *
+ * @param <V>
+ * The value type of the directory.
+ */
+public class UnifiedDirectory<K, V> implements Directory<K, V> {
+ /* Our directory children. */
+ private final IMap<K, Directory<K, V>> children;
+ /* Our data children. */
+ private final IMap<K, V> data;
+
+ /** Create a new directory. */
+ public UnifiedDirectory() {
+ children = new FunctionalMap<>();
+ data = new FunctionalMap<>();
+ }
+
+ @Override
+ public Directory<K, V> getSubdirectory(final K key) {
+ return children.get(key);
+ }
+
+ @Override
+ public boolean hasSubdirectory(final K key) {
+ return children.containsKey(key);
+ }
+
+ @Override
+ public Directory<K, V> putSubdirectory(final K key, final Directory<K, V> val) {
+ if(data.containsKey(key)) {
+ final String msg = String.format("Key %s is already used for data", key);
+
+ throw new IllegalArgumentException(msg);
+ }
+
+ return children.put(key, val);
+ }
+
+ @Override
+ public boolean containsKey(final K key) {
+ return data.containsKey(key);
+ }
+
+ @Override
+ public V getKey(final K key) {
+ return data.get(key);
+ }
+
+ @Override
+ public V putKey(final K key, final V val) {
+ if(children.containsKey(key)) {
+ final String msg = String.format("Key %s is already used for sub-directories.", key);
+
+ throw new IllegalArgumentException(msg);
+ }
+
+ return data.put(key, val);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (children == null ? 0 : children.hashCode());
+ result = prime * result + (data == null ? 0 : data.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if(this == obj) return true;
+ if(obj == null) return false;
+ if(!(obj instanceof UnifiedDirectory<?, ?>)) return false;
+
+ final UnifiedDirectory<?, ?> other = (UnifiedDirectory<?, ?>) obj;
+
+ if(children == null) {
+ if(other.children != null) return false;
+ } else if(!children.equals(other.children)) return false;
+
+ if(data == null) {
+ if(other.data != null) return false;
+ } else if(!data.equals(other.data)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("UnifiedDirectory [children=%s, data=%s]", children, data);
+ }
+}
diff --git a/src/main/java/bjc/esodata/todos.txt b/src/main/java/bjc/esodata/todos.txt
new file mode 100644
index 0000000..28af9b7
--- /dev/null
+++ b/src/main/java/bjc/esodata/todos.txt
@@ -0,0 +1,3 @@
+@TODO 10/11/17 Ben Culkin :CursorHands
+ Add cursored list/tree data structures with the ability to pick/put the
+ current node.