package bjc.pratt; import java.util.HashMap; import java.util.Map; import bjc.pratt.commands.DefaultInitialCommand; import bjc.pratt.commands.DefaultNonInitialCommand; import bjc.utils.data.ITree; import bjc.utils.funcutils.NumberUtils; import bjc.utils.parserutils.ParserException; /** * A configurable Pratt parser for expressions. * * @author EVE * * @param * The key type for the tokens. * * @param * The value type for the tokens. * * @param * The state type of the parser. * * */ public class PrattParser { /* * Default commands that error when used. */ private final NonInitialCommand DEFAULT_LEFT_COMMAND = new DefaultNonInitialCommand<>(); private final InitialCommand DEFAULT_NULL_COMMAND = new DefaultInitialCommand<>(); /* * Left-commands that depend on what the null command was. */ private final Map>> dependantLeftCommands; private final Map>> dependantMetaLeftCommands; /* * The left commands. */ private final Map> leftCommands; private final Map> metaLeftCommands; /* * The initial commands. */ private final Map> nullCommands; private final Map> metaNullCommands; /* * Initial commands only checked for statements. */ private final Map> statementCommands; private final Map> metaStatementCommands; /** * Create a new Pratt parser. * */ public PrattParser() { dependantLeftCommands = new HashMap<>(); dependantMetaLeftCommands = new HashMap<>(); leftCommands = new HashMap<>(); metaLeftCommands = new HashMap<>(); nullCommands = new HashMap<>(); metaNullCommands = new HashMap<>(); statementCommands = new HashMap<>(); metaStatementCommands = 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> parseExpression(final int precedence, final TokenStream tokens, final C state, final boolean isStatement) throws ParserException { if (precedence < 0) throw new IllegalArgumentException("Precedence must be greater than zero"); ParserContext parserContext = new ParserContext<>(tokens, this, state); final Token initToken = tokens.current(); tokens.next(); final K initKey = initToken.getKey(); InitialCommand nullCommand = getInitialCommand(isStatement, initKey, parserContext); ITree> ast = nullCommand.denote(initToken, parserContext); parserContext.initial = initKey; int rightPrec = Integer.MAX_VALUE; while (true) { final Token tok = tokens.current(); final K key = tok.getKey(); NonInitialCommand leftCommand = getNonInitialCommand(key, parserContext); final int leftBind = leftCommand.leftBinding(); if (NumberUtils.between(precedence, rightPrec, leftBind)) { tokens.next(); ast = leftCommand.denote(ast, tok, parserContext); rightPrec = leftCommand.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(final K marker, final NonInitialCommand 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(final K marker, final InitialCommand 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(final K marker, final InitialCommand comm) { statementCommands.put(marker, comm); } /** * Add a dependent non-initial command to this parser. * * @param dependant * The dependent that precedes the command. * * @param marker * The token key that marks the command. * * @param comm * The command. */ public void addDependantCommand(final K dependant, final K marker, final NonInitialCommand comm) { Map> dependantMap = dependantLeftCommands.getOrDefault(dependant, new HashMap<>()); dependantMap.put(marker, comm); } /** * Lookup an initial command. * * @param isStatement * Whether to look for statement commands or not. * * @param key * The key of the command. * * @param ctx * The context for meta-commands. * * @return A command attached to that key, or a default implementation. */ public InitialCommand getInitialCommand(boolean isStatement, K key, ParserContext ctx) { if (isStatement) { if (metaStatementCommands.containsKey(key)) return metaStatementCommands.get(key).getCommand(ctx); else if (statementCommands.containsKey(key)) return statementCommands.get(key); } if (metaNullCommands.containsKey(key)) return metaNullCommands.get(key).getCommand(ctx); else return nullCommands.getOrDefault(key, DEFAULT_NULL_COMMAND); } /** * Lookup a non-initial command. * * @param key * The key of the command. * * @param ctx * The context for meta-commands. * * @return A command attached to that key, or a default implementation. */ public NonInitialCommand getNonInitialCommand(K key, ParserContext ctx) { if (dependantMetaLeftCommands.containsKey(ctx.initial)) { Map> dependantCommands = dependantMetaLeftCommands .get(ctx.initial); if (dependantCommands.containsKey(key)) { return dependantCommands.get(key).getCommand(ctx); } } if (dependantLeftCommands.containsKey(ctx.initial)) { Map> dependantCommands = dependantLeftCommands.get(ctx.initial); if (dependantCommands.containsKey(key)) { return dependantCommands.getOrDefault(key, DEFAULT_LEFT_COMMAND); } } if (metaLeftCommands.containsKey(key)) { return metaLeftCommands.get(key).getCommand(ctx); } else return leftCommands.getOrDefault(key, DEFAULT_LEFT_COMMAND); } }