diff --git a/src/main/java/org/scijava/parsington/ExpressionParser.java b/src/main/java/org/scijava/parsington/ExpressionParser.java index 6abb42b..c010c28 100644 --- a/src/main/java/org/scijava/parsington/ExpressionParser.java +++ b/src/main/java/org/scijava/parsington/ExpressionParser.java @@ -58,7 +58,7 @@ public class ExpressionParser { private final String elementSeparator; private final String statementSeparator; private final BiFunction parseOperationFactory; - + private final ParsingNode parsingNodeOperatorStart; /** * Creates an expression parser with the standard set of operators and default * separator symbols ({@code ,} for group elements, {@code ;} for statements). @@ -158,6 +158,7 @@ public ExpressionParser(final Collection operators, this.elementSeparator = elementSeparator; this.statementSeparator = statementSeparator; this.parseOperationFactory = parseOperationFactory; + this.parsingNodeOperatorStart = buildParsingNodeOperator(); } // -- ExpressionParser methods -- @@ -217,4 +218,34 @@ public String statementSeparator() { return statementSeparator; } + /** + * Gets the starting ParsingNode based on Operators() + * + * @return the starting node of the DFA + */ + public ParsingNode getParsingNodeOperatorStart() { + return parsingNodeOperatorStart; + } + + + /** + * Creates a mini deterministic finite automaton (DFA) from the list of operators. + * The DFA is used to identify operators in the given expression + * + * @return the starting node of the DFA + */ + public ParsingNode buildParsingNodeOperator() { + final ParsingNode startingParsingNodeOperator = new ParsingNode<>(); + for (Operator op : operators()) { + String textToMatch = op.getToken(); + int lengthMinusOne = textToMatch.length()-1; + ParsingNode node = startingParsingNodeOperator; + for (int i = 0; i <= lengthMinusOne; i++) { + char ch = textToMatch.charAt(i); + node = node.addNextValue(ch, (i == lengthMinusOne) ? op : null); + } + } + return startingParsingNodeOperator; + } + } diff --git a/src/main/java/org/scijava/parsington/ParseOperation.java b/src/main/java/org/scijava/parsington/ParseOperation.java index 318c22d..fa15228 100644 --- a/src/main/java/org/scijava/parsington/ParseOperation.java +++ b/src/main/java/org/scijava/parsington/ParseOperation.java @@ -33,6 +33,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.LinkedList; +import java.util.List; /** A stateful parsing operation. */ public class ParseOperation { @@ -43,7 +44,8 @@ public class ParseOperation { protected final Position pos = new Position(); protected final Deque stack = new ArrayDeque<>(); protected final LinkedList outputQueue = new LinkedList<>(); - + // this is a copy of the value in ExpressionParser + private final ParsingNode parsingNodeOperatorStart; /** * State flag for parsing context. *
    @@ -58,6 +60,7 @@ public ParseOperation(final ExpressionParser parser, final String expression) { this.parser = parser; this.expression = expression; + parsingNodeOperatorStart = parser.getParsingNodeOperatorStart(); } /** @@ -242,9 +245,18 @@ protected boolean isIdentifierPart(char c) { * @return The parsed operator, or null if the next token is not one. */ protected Operator parseOperator() { - for (final Operator op : parser.operators()) { - final String symbol = op.getToken(); - if (operatorMatches(op, symbol)) return op; + ParsingNode node = parsingNodeOperatorStart; + List lastHit = null; + int ndx = pos.get(); + int last = expression.length(); + while ((ndx < last) && (node != null)) { + node = node.hasValueNext(expression.charAt(ndx++)); + if (node != null) lastHit = node.payload; + } + if (lastHit != null) { + for (Operator op : lastHit) { + if (operatorMatches(op, op.getToken())) return op; + } } return null; } diff --git a/src/main/java/org/scijava/parsington/ParsingNode.java b/src/main/java/org/scijava/parsington/ParsingNode.java new file mode 100644 index 0000000..701601c --- /dev/null +++ b/src/main/java/org/scijava/parsington/ParsingNode.java @@ -0,0 +1,90 @@ +package org.scijava.parsington; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * A simple deterministic finite automaton for parsing characters + * + * @author Jared Davis + */ + + +public class ParsingNode { + // when payload is non-null, this node is equivalent to an accept state + List payload = null; + // transitions to the next state, in value order + List> next = new ArrayList<>(); + // starting ParsingNode value is not used + char value = 0; + + /** + * Get the next matching state. + * @param v The character to find in the next list. + * @return next ParsingNode that matches v or null when v is not found. + */ + ParsingNode hasValueNext(char v) { + int ndx = getIndex(v); + if (ndx >= 0) { + return next.get(ndx); + } else { + return null; + } + } + + /** + * Get the current value + * + * @return value of this ParsingNode + */ + char getValue() { + return value; + } + + + // Use an int comparator to keep the next list in char order. + Comparator> vComparator = Comparator.comparingInt(ParsingNode::getValue); + + /** + * Get index value of next list for char v. + * Performs a binarySearch of list next looking for v. + * + * @param v The character to index in the next list + * @return The index of v in the next list, if it is contained in the next list; + * otherwise, (-(insertion point) - 1). + */ + int getIndex(char v) { + ParsingNode pn = new ParsingNode(); + pn.value = v; + return Collections.binarySearch(next, pn, vComparator); + } + + /** + * Add a new next ParsingNode. + * + * @param v The character to add. + * @param action when non-null this defines an accept state. + * @return The ParsingNode that was added. + */ + + ParsingNode addNextValue(char v, T action) { + int ndx = getIndex(v); + ParsingNode n; + if (ndx >= 0) { + n = next.get(ndx); + } else { + n = new ParsingNode(); + n.value = v; + next.add(-ndx - 1, n); + } + if (action != null) { + if (n.payload == null) { + n.payload = new ArrayList<>(6); + } + n.payload.add(action); + } + return n; + } +}