Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion src/main/java/org/scijava/parsington/ExpressionParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class ExpressionParser {
private final String elementSeparator;
private final String statementSeparator;
private final BiFunction<ExpressionParser, String, ParseOperation> parseOperationFactory;

private final ParsingNode<Operator> parsingNodeOperatorStart;
/**
* Creates an expression parser with the standard set of operators and default
* separator symbols ({@code ,} for group elements, {@code ;} for statements).
Expand Down Expand Up @@ -158,6 +158,7 @@ public ExpressionParser(final Collection<? extends Operator> operators,
this.elementSeparator = elementSeparator;
this.statementSeparator = statementSeparator;
this.parseOperationFactory = parseOperationFactory;
this.parsingNodeOperatorStart = buildParsingNodeOperator();
}

// -- ExpressionParser methods --
Expand Down Expand Up @@ -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<Operator> 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<Operator> buildParsingNodeOperator() {
final ParsingNode<Operator> startingParsingNodeOperator = new ParsingNode<>();
for (Operator op : operators()) {
String textToMatch = op.getToken();
int lengthMinusOne = textToMatch.length()-1;
ParsingNode<Operator> node = startingParsingNodeOperator;
for (int i = 0; i <= lengthMinusOne; i++) {
char ch = textToMatch.charAt(i);
node = node.addNextValue(ch, (i == lengthMinusOne) ? op : null);
}
}
return startingParsingNodeOperator;
}

}
20 changes: 16 additions & 4 deletions src/main/java/org/scijava/parsington/ParseOperation.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -43,7 +44,8 @@ public class ParseOperation {
protected final Position pos = new Position();
protected final Deque<Object> stack = new ArrayDeque<>();
protected final LinkedList<Object> outputQueue = new LinkedList<>();

// this is a copy of the value in ExpressionParser
private final ParsingNode<Operator> parsingNodeOperatorStart;
/**
* State flag for parsing context.
* <ul>
Expand All @@ -58,6 +60,7 @@ public ParseOperation(final ExpressionParser parser, final String expression)
{
this.parser = parser;
this.expression = expression;
parsingNodeOperatorStart = parser.getParsingNodeOperatorStart();
}

/**
Expand Down Expand Up @@ -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<Operator> node = parsingNodeOperatorStart;
List<Operator> 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;
}
Expand Down
90 changes: 90 additions & 0 deletions src/main/java/org/scijava/parsington/ParsingNode.java
Original file line number Diff line number Diff line change
@@ -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<T> {
// when payload is non-null, this node is equivalent to an accept state
List<T> payload = null;
// transitions to the next state, in value order
List<ParsingNode<T>> 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<T> 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<ParsingNode<T>> 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<T> pn = new ParsingNode<T>();
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<T> addNextValue(char v, T action) {
int ndx = getIndex(v);
ParsingNode<T> n;
if (ndx >= 0) {
n = next.get(ndx);
} else {
n = new ParsingNode<T>();
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;
}
}
Loading