|
3 | 3 | import net.marcellperger.mathexpr.*; |
4 | 4 | import net.marcellperger.mathexpr.util.Pair; |
5 | 5 | import net.marcellperger.mathexpr.util.Util; |
6 | | -import net.marcellperger.mathexpr.util.UtilCollectors; |
7 | 6 | import org.jetbrains.annotations.NotNull; |
8 | 7 | import org.jetbrains.annotations.Nullable; |
9 | 8 | import org.jetbrains.annotations.Range; |
10 | 9 |
|
11 | 10 | import java.nio.CharBuffer; |
12 | | -import java.util.*; |
| 11 | +import java.util.ArrayList; |
| 12 | +import java.util.Comparator; |
| 13 | +import java.util.List; |
13 | 14 | import java.util.function.Function; |
14 | 15 | import java.util.regex.Matcher; |
15 | 16 | import java.util.regex.Pattern; |
16 | | -import java.util.stream.Collectors; |
17 | 17 |
|
18 | 18 |
|
19 | 19 | public class Parser { |
@@ -79,52 +79,39 @@ public MathSymbol parseExpr() throws ExprParseException { |
79 | 79 |
|
80 | 80 | public MathSymbol parseInfixPrecedenceLevel(int level) throws ExprParseException { |
81 | 81 | discardWhitespace(); |
82 | | - if(level == 0) { |
83 | | - return parseParensOrLiteral(); |
84 | | - } |
85 | | - Set<SymbolInfo> symbols = SymbolInfo.PREC_TO_INFO_MAP.get(level); |
86 | | - if(symbols == null || symbols.isEmpty()) return parseInfixPrecedenceLevel(level - 1); |
87 | | - @Nullable GroupingDirection dirn = symbols.stream() |
88 | | - .map(sm -> sm.groupingDirection).collect(UtilCollectors.singleDistinctItem()); |
89 | | - Map<String, SymbolInfo> infixToSymbolInfo = symbols.stream().collect( // TODO pre-compute/cache these |
90 | | - Collectors.toUnmodifiableMap( |
91 | | - si -> Objects.requireNonNull(si.infix, "null infix not allowed for parseInfixPrecedenceLevel"), |
92 | | - Function.identity())); |
93 | | - String[] infixesToFind = sortedByLength(infixToSymbolInfo.keySet().toArray(String[]::new)); |
| 82 | + if(level == 0) return parseParensOrLiteral(); |
| 83 | + PrecedenceLevelInfo precInfo = SymbolInfo.PREC_LEVELS_INFO.get(level); |
| 84 | + if (precInfo == null) return parseInfixPrecedenceLevel(level - 1); |
94 | 85 | MathSymbol left = parseInfixPrecedenceLevel(level - 1); |
95 | | - if(dirn == GroupingDirection.RightToLeft) { |
96 | | - return parseInfixPrecedenceLevel_RTL(left, level, infixesToFind, infixToSymbolInfo); |
| 86 | + if(precInfo.dirn == GroupingDirection.RightToLeft) { |
| 87 | + return parseInfixPrecedenceLevel_RTL(left, precInfo); |
97 | 88 | } |
98 | | - return parseInfixPrecedenceLevel_LTR(left, level, infixesToFind, infixToSymbolInfo); |
| 89 | + return parseInfixPrecedenceLevel_LTR(left, precInfo); |
99 | 90 | // TODO what if dirn == null? Maybe just disallow the ambiguous case of > 2 operands in same level |
100 | 91 | } |
101 | 92 |
|
102 | | - private MathSymbol parseInfixPrecedenceLevel_RTL(MathSymbol left, int level, String[] infixesToFind, |
103 | | - Map<String, SymbolInfo> infixToSymbolInfo) throws ExprParseException { |
104 | | - // TODO: refactor this mess - 2 separate loops? |
105 | | - // I feel like it should be doable w/ one loop but that may involve risking NullPointerException |
106 | | - // by setting some members of LeftRightBinaryOperation to null |
107 | | - // Actually, this first loop could be common between them if we make the other path 2 loops as well |
| 93 | + private MathSymbol parseInfixPrecedenceLevel_RTL(MathSymbol left, PrecedenceLevelInfo precInfo) throws ExprParseException { |
108 | 94 | String op; |
109 | 95 | List<Pair<SymbolInfo, MathSymbol>> otherOps = new ArrayList<>(); |
110 | 96 | discardWhitespace(); |
111 | | - while((op = discardMatchesNextAny_optionsSorted(infixesToFind)) != null) { |
112 | | - otherOps.add(new Pair<>(Util.getNotNull(infixToSymbolInfo, op), parseInfixPrecedenceLevel(level - 1))); |
| 97 | + while((op = discardMatchesNextAny_optionsSorted(precInfo.sortedInfixes)) != null) { |
| 98 | + otherOps.add(new Pair<>( |
| 99 | + Util.getNotNull(precInfo.infixToSymbolMap, op), |
| 100 | + parseInfixPrecedenceLevel(precInfo.precedence - 1))); |
113 | 101 | discardWhitespace(); |
114 | 102 | } |
115 | 103 | return otherOps.reversed().stream().reduce((rightpair, leftpair) -> |
116 | 104 | leftpair.asVars((preOp, argL) -> |
117 | | - new Pair<>(preOp, rightpair.asVars((midOp, argR) -> midOp.getBiConstructor().construct(argL, argR)))) |
118 | | - ).map(p -> p.asVars((midOp, argR) -> midOp.getBiConstructor().construct(left, argR))).orElse(left); |
| 105 | + new Pair<>(preOp, rightpair.asVars((midOp, argR) -> BinaryOperation.construct(argL, midOp, argR)))) |
| 106 | + ).map(p -> p.asVars((midOp, argR) -> BinaryOperation.construct(left, midOp, argR))).orElse(left); |
119 | 107 | } |
120 | 108 |
|
121 | | - private MathSymbol parseInfixPrecedenceLevel_LTR(MathSymbol left, int level, String[] infixesToFind, |
122 | | - Map<String, SymbolInfo> infixToSymbolInfo) throws ExprParseException { |
| 109 | + private MathSymbol parseInfixPrecedenceLevel_LTR(MathSymbol left, PrecedenceLevelInfo precInfo) throws ExprParseException { |
123 | 110 | String op; |
124 | 111 | discardWhitespace(); |
125 | | - while((op = discardMatchesNextAny_optionsSorted(infixesToFind)) != null) { |
126 | | - left = Util.getNotNull(infixToSymbolInfo, op).getBiConstructor() |
127 | | - .construct(left, parseInfixPrecedenceLevel(level - 1)); |
| 112 | + while((op = discardMatchesNextAny_optionsSorted(precInfo.sortedInfixes)) != null) { |
| 113 | + left = BinaryOperation.construct( |
| 114 | + left, Util.getNotNull(precInfo.infixToSymbolMap, op), parseInfixPrecedenceLevel(precInfo.precedence - 1)); |
128 | 115 | discardWhitespace(); |
129 | 116 | } |
130 | 117 | return left; |
@@ -182,24 +169,24 @@ protected boolean matchesNext(@NotNull String expected) { |
182 | 169 | return src.startsWith(expected, /*start*/idx); |
183 | 170 | } |
184 | 171 |
|
185 | | - private @NotNull String @NotNull [] sortedByLength(@NotNull String @NotNull[] arr) { |
186 | | - return Arrays.stream(arr).sorted(Comparator.comparingInt(String::length).reversed()).toArray(String[]::new); |
| 172 | + private @NotNull List<@NotNull String> sortedByLength(@NotNull List<@NotNull String> arr) { |
| 173 | + return arr.stream().sorted(Comparator.comparingInt(String::length).reversed()).toList(); |
187 | 174 | } |
188 | | - private @Nullable String matchesNextAny_optionsSorted(@NotNull String... expected) { |
189 | | - return Arrays.stream(expected).filter(this::matchesNext).findFirst().orElse(null); |
| 175 | + private @Nullable String matchesNextAny_optionsSorted(@NotNull List<@NotNull String> expected) { |
| 176 | + return expected.stream().filter(this::matchesNext).findFirst().orElse(null); |
190 | 177 | } |
191 | | - private @Nullable String discardMatchesNextAny_optionsSorted(@NotNull String... expected) { |
| 178 | + private @Nullable String discardMatchesNextAny_optionsSorted(@NotNull List<@NotNull String> expected) { |
192 | 179 | String s = matchesNextAny_optionsSorted(expected); |
193 | 180 | if(s != null) discardN(s.length()); |
194 | 181 | return s; |
195 | 182 | } |
196 | 183 | @SuppressWarnings("unused") |
197 | | - protected @Nullable String matchesNextAny(@NotNull String... expected) { |
| 184 | + protected @Nullable String matchesNextAny(@NotNull List<@NotNull String> expected) { |
198 | 185 | // Try to longer ones first, then shorter ones. |
199 | 186 | return matchesNextAny_optionsSorted(sortedByLength(expected)); |
200 | 187 | } |
201 | 188 | @SuppressWarnings("unused") |
202 | | - protected @Nullable String discardMatchesNextAny(@NotNull String... expected) { |
| 189 | + protected @Nullable String discardMatchesNextAny(@NotNull List<@NotNull String> expected) { |
203 | 190 | // Try to longer ones first, then shorter ones. |
204 | 191 | return discardMatchesNextAny_optionsSorted(sortedByLength(expected)); |
205 | 192 | } |
|
0 commit comments