summaryrefslogtreecommitdiff
path: root/BJC-Utils2/src/main/java/bjc/utils/parserutils/pratt/PrattParser.java
blob: a2cefda75a5d2c7e920b5e61d9d521f1e85a158c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package bjc.utils.parserutils.pratt;

import bjc.utils.data.ITree;
import bjc.utils.funcutils.NumberUtils;
import bjc.utils.parserutils.ParserException;

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> {
	private final LeftCommand<K, V, C>	DEFAULT_LEFT_COMMAND	= new DefaultLeftCommand<>();
	private final NullCommand<K, V, C>	DEFAULT_NULL_COMMAND	= new DefaultNullCommand<>();

	private Map<K, LeftCommand<K, V, C>>	leftCommands;
	private Map<K, NullCommand<K, V, C>>	nullCommands;

	/**
	 * Create a new Pratt parser.
	 * 
	 * @param terminal
	 *                The terminal symbol.
	 */
	public PrattParser() {
		leftCommands = new HashMap<>();
		nullCommands = 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.
	 * 
	 * @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)
			throws ParserException {
		if(precedence < 0) {
			throw new IllegalArgumentException("Precedence must be greater than zero");
		}

		Token<K, V> initToken = tokens.current();
		tokens.next();

		ITree<Token<K, V>> ast = nullCommands.getOrDefault(initToken.getKey(), DEFAULT_NULL_COMMAND)
				.nullDenotation(initToken, new ParserContext<>(tokens, this, state));

		int rightPrec = Integer.MAX_VALUE;

		while(true) {
			Token<K, V> tok = tokens.current();

			K key = tok.getKey();

			LeftCommand<K, V, C> command = leftCommands.getOrDefault(key, DEFAULT_LEFT_COMMAND);
			int leftBind = command.leftBinding();

			if(NumberUtils.between(precedence, rightPrec, leftBind)) {
				tokens.next();

				ast = command.leftDenote(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, LeftCommand<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, NullCommand<K, V, C> comm) {
		nullCommands.put(marker, comm);
	}
}