From 90f343c701585066bf30f226811d65f3bce58c95 Mon Sep 17 00:00:00 2001 From: bculkin2442 Date: Wed, 24 Aug 2016 14:46:16 -0400 Subject: Added tentative support for subrules Added a new sort of config reader that tentatively supports subrules through indentation. I need to actually write a parser that uses it though. I think something to do with trees would work well for testing it. --- .../src/main/java/bjc/utils/cli/CLICommander.java | 6 +- .../src/main/java/bjc/utils/funcdata/IMap.java | 10 + .../src/main/java/bjc/utils/funcdata/Tree.java | 3 +- .../utils/parserutils/RuleBasedConfigReader.java | 8 +- .../parserutils/RuleStackBasedConfigReader.java | 306 +++++++++++++++++++++ 5 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleStackBasedConfigReader.java (limited to 'BJC-Utils2/src/main/java/bjc') diff --git a/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java b/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java index 5a7f95b..179eec0 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java +++ b/BJC-Utils2/src/main/java/bjc/utils/cli/CLICommander.java @@ -92,18 +92,16 @@ public class CLICommander { // Read in a command String currentLine = inputSource.nextLine(); - // Handle handleable commands + // Handle commands we can handle if (currentMode.canHandle(currentLine)) { String[] commandTokens = currentLine.split(" "); - String[] commandArgs; + String[] commandArgs = null; // Parse args if they are present if (commandTokens.length > 1) { commandArgs = Arrays.copyOfRange(commandTokens, 1, commandTokens.length); - } else { - commandArgs = null; } // Process command diff --git a/BJC-Utils2/src/main/java/bjc/utils/funcdata/IMap.java b/BJC-Utils2/src/main/java/bjc/utils/funcdata/IMap.java index 5e0c67e..f1616b2 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/funcdata/IMap.java +++ b/BJC-Utils2/src/main/java/bjc/utils/funcdata/IMap.java @@ -137,4 +137,14 @@ public interface IMap { * Delete all the values in the map */ void clear(); + + default ValueType getOrDefault(KeyType key, ValueType defaultValue) { + try { + return get(key); + } catch (@SuppressWarnings("unused") IllegalArgumentException iaex) { + // We don't care about this, because it indicates a key is + // missing + return defaultValue; + } + } } \ No newline at end of file diff --git a/BJC-Utils2/src/main/java/bjc/utils/funcdata/Tree.java b/BJC-Utils2/src/main/java/bjc/utils/funcdata/Tree.java index 834c124..77b5673 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/funcdata/Tree.java +++ b/BJC-Utils2/src/main/java/bjc/utils/funcdata/Tree.java @@ -134,7 +134,8 @@ public class Tree implements ITree { Function, NewType> nodeTransformer = nodeCollapser .apply(data); - IList collapsedChildren = children.map((child) -> { + @SuppressWarnings("unchecked") + IList collapsedChildren = (IList) children.map((child) -> { return child.collapse(leafTransform, nodeCollapser, (subTreeVal) -> subTreeVal); }); diff --git a/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleBasedConfigReader.java b/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleBasedConfigReader.java index 67ca215..1ad2ff5 100644 --- a/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleBasedConfigReader.java +++ b/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleBasedConfigReader.java @@ -1,9 +1,7 @@ package bjc.utils.parserutils; import java.io.InputStream; -import java.util.HashMap; import java.util.InputMismatchException; -import java.util.Map; import java.util.Scanner; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -13,7 +11,9 @@ import bjc.utils.data.IPair; import bjc.utils.data.Identity; import bjc.utils.data.Pair; import bjc.utils.exceptions.UnknownPragmaException; +import bjc.utils.funcdata.FunctionalMap; import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IMap; /** * This class parses a rules based config file, and uses it to drive a @@ -30,7 +30,7 @@ public class RuleBasedConfigReader { private BiConsumer continueRule; private Consumer endRule; - private Map> pragmas; + private IMap> pragmas; /** * Create a new rule-based config reader @@ -50,7 +50,7 @@ public class RuleBasedConfigReader { this.continueRule = continueRule; this.endRule = endRule; - this.pragmas = new HashMap<>(); + this.pragmas = new FunctionalMap<>(); } /** diff --git a/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleStackBasedConfigReader.java b/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleStackBasedConfigReader.java new file mode 100644 index 0000000..253d618 --- /dev/null +++ b/BJC-Utils2/src/main/java/bjc/utils/parserutils/RuleStackBasedConfigReader.java @@ -0,0 +1,306 @@ +package bjc.utils.parserutils; + +import java.io.InputStream; +import java.util.InputMismatchException; +import java.util.Scanner; +import java.util.Stack; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; + +import bjc.utils.data.IHolder; +import bjc.utils.data.IPair; +import bjc.utils.data.Identity; +import bjc.utils.data.Pair; +import bjc.utils.exceptions.UnknownPragmaException; +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.FunctionalStringTokenizer; +import bjc.utils.funcdata.IMap; + +/** + * This class parses a rules based config file, and uses it to drive a + * provided set of actions. It differs from {@link RuleBasedConfigReader} + * in that it has support for subrules + * + * @author ben + * + * @param + * The type of the state object to use + * @param + * The type of state to use for subrules + * + */ +public class RuleStackBasedConfigReader { + private BiConsumer> startRule; + private BiConsumer continueRule; + private Consumer endRule; + + private Stack subruleStack; + + private BiFunction startSubrule; + private BiConsumer continueSubrule; + private BiConsumer endSubrule; + + private IMap> pragmas; + + /** + * Create a new rule-based config reader + * + * @param startRule + * The action to fire when starting a rule + * @param continueRule + * The action to fire when continuing a rule + * @param endRule + * The action to fire when ending a rule + */ + public RuleStackBasedConfigReader( + BiConsumer> startRule, + BiConsumer continueRule, + Consumer endRule) { + this.startRule = startRule; + this.continueRule = continueRule; + this.endRule = endRule; + + this.pragmas = new FunctionalMap<>(); + } + + public RuleStackBasedConfigReader() { + this.pragmas = new FunctionalMap<>(); + } + + /** + * Add a pragma to this reader + * + * @param pragmaName + * The name of the pragma to add + * @param pragmaAction + * The function to execute when this pragma is read + */ + public void addPragma(String pragmaName, + BiConsumer pragmaAction) { + if (pragmaName == null) { + throw new NullPointerException("Pragma name must not be null"); + } else if (pragmaAction == null) { + throw new NullPointerException( + "Pragma action must not be null"); + } + + pragmas.put(pragmaName, pragmaAction); + } + + private void continueRule(State state, boolean ruleOpen, String line) { + if (ruleOpen == false) { + throw new InputMismatchException( + "Can't continue rule with no rule currently open"); + } + + if (continueRule == null) { + throw new InputMismatchException( + "Attempted to continue rule with rule continuation disabled." + + " Check for extraneous tabs"); + } + + String lineSansInitTab = line.substring(1); + + if (lineSansInitTab.startsWith("\t")) { + // Do subrule stuff + int subruleLevel; + + for (subruleLevel = 1; lineSansInitTab + .charAt(subruleLevel) != '\t'; subruleLevel++) { + // Count up subrule levels + } + + if (subruleStack.size() + 1 < subruleLevel) { + throw new InputMismatchException( + "Attempted to start a nested subrule without starting its parent." + + " Check indentation, as subrules can only be started one at a time"); + } else if (subruleStack.size() + 1 == subruleLevel) { + SubruleState subruleState = startSubrule.apply( + new FunctionalStringTokenizer(lineSansInitTab + .substring(subruleLevel - 1), " "), + state); + + subruleStack.push(subruleState); + } else if (subruleStack.size() == subruleLevel) { + continueSubrule.accept( + new FunctionalStringTokenizer(lineSansInitTab + .substring(subruleLevel - 1), " "), + subruleStack.peek()); + } else { + while (subruleStack.size() != subruleLevel) { + endSubrule.accept(state, subruleStack.pop()); + } + + continueSubrule.accept( + new FunctionalStringTokenizer(lineSansInitTab + .substring(subruleLevel - 1), " "), + subruleStack.peek()); + } + } else { + if (!subruleStack.empty()) { + while (!subruleStack.empty()) { + endSubrule.accept(state, subruleStack.pop()); + } + } + + continueRule.accept( + new FunctionalStringTokenizer(lineSansInitTab, " "), + state); + } + } + + private boolean endRule(State state, boolean ruleOpen) { + if (ruleOpen == false) { + // Ignore blank line without an open rule + } else { + if (endRule == null) { + // Nothing happens on rule end + ruleOpen = false; + } else { + endRule.accept(state); + } + + ruleOpen = false; + } + return ruleOpen; + } + + /** + * Run a stream through this reader + * + * @param inputStream + * The stream to get input + * @param initialState + * The initial state of the reader + * @return The final state of the reader + */ + public State fromStream(InputStream inputStream, State initialState) { + if (inputStream == null) { + throw new NullPointerException( + "Input stream must not be null"); + } + + State state; + + try (Scanner inputSource = new Scanner(inputStream, "\n")) { + + state = initialState; + IHolder ruleOpen = new Identity<>(false); + + inputSource.forEachRemaining((line) -> { + if (line.startsWith("#") || line.startsWith("//")) { + // It's a comment + return; + } else if (line.equals("")) { + ruleOpen.replace(endRule(state, ruleOpen.getValue())); + + return; + } else if (line.startsWith("\t")) { + continueRule(state, ruleOpen.getValue(), line); + } else { + ruleOpen.replace( + startRule(state, ruleOpen.getValue(), line)); + } + }); + } + + return state; + + } + + /** + * Set the action to execute when continuing a rule + * + * @param continueRule + * The action to execute on continuation of a rule + */ + public void setContinueRule( + BiConsumer continueRule) { + this.continueRule = continueRule; + } + + /** + * Set the action to execute when ending a rule + * + * @param endRule + * The action to execute on ending of a rule + */ + public void setEndRule(Consumer endRule) { + this.endRule = endRule; + } + + /** + * Set the action to execute when starting a rule + * + * @param startRule + * The action to execute on starting of a rule + */ + public void setStartRule( + BiConsumer> startRule) { + if (startRule == null) { + throw new NullPointerException( + "Action on rule start must be non-null"); + } + + this.startRule = startRule; + } + + private boolean startRule(State state, boolean ruleOpen, String line) { + FunctionalStringTokenizer tokenizer = new FunctionalStringTokenizer( + line, " "); + + String nextToken = tokenizer.nextToken(); + + if (nextToken.equals("pragma")) { + String token = tokenizer.nextToken(); + + pragmas.getOrDefault(token, (tokenzer, stat) -> { + throw new UnknownPragmaException( + "Unknown pragma " + token); + }).accept(tokenizer, state); + } else { + if (ruleOpen == true) { + throw new InputMismatchException("Attempted to open a" + + " rule with a rule already open. Make sure rules are" + + " seperated by blank lines"); + } + + startRule.accept(tokenizer, new Pair<>(nextToken, state)); + ruleOpen = true; + } + return ruleOpen; + } + + /** + * Set the function to use when a subrule starts + * + * @param startSubrule + * the function to use for starting a subrule + */ + public void setStartSubrule( + BiFunction startSubrule) { + this.startSubrule = startSubrule; + } + + /** + * Set the function to use when a subrule continues + * + * @param continueSubrule + * the function to use for continuing a subrule + */ + public void setContinueSubrule( + BiConsumer continueSubrule) { + this.continueSubrule = continueSubrule; + } + + /** + * Set the function to use when a subrule ends + * + * @param endSubrule + * the function to use for ending a subrule + */ + public void setEndSubrule(BiConsumer endSubrule) { + this.endSubrule = endSubrule; + } +} -- cgit v1.2.3