summaryrefslogtreecommitdiff
path: root/JPratt/src/main/java/bjc/utils/parserutils/pratt
diff options
context:
space:
mode:
Diffstat (limited to 'JPratt/src/main/java/bjc/utils/parserutils/pratt')
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/InitialCommand.java38
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/NonInitialCommand.java63
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/ParseBlock.java37
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/ParserContext.java47
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/PrattParser.java190
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/Token.java30
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/TokenStream.java95
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/ParseBlocks.java84
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/RepeatingParseBlock.java96
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/SimpleParseBlock.java101
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/TriggeredParseBlock.java61
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/AbstractInitialCommand.java32
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryCommand.java43
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryPostCommand.java40
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ChainCommand.java73
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ConstantCommand.java40
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultInitialCommand.java28
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultNonInitialCommand.java32
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DenestingCommand.java45
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/GroupingCommand.java51
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/InitialCommands.java168
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeafCommand.java29
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeftBinaryCommand.java32
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonBinaryCommand.java37
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonInitialCommands.java140
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostCircumfixCommand.java60
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostfixCommand.java39
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PreTernaryCommand.java75
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/RightBinaryCommand.java30
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TernaryCommand.java77
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TransformingInitialCommand.java52
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/UnaryCommand.java47
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringToken.java84
-rw-r--r--JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringTokenStream.java56
34 files changed, 2152 insertions, 0 deletions
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/InitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/InitialCommand.java
new file mode 100644
index 0000000..716b99e
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/InitialCommand.java
@@ -0,0 +1,38 @@
+package bjc.utils.parserutils.pratt;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+
+/**
+ * Represents an initial command in parsing.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type for the tokens.
+ *
+ * @param <V>
+ * The value type for the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ *
+ *
+ */
+@FunctionalInterface
+public interface InitialCommand<K, V, C> {
+ /**
+ * Construct the null denotation of this command.
+ *
+ * @param operator
+ * The operator for this command.
+ * @param ctx
+ * The context for the command.
+ *
+ * @return The tree for this command.
+ *
+ * @throws ParserException
+ * If something goes wrong during parsing.
+ */
+ ITree<Token<K, V>> denote(Token<K, V> operator, ParserContext<K, V, C> ctx) throws ParserException;
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/NonInitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/NonInitialCommand.java
new file mode 100644
index 0000000..b6797d3
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/NonInitialCommand.java
@@ -0,0 +1,63 @@
+package bjc.utils.parserutils.pratt;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+
+/**
+ * Represents a non-initial command in parsing.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type for the tokens.
+ *
+ * @param <V>
+ * The value type for the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ *
+ */
+public abstract class NonInitialCommand<K, V, C> {
+ /**
+ * Construct the left denotation of this command.
+ *
+ * @param operand
+ * The left-hand operand of this command.
+ * @param operator
+ * The operator for this command.
+ *
+ * @param ctx
+ * The state needed for commands.
+ *
+ * @return The tree this command forms.
+ *
+ * @throws ParserException
+ * If something went wrong during parsing.
+ */
+ public abstract ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator,
+ ParserContext<K, V, C> ctx) throws ParserException;
+
+ /**
+ * Get the left-binding power of this command.
+ *
+ * This represents the general precedence of this command.
+ *
+ * @return The left-binding power of this command.
+ */
+ public abstract int leftBinding();
+
+ /**
+ * Get the next-binding power of this command.
+ *
+ * This represents the highest precedence of command this command can be
+ * the left operand of.
+ *
+ * This is the same as the left-binding power by default.
+ *
+ * @return The next-binding power of this command.
+ */
+ public int nextBinding() {
+ return leftBinding();
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParseBlock.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParseBlock.java
new file mode 100644
index 0000000..4542107
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParseBlock.java
@@ -0,0 +1,37 @@
+package bjc.utils.parserutils.pratt;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+
+/**
+ * Represents a embedded block in an expression.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the token.
+ *
+ * @param <V>
+ * The value type of the token.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+@FunctionalInterface
+public interface ParseBlock<K, V, C> {
+
+ /**
+ * Parse the block this represents.
+ *
+ * @param ctx
+ * The context for parsing.
+ *
+ * @return A AST for this block.
+ *
+ * @throws ParserException
+ * If something goes wrong during parsing, or the block
+ * fails validation.
+ */
+ ITree<Token<K, V>> parse(ParserContext<K, V, C> ctx) throws ParserException;
+
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParserContext.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParserContext.java
new file mode 100644
index 0000000..55b5e98
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/ParserContext.java
@@ -0,0 +1,47 @@
+package bjc.utils.parserutils.pratt;
+
+/**
+ * Represents the contextual state passed to a command.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the tokens.
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class ParserContext<K, V, C> {
+ /**
+ * The source of tokens.
+ */
+ public TokenStream<K, V> tokens;
+ /**
+ * The parser for sub-expressions.
+ */
+ public PrattParser<K, V, C> parse;
+ /**
+ * The state of the parser.
+ */
+ public C state;
+
+ /**
+ * Create a new parser context.
+ *
+ * @param tokens
+ * The source of tokens.
+ *
+ * @param parse
+ * The parser to call for sub expressions.
+ *
+ * @param state
+ * Any state needing to be kept during parsing.
+ */
+ public ParserContext(TokenStream<K, V> tokens, PrattParser<K, V, C> parse, C state) {
+ this.tokens = tokens;
+ this.parse = parse;
+ this.state = state;
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/PrattParser.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/PrattParser.java
new file mode 100644
index 0000000..c53b4e1
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/PrattParser.java
@@ -0,0 +1,190 @@
+package bjc.utils.parserutils.pratt;
+
+import bjc.utils.data.ITree;
+import bjc.utils.funcutils.NumberUtils;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.commands.DefaultNonInitialCommand;
+import bjc.utils.parserutils.pratt.commands.DefaultInitialCommand;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A configurable Pratt parser for expressions.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type for the tokens.
+ *
+ * @param <V>
+ * The value type for the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ *
+ *
+ */
+public class PrattParser<K, V, C> {
+ /*
+ * Default commands that error when used.
+ */
+ private final NonInitialCommand<K, V, C> DEFAULT_LEFT_COMMAND = new DefaultNonInitialCommand<>();
+ private final InitialCommand<K, V, C> DEFAULT_NULL_COMMAND = new DefaultInitialCommand<>();
+
+ /*
+ * Left-commands that depend on what the null command was.
+ */
+ private Map<K, Map<K, NonInitialCommand<K, V, C>>> dependantLeftCommands;
+
+ /*
+ * The left commands.
+ */
+ private Map<K, NonInitialCommand<K, V, C>> leftCommands;
+ /*
+ * The initial commands.
+ */
+ private Map<K, InitialCommand<K, V, C>> nullCommands;
+ /*
+ * Initial commands only checked for statements.
+ */
+ private Map<K, InitialCommand<K, V, C>> statementCommands;
+
+ /**
+ * Create a new Pratt parser.
+ *
+ */
+ public PrattParser() {
+ dependantLeftCommands = new HashMap<>();
+
+ leftCommands = new HashMap<>();
+ nullCommands = new HashMap<>();
+ statementCommands = new HashMap<>();
+ }
+
+ /**
+ * Parse an expression.
+ *
+ * @param precedence
+ * The initial precedence for the expression.
+ *
+ * @param tokens
+ * The tokens for the expression.
+ *
+ * @param state
+ * The state of the parser.
+ *
+ * @param isStatement
+ * Whether or not to parse statements.
+ *
+ * @return The expression as an AST.
+ *
+ * @throws ParserException
+ * If something goes wrong during parsing.
+ */
+ public ITree<Token<K, V>> parseExpression(int precedence, TokenStream<K, V> tokens, C state,
+ boolean isStatement) throws ParserException {
+ if (precedence < 0) {
+ throw new IllegalArgumentException("Precedence must be greater than zero");
+ }
+
+ Token<K, V> initToken = tokens.current();
+ tokens.next();
+
+ K initKey = initToken.getKey();
+
+ ITree<Token<K, V>> ast;
+
+ if (isStatement && statementCommands.containsKey(initKey)) {
+ ast = statementCommands.getOrDefault(initKey, DEFAULT_NULL_COMMAND).denote(initToken,
+ new ParserContext<>(tokens, this, state));
+ } else {
+ ast = nullCommands.getOrDefault(initKey, DEFAULT_NULL_COMMAND).denote(initToken,
+ new ParserContext<>(tokens, this, state));
+ }
+
+ int rightPrec = Integer.MAX_VALUE;
+
+ while (true) {
+ Token<K, V> tok = tokens.current();
+
+ K key = tok.getKey();
+
+ NonInitialCommand<K, V, C> command = leftCommands.getOrDefault(key, DEFAULT_LEFT_COMMAND);
+
+ if (dependantLeftCommands.containsKey(initKey)) {
+ command = dependantLeftCommands.get(initKey).getOrDefault(key, command);
+ }
+
+ int leftBind = command.leftBinding();
+
+ if (NumberUtils.between(precedence, rightPrec, leftBind)) {
+ tokens.next();
+
+ ast = command.denote(ast, tok, new ParserContext<>(tokens, this, state));
+ rightPrec = command.nextBinding();
+ } else {
+ break;
+ }
+ }
+
+ return ast;
+ }
+
+ /**
+ * Add a non-initial command to this parser.
+ *
+ * @param marker
+ * The key that marks the command.
+ *
+ * @param comm
+ * The command.
+ */
+ public void addNonInitialCommand(K marker, NonInitialCommand<K, V, C> comm) {
+ leftCommands.put(marker, comm);
+ }
+
+ /**
+ * Add a initial command to this parser.
+ *
+ * @param marker
+ * The key that marks the command.
+ *
+ * @param comm
+ * The command.
+ */
+ public void addInitialCommand(K marker, InitialCommand<K, V, C> comm) {
+ nullCommands.put(marker, comm);
+ }
+
+ /**
+ * Add a statement command to this parser.
+ *
+ * The difference between statements and initial commands is that
+ * statements can only appear at the start of the expression.
+ *
+ * @param marker
+ * The key that marks the command.
+ *
+ * @param comm
+ * The command.
+ */
+ public void addStatementCommand(K marker, InitialCommand<K, V, C> comm) {
+ statementCommands.put(marker, comm);
+ }
+
+ /**
+ * Add a dependant non-initial command to this parser.
+ */
+ public void addDependantCommand(K dependant, K marker, NonInitialCommand<K, V, C> comm) {
+ if (dependantLeftCommands.containsKey(dependant)) {
+ dependantLeftCommands.get(dependant).put(marker, comm);
+ } else {
+ Map<K, NonInitialCommand<K, V, C>> comms = new HashMap<>();
+
+ comms.put(marker, comm);
+
+ dependantLeftCommands.put(dependant, comms);
+ }
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/Token.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/Token.java
new file mode 100644
index 0000000..6db8b63
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/Token.java
@@ -0,0 +1,30 @@
+package bjc.utils.parserutils.pratt;
+
+/**
+ * Represents a simple parsing token.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of this token. Represents the type of the token.
+ *
+ * @param <V>
+ * The value type of this token. Represents any additional data
+ * for the token.
+ *
+ */
+public interface Token<K, V> {
+ /**
+ * Get the key for this token.
+ *
+ * @return The key for this token
+ */
+ K getKey();
+
+ /**
+ * Get the value for this token.
+ *
+ * @return The value for this token.
+ */
+ V getValue();
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/TokenStream.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/TokenStream.java
new file mode 100644
index 0000000..227e9a1
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/TokenStream.java
@@ -0,0 +1,95 @@
+package bjc.utils.parserutils.pratt;
+
+import bjc.utils.funcutils.StringUtils;
+import bjc.utils.parserutils.ParserException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * A stream of tokens.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the token.
+ *
+ * @param <V>
+ * The value type of the token.
+ */
+public abstract class TokenStream<K, V> implements Iterator<Token<K, V>> {
+ /**
+ * The exception thrown when an expectation fails.
+ *
+ * @author EVE
+ *
+ */
+ public static class ExpectationException extends ParserException {
+ private static final long serialVersionUID = 4299299480127680805L;
+
+ /**
+ * Create a new exception with the specified message.
+ *
+ * @param msg
+ * The message of the exception.
+ */
+ public ExpectationException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Get the current token.
+ *
+ * @return The current token.
+ */
+ public abstract Token<K, V> current();
+
+ @Override
+ public abstract Token<K, V> next();
+
+ @Override
+ public abstract boolean hasNext();
+
+ /**
+ * Utility method for checking that the next token is one of a specific
+ * set of types, and then consuming it.
+ *
+ * @param expectedKeys
+ * The expected values
+ *
+ * @throws ExpectationException
+ * If the token is not one of the expected types.
+ */
+ public void expect(Set<K> expectedKeys) throws ExpectationException {
+ K curKey = current().getKey();
+
+ if (!expectedKeys.contains(curKey)) {
+ String expectedList = StringUtils.toEnglishList(expectedKeys.toArray(), false);
+
+ throw new ExpectationException("One of '" + expectedList + "' was expected, not " + curKey);
+ } else {
+ next();
+ }
+ }
+
+ /**
+ * Utility method for checking that the next token is one of a specific
+ * set of types, and then consuming it.
+ *
+ * @param expectedKeys
+ * The expected values
+ *
+ * @throws ExpectationException
+ * If the token is not one of the expected types.
+ */
+ @SafeVarargs
+ public final void expect(K... expectedKeys) throws ExpectationException {
+ expect(new HashSet<>(Arrays.asList(expectedKeys)));
+ }
+
+ public boolean headIs(K val) {
+ return current().getKey().equals(val);
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/ParseBlocks.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/ParseBlocks.java
new file mode 100644
index 0000000..9df8355
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/ParseBlocks.java
@@ -0,0 +1,84 @@
+package bjc.utils.parserutils.pratt.blocks;
+
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Utility class for creating common implementations of {@link ParseBlock}
+ *
+ * @author bjculkin
+ *
+ */
+public class ParseBlocks {
+ /**
+ * Create a new repeating parse block.
+ *
+ * @param inner
+ * The parse block to repeat.
+ *
+ * @param delim
+ * The token type that seperates repetitions.
+ *
+ * @param term
+ * The token type that terminates repititions.
+ *
+ * @param mark
+ * The token to use as the node in the AST.
+ *
+ * @param action
+ * The action to perform on the state after every
+ * repitition.
+ *
+ * @return A configured repeating parse block.
+ */
+ public static <K, V, C> ParseBlock<K, V, C> repeating(ParseBlock<K, V, C> inner, K delim, K term,
+ Token<K, V> mark, UnaryOperator<C> action) {
+ return new RepeatingParseBlock<>(inner, delim, term, mark, action);
+ }
+
+ /**
+ * Create a new triggered parse block.
+ *
+ * @param source
+ * The block to trigger around.
+ *
+ * @param onEnter
+ * The action to perform upon the state before entering
+ * the block.
+ *
+ * @param onExit
+ * The action to perform upon the state after exiting the
+ * block.
+ *
+ * @return A configured trigger parse block.
+ */
+ public static <K, V, C> ParseBlock<K, V, C> trigger(ParseBlock<K, V, C> source, UnaryOperator<C> onEnter,
+ UnaryOperator<C> onExit) {
+ return new TriggeredParseBlock<>(onEnter, onExit, source);
+ }
+
+ /**
+ * Create a new simple parse block.
+ *
+ * @param precedence
+ * The precedence of the expression inside the block.
+ *
+ * @param terminator
+ * The key type of the token expected after this block,
+ * or null if none is expected.
+ *
+ * @param validator
+ * The predicate to use to validate parsed expressions,
+ * or null if none is used.
+ *
+ * @return A configured simple parse block.
+ */
+ public static <K, V, C> ParseBlock<K, V, C> simple(int precedence, K terminator,
+ Predicate<ITree<Token<K, V>>> validator) {
+ return new SimpleParseBlock<>(precedence, terminator, validator);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/RepeatingParseBlock.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/RepeatingParseBlock.java
new file mode 100644
index 0000000..08a4bae
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/RepeatingParseBlock.java
@@ -0,0 +1,96 @@
+package bjc.utils.parserutils.pratt.blocks;
+
+import java.util.function.UnaryOperator;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A parse block that can parse a sequnce of zero or more occurances of another
+ * block.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class RepeatingParseBlock<K, V, C> implements ParseBlock<K, V, C> {
+ private ParseBlock<K, V, C> innerBlock;
+
+ private K delim;
+ private K term;
+
+ private UnaryOperator<C> onDelim;
+
+ private Token<K, V> mark;
+
+ /**
+ * Create a new repeating block.
+ *
+ * @param inner
+ * The inner block for elements.
+ *
+ * @param delimiter
+ * The token that delimits elements in the sequence.
+ *
+ * @param terminator
+ * The token that terminates the sequence.
+ *
+ * @param marker
+ * The token to use as the node in the AST.
+ *
+ * @param action
+ * The action to apply to the state after every
+ * delimiter.
+ */
+ public RepeatingParseBlock(ParseBlock<K, V, C> inner, K delimiter, K terminator, Token<K, V> marker,
+ UnaryOperator<C> action) {
+ super();
+
+ if (inner == null)
+ throw new NullPointerException("Inner block must not be null");
+ else if (delimiter == null)
+ throw new NullPointerException("Delimiter must not be null");
+ else if (terminator == null) throw new NullPointerException("Terminator must not be null");
+
+ innerBlock = inner;
+
+ delim = delimiter;
+ term = terminator;
+
+ mark = marker;
+
+ onDelim = action;
+ }
+
+ @Override
+ public ITree<Token<K, V>> parse(ParserContext<K, V, C> ctx) throws ParserException {
+ ITree<Token<K, V>> ret = new Tree<>(mark);
+
+ Token<K, V> tok = ctx.tokens.current();
+
+ while (!tok.getKey().equals(term)) {
+ ITree<Token<K, V>> kid = innerBlock.parse(ctx);
+ ret.addChild(kid);
+
+ tok = ctx.tokens.current();
+
+ ctx.tokens.expect(delim, term);
+
+ if (onDelim != null) ctx.state = onDelim.apply(ctx.state);
+ }
+
+ return ret;
+ }
+
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/SimpleParseBlock.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/SimpleParseBlock.java
new file mode 100644
index 0000000..c2e9e54
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/SimpleParseBlock.java
@@ -0,0 +1,101 @@
+package bjc.utils.parserutils.pratt.blocks;
+
+import java.util.function.Predicate;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Simple implementation of {@link ParseBlock}
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class SimpleParseBlock<K, V, C> implements ParseBlock<K, V, C> {
+ private int pow;
+
+ private K term;
+
+ private Predicate<ITree<Token<K, V>>> validatr;
+
+ /**
+ * Create a new block.
+ *
+ * @param precedence
+ * The precedence of this block.
+ *
+ * @param terminator
+ * The token type that terminates the block. If this is
+ * null, don't check for a terminator.
+ *
+ * @param validator
+ * The predicate to apply to blocks.
+ */
+ public SimpleParseBlock(int precedence, K terminator, Predicate<ITree<Token<K, V>>> validator) {
+ if (precedence < 0) throw new IllegalArgumentException("Precedence must be non-negative");
+
+ pow = precedence;
+ term = terminator;
+ validatr = validator;
+ }
+
+ @Override
+ public ITree<Token<K, V>> parse(ParserContext<K, V, C> ctx) throws ParserException {
+ ITree<Token<K, V>> res = ctx.parse.parseExpression(pow, ctx.tokens, ctx.state, false);
+
+ if (term != null) {
+ ctx.tokens.expect(term);
+ }
+
+ if (validatr == null || validatr.test(res)) {
+ return res;
+ }
+
+ throw new ParserException("Block failed validation");
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+
+ result = prime * result + pow;
+ result = prime * result + ((term == null) ? 0 : term.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null) return false;
+ if (!(obj instanceof SimpleParseBlock)) return false;
+
+ SimpleParseBlock<?, ?, ?> other = (SimpleParseBlock<?, ?, ?>) obj;
+
+ if (pow != other.pow) return false;
+
+ if (term == null) {
+ if (other.term != null) return false;
+ } else if (!term.equals(other.term)) return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("ParseBlock [pow=%s, term='%s']", pow, term);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/TriggeredParseBlock.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/TriggeredParseBlock.java
new file mode 100644
index 0000000..fbfc61b
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/blocks/TriggeredParseBlock.java
@@ -0,0 +1,61 @@
+package bjc.utils.parserutils.pratt.blocks;
+
+import java.util.function.UnaryOperator;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A parse block that can adjust the state before handling its context.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ * @param <V>
+ * The value type of the tokens.
+ * @param <C>
+ * The state type of the parser.
+ */
+public class TriggeredParseBlock<K, V, C> implements ParseBlock<K, V, C> {
+ private UnaryOperator<C> onEnter;
+ private UnaryOperator<C> onExit;
+
+ private ParseBlock<K, V, C> source;
+
+ /**
+ * Create a new triggered parse block.
+ *
+ * @param onEnter
+ * The action to fire before parsing the block.
+ *
+ * @param onExit
+ * The action to fire after parsing the block.
+ *
+ * @param source
+ * The block to use for parsing.
+ */
+ public TriggeredParseBlock(UnaryOperator<C> onEnter, UnaryOperator<C> onExit, ParseBlock<K, V, C> source) {
+ super();
+ this.onEnter = onEnter;
+ this.onExit = onExit;
+ this.source = source;
+ }
+
+ @Override
+ public ITree<Token<K, V>> parse(ParserContext<K, V, C> ctx) throws ParserException {
+ C newState = onEnter.apply(ctx.state);
+
+ ParserContext<K, V, C> newCtx = new ParserContext<>(ctx.tokens, ctx.parse, newState);
+
+ ITree<Token<K, V>> res = source.parse(newCtx);
+
+ ctx.state = onExit.apply(newState);
+
+ return res;
+ }
+
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/AbstractInitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/AbstractInitialCommand.java
new file mode 100644
index 0000000..3c3a89b
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/AbstractInitialCommand.java
@@ -0,0 +1,32 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Abstract base for initial commands.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public abstract class AbstractInitialCommand<K, V, C> implements InitialCommand<K, V, C> {
+ @Override
+ public ITree<Token<K, V>> denote(Token<K, V> operator, ParserContext<K, V, C> ctx) throws ParserException {
+ return intNullDenotation(operator, ctx);
+ }
+
+ protected abstract ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException;
+
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryCommand.java
new file mode 100644
index 0000000..781309c
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryCommand.java
@@ -0,0 +1,43 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A binary operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public abstract class BinaryCommand<K, V, C> extends BinaryPostCommand<K, V, C> {
+ /**
+ * Create a new binary operator with the specified precedence.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public BinaryCommand(int precedence) {
+ super(precedence);
+ }
+
+ protected abstract int rightBinding();
+
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> opr = ctx.parse.parseExpression(rightBinding(), ctx.tokens, ctx.state, false);
+
+ return new Tree<>(operator, operand, opr);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryPostCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryPostCommand.java
new file mode 100644
index 0000000..806f2f3
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/BinaryPostCommand.java
@@ -0,0 +1,40 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.parserutils.pratt.NonInitialCommand;
+
+/**
+ * A operator with fixed precedence.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public abstract class BinaryPostCommand<K, V, C> extends NonInitialCommand<K, V, C> {
+ private final int leftPower;
+
+ /**
+ * Create a new operator with fixed precedence.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public BinaryPostCommand(int precedence) {
+ if (precedence < 0) {
+ throw new IllegalArgumentException("Precedence must be non-negative");
+ }
+
+ leftPower = precedence;
+ }
+
+ @Override
+ public int leftBinding() {
+ return leftPower;
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ChainCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ChainCommand.java
new file mode 100644
index 0000000..1324586
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ChainCommand.java
@@ -0,0 +1,73 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+import java.util.Set;
+
+/**
+ * Create a new chained operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class ChainCommand<K, V, C> extends BinaryPostCommand<K, V, C> {
+ private Set<K> chainWith;
+
+ private Token<K, V> chain;
+
+ /**
+ * Create a new chained operator.
+ *
+ * @param precedence
+ * The precedence of this operator.
+ *
+ * @param chainSet
+ * The operators to chain with.
+ *
+ * @param chainMarker
+ * The token to use as the node in the AST.
+ */
+ public ChainCommand(int precedence, Set<K> chainSet, Token<K, V> chainMarker) {
+ super(precedence);
+
+ chainWith = chainSet;
+ chain = chainMarker;
+ }
+
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> tree = ctx.parse.parseExpression(1 + leftBinding(), ctx.tokens, ctx.state, false);
+
+ ITree<Token<K, V>> res = new Tree<>(operator, operand, tree);
+
+ if (chainWith.contains(ctx.tokens.current().getKey())) {
+ Token<K, V> tok = ctx.tokens.current();
+ ctx.tokens.next();
+
+ ITree<Token<K, V>> other = denote(tree, tok,
+ new ParserContext<>(ctx.tokens, ctx.parse, ctx.state));
+
+ return new Tree<>(chain, res, other);
+ } else {
+ return res;
+ }
+ }
+
+ @Override
+ public int nextBinding() {
+ return leftBinding() - 1;
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ConstantCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ConstantCommand.java
new file mode 100644
index 0000000..10ff184
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/ConstantCommand.java
@@ -0,0 +1,40 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A command that represents a specific tree.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class ConstantCommand<K, V, C> implements InitialCommand<K, V, C> {
+ private ITree<Token<K, V>> val;
+
+ /**
+ * Create a new constant.
+ *
+ * @param con
+ * The tree this constant represents.
+ */
+ public ConstantCommand(ITree<Token<K, V>> con) {
+ val = con;
+ }
+
+ @Override
+ public ITree<Token<K, V>> denote(Token<K, V> operator, ParserContext<K, V, C> ctx) throws ParserException {
+ return val;
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultInitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultInitialCommand.java
new file mode 100644
index 0000000..7409755
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultInitialCommand.java
@@ -0,0 +1,28 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Default implementation of an initial command.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the token.
+ *
+ * @param <V>
+ * The value type of the token.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class DefaultInitialCommand<K, V, C> implements InitialCommand<K, V, C> {
+ @Override
+ public ITree<Token<K, V>> denote(Token<K, V> operator, ParserContext<K, V, C> ctx) throws ParserException {
+ throw new ParserException("Unexpected token " + operator);
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultNonInitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultNonInitialCommand.java
new file mode 100644
index 0000000..887dd25
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DefaultNonInitialCommand.java
@@ -0,0 +1,32 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.pratt.NonInitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Default implementation of a non-initial command.
+ *
+ * @author EVE
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class DefaultNonInitialCommand<K, V, C> extends NonInitialCommand<K, V, C> {
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx) {
+ throw new UnsupportedOperationException("Default command has no left denotation");
+ }
+
+ @Override
+ public int leftBinding() {
+ return -1;
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DenestingCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DenestingCommand.java
new file mode 100644
index 0000000..567e608
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/DenestingCommand.java
@@ -0,0 +1,45 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A command that denests a input tree.
+ *
+ * Useful for processing the result of passing a complex parse group to a
+ * command.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ *
+ */
+public class DenestingCommand<K, V, C> extends AbstractInitialCommand<K, V, C> {
+ private InitialCommand<K, V, C> wrapped;
+
+ /**
+ * Create a new transforming initial command.
+ *
+ * @param internal
+ * The initial command to delegate to.
+ */
+ public DenestingCommand(InitialCommand<K, V, C> internal) {
+ wrapped = internal;
+ }
+
+ @Override
+ protected ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ return wrapped.denote(operator, ctx).getChild(0);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/GroupingCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/GroupingCommand.java
new file mode 100644
index 0000000..37cc6ee
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/GroupingCommand.java
@@ -0,0 +1,51 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A grouping operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class GroupingCommand<K, V, C> extends AbstractInitialCommand<K, V, C> {
+ private ParseBlock<K, V, C> innerBlock;
+
+ private Token<K, V> mark;
+
+ /**
+ * Create a new grouping command.
+ *
+ * @param inner
+ * The inner block.
+ *
+ * @param marker
+ * The token to use as the node in the AST.
+ */
+ public GroupingCommand(ParseBlock<K, V, C> inner, Token<K, V> marker) {
+ innerBlock = inner;
+
+ mark = marker;
+ }
+
+ @Override
+ protected ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> opr = innerBlock.parse(ctx);
+
+ return new Tree<>(mark, opr);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/InitialCommands.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/InitialCommands.java
new file mode 100644
index 0000000..eac357a
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/InitialCommands.java
@@ -0,0 +1,168 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.Token;
+
+import java.util.function.UnaryOperator;
+
+import static bjc.utils.parserutils.pratt.blocks.ParseBlocks.*;
+
+/**
+ * * Contains factory methods for producing common implementations of
+ * {@link InitialCommand}
+ *
+ * @author EVE
+ *
+ */
+public class InitialCommands {
+ /**
+ * Create a new unary operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> unary(int precedence) {
+ return new UnaryCommand<>(precedence);
+ }
+
+ /**
+ * Create a new grouping operator.
+ *
+ * @param precedence
+ * The precedence of the expression in the operator.
+ *
+ * @param term
+ * The type that closes the group.
+ *
+ * @param mark
+ * The token for the AST node of the group.
+ *
+ * @return A command implementing the operator.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> grouping(int precedence, K term, Token<K, V> mark) {
+ ParseBlock<K, V, C> innerBlock = simple(precedence, term, null);
+
+ return new GroupingCommand<>(innerBlock, mark);
+ }
+
+ /**
+ * Create a new leaf operator.
+ *
+ * @return A command implementing the operator.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> leaf() {
+ return new LeafCommand<>();
+ }
+
+ /**
+ * Create a new pre-ternary operator, like an if-then-else statement.
+ *
+ * @param cond1
+ * The priority of the first block.
+ *
+ * @param block1
+ * The priority of the second block.
+ *
+ * @param block2
+ * The priority of the third block.
+ *
+ * @param mark1
+ * The marker that ends the first block.
+ *
+ * @param mark2
+ * The marker that ends the second block.
+ *
+ * @param term
+ * The token for the AST node of the group.
+ *
+ * @return A command implementing the operator.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> preTernary(int cond1, int block1, int block2, K mark1, K mark2,
+ Token<K, V> term) {
+ ParseBlock<K, V, C> condBlock = simple(cond1, mark1, null);
+ ParseBlock<K, V, C> opblock1 = simple(block1, mark2, null);
+ ParseBlock<K, V, C> opblock2 = simple(block2, null, null);
+
+ return new PreTernaryCommand<>(condBlock, opblock1, opblock2, term);
+ }
+
+ /**
+ * Create a new named constant.
+ *
+ * @param val
+ * The value of the constant.
+ *
+ * @return A command implementing the constant.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> constant(ITree<Token<K, V>> val) {
+ return new ConstantCommand<>(val);
+ }
+
+ /**
+ * Create a new delimited command. This is for block-like constructs.
+ *
+ * @param inner
+ * The precedence of the inner blocks.
+ *
+ * @param delim
+ * The marker between sub-blocks.
+ *
+ * @param mark
+ * The block terminator.
+ *
+ * @param term
+ * The token for the AST node of the group.
+ *
+ * @param onEnter
+ * The function to apply to the state on entering the
+ * block.
+ *
+ * @param onDelim
+ * The function to apply to the state on finishing a
+ * sub-block.
+ *
+ * @param onExit
+ * The function to apply to the state on exiting the
+ * block.
+ *
+ * @param statement
+ * Whether or not the sub-blocks are statements or
+ * expressions.
+ *
+ * @return A command implementing the operator.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> delimited(int inner, K delim, K mark, Token<K, V> term,
+ UnaryOperator<C> onEnter, UnaryOperator<C> onDelim, UnaryOperator<C> onExit,
+ boolean statement) {
+ ParseBlock<K, V, C> innerBlock = simple(inner, null, null);
+ ParseBlock<K, V, C> delimsBlock = repeating(innerBlock, delim, mark, term, onDelim);
+ ParseBlock<K, V, C> scopedBlock = trigger(delimsBlock, onEnter, onExit);
+
+ GroupingCommand<K, V, C> command = new GroupingCommand<>(scopedBlock, term);
+
+ /*
+ * Remove the wrapper layer from grouping-command on top of
+ * RepeatingParseBlock.
+ */
+ return denest(command);
+ }
+
+ /**
+ * Create a new denesting command.
+ *
+ * This removes one tree-level, and is useful when combining complex
+ * parse blocks with commands.
+ *
+ * @param comm
+ * The command to denest.
+ *
+ * @return A command that denests the result of the provided command.
+ */
+ public static <K, V, C> InitialCommand<K, V, C> denest(InitialCommand<K, V, C> comm) {
+ return new DenestingCommand<>(comm);
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeafCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeafCommand.java
new file mode 100644
index 0000000..3937f5f
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeafCommand.java
@@ -0,0 +1,29 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A operator that stands for itself.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class LeafCommand<K, V, C> implements InitialCommand<K, V, C> {
+ @Override
+ public ITree<Token<K, V>> denote(Token<K, V> operator, ParserContext<K, V, C> ctx) throws ParserException {
+ return new Tree<>(operator);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeftBinaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeftBinaryCommand.java
new file mode 100644
index 0000000..58d7261
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/LeftBinaryCommand.java
@@ -0,0 +1,32 @@
+package bjc.utils.parserutils.pratt.commands;
+
+/**
+ * A left-associative operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class LeftBinaryCommand<K, V, C> extends BinaryCommand<K, V, C> {
+ /**
+ * Create a new left-associative operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public LeftBinaryCommand(int precedence) {
+ super(precedence);
+ }
+
+ @Override
+ protected int rightBinding() {
+ return 1 + leftBinding();
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonBinaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonBinaryCommand.java
new file mode 100644
index 0000000..d32a1a7
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonBinaryCommand.java
@@ -0,0 +1,37 @@
+package bjc.utils.parserutils.pratt.commands;
+
+/**
+ * A non-associative operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class NonBinaryCommand<K, V, C> extends BinaryCommand<K, V, C> {
+ /**
+ * Create a new non-associative operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public NonBinaryCommand(int precedence) {
+ super(precedence);
+ }
+
+ @Override
+ protected int rightBinding() {
+ return 1 + leftBinding();
+ }
+
+ @Override
+ public int nextBinding() {
+ return leftBinding() - 1;
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonInitialCommands.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonInitialCommands.java
new file mode 100644
index 0000000..45bdc51
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/NonInitialCommands.java
@@ -0,0 +1,140 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.parserutils.pratt.NonInitialCommand;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.Token;
+import bjc.utils.parserutils.pratt.blocks.SimpleParseBlock;
+
+import java.util.Set;
+
+/**
+ * Contains factory methods for producing common implementations of
+ * {@link NonInitialCommand}
+ *
+ * @author EVE
+ *
+ */
+public class NonInitialCommands {
+ /**
+ * Create a left-associative infix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> infixLeft(int precedence) {
+ return new LeftBinaryCommand<>(precedence);
+ }
+
+ /**
+ * Create a right-associative infix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> infixRight(int precedence) {
+ return new RightBinaryCommand<>(precedence);
+ }
+
+ /**
+ * Create a non-associative infix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> infixNon(int precedence) {
+ return new NonBinaryCommand<>(precedence);
+ }
+
+ /**
+ * Create a chained operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @param chainSet
+ * The operators it forms a chain with.
+ *
+ * @param marker
+ * The token to use as the AST node for the chained
+ * operators.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> chain(int precedence, Set<K> chainSet, Token<K, V> marker) {
+ return new ChainCommand<>(precedence, chainSet, marker);
+ }
+
+ /**
+ * Create a postfix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> postfix(int precedence) {
+ return new PostfixCommand<>(precedence);
+ }
+
+ /**
+ * Create a post-circumfix operator.
+ *
+ * This is an operator in form similar to array indexing.
+ *
+ * @param precedence
+ * The precedence of this operator
+ *
+ * @param insidePrecedence
+ * The precedence of the expression inside the operator
+ *
+ * @param closer
+ * The token that closes the circumfix.
+ *
+ * @param marker
+ * The token to use as the AST node for the operator.
+ *
+ * @return A command implementing that operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> postCircumfix(int precedence, int insidePrecedence, K closer,
+ Token<K, V> marker) {
+ ParseBlock<K, V, C> innerBlock = new SimpleParseBlock<>(insidePrecedence, closer, null);
+
+ return new PostCircumfixCommand<>(precedence, innerBlock, marker);
+ }
+
+ /**
+ * Create a ternary operator.
+ *
+ * This is like C's ?: operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @param insidePrecedence
+ * The precedence of the inner section of the operator.
+ *
+ * @param closer
+ * The token that marks the end of the inner section.
+ *
+ * @param marker
+ * The token to use as the AST node for the operator.
+ *
+ * @param nonassoc
+ * True if the command is non-associative, false
+ * otherwise.
+ *
+ * @return A command implementing this operator.
+ */
+ public static <K, V, C> NonInitialCommand<K, V, C> ternary(int precedence, int insidePrecedence, K closer,
+ Token<K, V> marker, boolean nonassoc) {
+ ParseBlock<K, V, C> innerBlock = new SimpleParseBlock<>(insidePrecedence, closer, null);
+
+ return new TernaryCommand<>(precedence, innerBlock, marker, nonassoc);
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostCircumfixCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostCircumfixCommand.java
new file mode 100644
index 0000000..90fca00
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostCircumfixCommand.java
@@ -0,0 +1,60 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A post-circumfix operator, like array indexing.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class PostCircumfixCommand<K, V, C> extends BinaryPostCommand<K, V, C> {
+ private ParseBlock<K, V, C> innerBlock;
+
+ private Token<K, V> mark;
+
+ /**
+ * Create a new post-circumfix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ *
+ * @param inner
+ * The block inside the expression.
+ *
+ * @param marker
+ * The token to use as the node for the AST.
+ */
+ public PostCircumfixCommand(int precedence, ParseBlock<K, V, C> inner, Token<K, V> marker) {
+ super(precedence);
+
+ if (inner == null) {
+ throw new NullPointerException("Inner block must not be null");
+ }
+
+ innerBlock = inner;
+
+ mark = marker;
+ }
+
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> inside = innerBlock.parse(ctx);
+
+ return new Tree<>(mark, operand, inside);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostfixCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostfixCommand.java
new file mode 100644
index 0000000..bab3de4
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PostfixCommand.java
@@ -0,0 +1,39 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A postfix operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class PostfixCommand<K, V, C> extends BinaryPostCommand<K, V, C> {
+ /**
+ * Create a new postfix operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public PostfixCommand(int precedence) {
+ super(precedence);
+ }
+
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ return new Tree<>(operator, operand);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PreTernaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PreTernaryCommand.java
new file mode 100644
index 0000000..42d1a6e
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/PreTernaryCommand.java
@@ -0,0 +1,75 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A prefix ternary operator, like an if/then/else group.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class PreTernaryCommand<K, V, C> extends AbstractInitialCommand<K, V, C> {
+ private Token<K, V> term;
+
+ private ParseBlock<K, V, C> condBlock;
+
+ private ParseBlock<K, V, C> opblock1;
+ private ParseBlock<K, V, C> opblock2;
+
+ /**
+ * Create a new ternary statement.
+ *
+ * @param cond
+ * The block for handling the condition.
+ *
+ * @param op1
+ * The block for handling the first operator.
+ *
+ * @param op2
+ * The block for handling the second operator.
+ *
+ * @param term
+ * The token to use as the node for the AST.
+ */
+ public PreTernaryCommand(ParseBlock<K, V, C> cond, ParseBlock<K, V, C> op1, ParseBlock<K, V, C> op2,
+ Token<K, V> term) {
+ super();
+
+ if (cond == null)
+ throw new NullPointerException("Cond block must not be null");
+ else if (op1 == null)
+ throw new NullPointerException("Op block #1 must not be null");
+ else if (op2 == null) throw new NullPointerException("Op block #2 must not be null");
+
+ this.condBlock = cond;
+ this.opblock1 = op1;
+ this.opblock2 = op2;
+
+ this.term = term;
+ }
+
+ @Override
+ protected ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> cond = condBlock.parse(ctx);
+
+ ITree<Token<K, V>> op1 = opblock1.parse(ctx);
+
+ ITree<Token<K, V>> op2 = opblock2.parse(ctx);
+
+ return new Tree<>(term, cond, op1, op2);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/RightBinaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/RightBinaryCommand.java
new file mode 100644
index 0000000..5f3d9f2
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/RightBinaryCommand.java
@@ -0,0 +1,30 @@
+package bjc.utils.parserutils.pratt.commands;
+
+/**
+ * A right-associative binary operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ * @param <V>
+ * The value type of the tokens.
+ * @param <C>
+ * The state type of the parser.
+ */
+public class RightBinaryCommand<K, V, C> extends BinaryCommand<K, V, C> {
+ /**
+ * Create a new right-associative operator.
+ *
+ * @param precedence
+ * The precedence of the operator.
+ */
+ public RightBinaryCommand(int precedence) {
+ super(precedence);
+ }
+
+ @Override
+ protected int rightBinding() {
+ return leftBinding();
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TernaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TernaryCommand.java
new file mode 100644
index 0000000..8f04368
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TernaryCommand.java
@@ -0,0 +1,77 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParseBlock;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A ternary command, like C's ?:
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class TernaryCommand<K, V, C> extends BinaryPostCommand<K, V, C> {
+ private ParseBlock<K, V, C> innerBlck;
+
+ private Token<K, V> mark;
+
+ private boolean nonassoc;
+
+ /**
+ * Create a new ternary command.
+ *
+ * @param precedence
+ * The precedence of this operator.
+ *
+ * @param innerBlock
+ * The representation of the inner block of the
+ * expression.
+ *
+ * @param marker
+ * The token to use as the root of the AST node.
+ *
+ * @param isNonassoc
+ * Whether or not the conditional is associative.
+ */
+ public TernaryCommand(int precedence, ParseBlock<K, V, C> innerBlock, Token<K, V> marker, boolean isNonassoc) {
+ super(precedence);
+
+ if (innerBlock == null)
+ throw new NullPointerException("Inner block must not be null");
+ else if (marker == null) throw new NullPointerException("Marker must not be null");
+
+ innerBlck = innerBlock;
+ mark = marker;
+ nonassoc = isNonassoc;
+ }
+
+ @Override
+ public ITree<Token<K, V>> denote(ITree<Token<K, V>> operand, Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> inner = innerBlck.parse(ctx);
+
+ ITree<Token<K, V>> outer = ctx.parse.parseExpression(1 + leftBinding(), ctx.tokens, ctx.state, false);
+
+ return new Tree<>(mark, inner, operand, outer);
+ }
+
+ @Override
+ public int nextBinding() {
+ if (nonassoc) {
+ return leftBinding() - 1;
+ } else {
+ return leftBinding();
+ }
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TransformingInitialCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TransformingInitialCommand.java
new file mode 100644
index 0000000..88803df
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/TransformingInitialCommand.java
@@ -0,0 +1,52 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import java.util.function.UnaryOperator;
+
+import bjc.utils.data.ITree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.InitialCommand;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * An initial command that transforms the result of another command.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class TransformingInitialCommand<K, V, C> extends AbstractInitialCommand<K, V, C> {
+ private InitialCommand<K, V, C> internal;
+
+ private UnaryOperator<ITree<Token<K, V>>> transform;
+
+ /**
+ * Create a new transforming initial command.
+ *
+ * @param internal
+ * The initial command to delegate to.
+ *
+ * @param transform
+ * The transform to apply to the returned tree.
+ */
+ public TransformingInitialCommand(InitialCommand<K, V, C> internal,
+ UnaryOperator<ITree<Token<K, V>>> transform) {
+ super();
+ this.internal = internal;
+ this.transform = transform;
+ }
+
+ @Override
+ protected ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ return transform.apply(internal.denote(operator, ctx));
+ }
+
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/UnaryCommand.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/UnaryCommand.java
new file mode 100644
index 0000000..c608362
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/commands/UnaryCommand.java
@@ -0,0 +1,47 @@
+package bjc.utils.parserutils.pratt.commands;
+
+import bjc.utils.data.ITree;
+import bjc.utils.data.Tree;
+import bjc.utils.parserutils.ParserException;
+import bjc.utils.parserutils.pratt.ParserContext;
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * A unary operator.
+ *
+ * @author bjculkin
+ *
+ * @param <K>
+ * The key type of the tokens.
+ *
+ * @param <V>
+ * The value type of the tokens.
+ *
+ * @param <C>
+ * The state type of the parser.
+ */
+public class UnaryCommand<K, V, C> extends AbstractInitialCommand<K, V, C> {
+ private final int nullPwer;
+
+ /**
+ * Create a new unary command.
+ *
+ * @param precedence
+ * The precedence of this operator.
+ */
+ public UnaryCommand(int precedence) {
+ if(precedence < 0) {
+ throw new IllegalArgumentException("Precedence must be non-negative");
+ }
+
+ nullPwer = precedence;
+ }
+
+ @Override
+ protected ITree<Token<K, V>> intNullDenotation(Token<K, V> operator, ParserContext<K, V, C> ctx)
+ throws ParserException {
+ ITree<Token<K, V>> opr = ctx.parse.parseExpression(nullPwer, ctx.tokens, ctx.state, false);
+
+ return new Tree<>(operator, opr);
+ }
+} \ No newline at end of file
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringToken.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringToken.java
new file mode 100644
index 0000000..f156f02
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringToken.java
@@ -0,0 +1,84 @@
+package bjc.utils.parserutils.pratt.tokens;
+
+import bjc.utils.parserutils.pratt.Token;
+
+/**
+ * Simple token implementation for strings.
+ *
+ * @author EVE
+ *
+ */
+public class StringToken implements Token<String, String> {
+ private String key;
+ private String val;
+
+ /**
+ * Create a new string token.
+ *
+ * @param ky
+ * The key for the token.
+ *
+ * @param vl
+ * The value for the token.
+ */
+ public StringToken(String ky, String vl) {
+ key = ky;
+ val = vl;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public String getValue() {
+ return val;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+
+ int result = 1;
+ result = prime * result + ((key == null) ? 0 : key.hashCode());
+ result = prime * result + ((val == null) ? 0 : val.hashCode());
+
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!(obj instanceof StringToken))
+ return false;
+
+ StringToken other = (StringToken) obj;
+
+ if (key == null) {
+ if (other.key != null)
+ return false;
+ } else if (!key.equals(other.key))
+ return false;
+
+ if (val == null) {
+ if (other.val != null)
+ return false;
+ } else if (!val.equals(other.val))
+ return false;
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("StringToken [key='%s', val='%s']", key, val);
+ }
+
+ public static StringToken litToken(String val) {
+ return new StringToken(val, val);
+ }
+}
diff --git a/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringTokenStream.java b/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringTokenStream.java
new file mode 100644
index 0000000..75e86c4
--- /dev/null
+++ b/JPratt/src/main/java/bjc/utils/parserutils/pratt/tokens/StringTokenStream.java
@@ -0,0 +1,56 @@
+package bjc.utils.parserutils.pratt.tokens;
+
+import java.util.Iterator;
+
+import bjc.utils.parserutils.pratt.Token;
+import bjc.utils.parserutils.pratt.TokenStream;
+
+import static bjc.utils.parserutils.pratt.tokens.StringToken.litToken;
+
+/**
+ * Simple implementation of token stream for strings.
+ *
+ * The terminal token here is represented by a token with type and value
+ * '(end)'.
+ *
+ * @author EVE
+ *
+ */
+public class StringTokenStream extends TokenStream<String, String> {
+ private Iterator<Token<String, String>> iter;
+
+ private Token<String, String> curr;
+
+ /**
+ * Create a new token stream from a iterator.
+ *
+ * @param itr
+ * The iterator to use.
+ *
+ */
+ public StringTokenStream(Iterator<Token<String, String>> itr) {
+ iter = itr;
+
+ }
+
+ @Override
+ public Token<String, String> current() {
+ return curr;
+ }
+
+ @Override
+ public Token<String, String> next() {
+ if (iter.hasNext()) {
+ curr = iter.next();
+ } else {
+ curr = litToken("(end)");
+ }
+
+ return curr;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+}