Skip to content

Commit f11139b

Browse files
Implement Pow in parse and add basic tests
1 parent af44b2a commit f11139b

5 files changed

Lines changed: 69 additions & 14 deletions

File tree

src/main/java/net/marcellperger/mathexpr/SymbolInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
public enum SymbolInfo {
2020
// Let's say that precedence 0 is for (parens) OR literals - TODO add a class?? but it wouldn't actually be used !
21-
// POW(PowOperation.class, 1, GroupingDirection.RightToLeft, "**"),
21+
POW(PowOperation.class, 1, GroupingDirection.RightToLeft, "**"), // TODO PowOperation::new
2222
MUL(MulOperation.class, 2, GroupingDirection.LeftToRight, "*", MulOperation::new),
2323
DIV(DivOperation.class, 2, GroupingDirection.LeftToRight, "/", DivOperation::new),
2424

src/main/java/net/marcellperger/mathexpr/parser/Parser.java

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
package net.marcellperger.mathexpr.parser;
22

33
import net.marcellperger.mathexpr.*;
4+
import net.marcellperger.mathexpr.util.Pair;
45
import net.marcellperger.mathexpr.util.Util;
56
import net.marcellperger.mathexpr.util.UtilCollectors;
67
import org.jetbrains.annotations.NotNull;
78
import org.jetbrains.annotations.Nullable;
89
import org.jetbrains.annotations.Range;
910

1011
import java.nio.CharBuffer;
11-
import java.util.Arrays;
12-
import java.util.Comparator;
13-
import java.util.Map;
14-
import java.util.Objects;
15-
import java.util.Set;
12+
import java.util.*;
1613
import java.util.function.Function;
1714
import java.util.regex.Matcher;
1815
import java.util.regex.Pattern;
@@ -88,7 +85,8 @@ public MathSymbol parseInfixPrecedenceLevel(int level) throws ExprParseException
8885
Set<SymbolInfo> symbols = Util.requireNonEmptyNonNull(SymbolInfo.PREC_TO_INFO_MAP.get(level));
8986
@Nullable GroupingDirection dirn = symbols.stream()
9087
.map(sm -> sm.groupingDirection).distinct().collect(UtilCollectors.singleItem());
91-
assert dirn == GroupingDirection.LeftToRight: "RTL/unknown operators not implemented yet"; // TODO
88+
// if(dirn != GroupingDirection.LeftToRight) { return parseInfixPrecedenceLevel(level - 1); } // TODO TDD: remove
89+
// assert dirn == GroupingDirection.LeftToRight: "RTL/unknown operators not implemented yet"; // TODO
9290
Map<String, SymbolInfo> infixToSymbolInfo = symbols.stream().collect( // TODO pre-compute/cache these
9391
Collectors.toUnmodifiableMap(
9492
si -> Objects.requireNonNull(si.infix, "null infix not allowed for parseInfixPrecedenceLevel"),
@@ -97,6 +95,29 @@ public MathSymbol parseInfixPrecedenceLevel(int level) throws ExprParseException
9795
MathSymbol left = parseInfixPrecedenceLevel(level - 1);
9896
String op;
9997

98+
if(dirn == GroupingDirection.RightToLeft) {
99+
// TODO: refactor this mess - 2 separate loops?
100+
// I feel like it should be doable w/ one loop but that may involve risking NullPointerException
101+
// by setting some members of LeftRightBinaryOperation to null
102+
List<Pair<SymbolInfo, MathSymbol>> otherOps = new ArrayList<>();
103+
discardWhitespace();
104+
while((op = discardMatchesNextAny_optionsSorted(infixesToFind)) != null) {
105+
SymbolInfo opInfo = Objects.requireNonNull(infixToSymbolInfo.get(op));
106+
MathSymbol right = parseInfixPrecedenceLevel(level - 1);
107+
otherOps.add(new Pair<>(opInfo, right));
108+
discardWhitespace();
109+
}
110+
if(otherOps.isEmpty()) return left;
111+
SymbolInfo currOp = otherOps.getLast().left;
112+
MathSymbol right = otherOps.removeLast().right;
113+
while(!otherOps.isEmpty()) {
114+
Pair<SymbolInfo, MathSymbol> currPair = otherOps.removeLast();
115+
MathSymbol leftLocal = currPair.right;
116+
right = currOp.getBiConstructor().construct(leftLocal, right);
117+
currOp = currPair.left;
118+
}
119+
return currOp.getBiConstructor().construct(left, right);
120+
}
100121
discardWhitespace();
101122
while((op = discardMatchesNextAny_optionsSorted(infixesToFind)) != null) {
102123
SymbolInfo opInfo = Objects.requireNonNull(infixToSymbolInfo.get(op));
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package net.marcellperger.mathexpr.util;
2+
3+
public class Pair<T, U> {
4+
public T left;
5+
public U right;
6+
7+
public Pair(T left, U right) {
8+
this.left = left;
9+
this.right = right;
10+
}
11+
}

src/test/java/net/marcellperger/mathexpr/MathSymbolTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ void test_fmt() {
1818
assertExprFmt(CommonData.getBigData2_minimumParens());
1919
}
2020

21+
@Test
22+
void test_fmt__pow() {
23+
// TODO decide spacing: `2.2**3.1` or `2.2 ** 3.1`
24+
assertExprFmt(new ObjStringPair(new PowOperation(new BasicDoubleSymbol(0.2), new BasicDoubleSymbol(5.5)), "0.2 ** 5.5"));
25+
assertExprFmt(new ObjStringPair(new PowOperation(new PowOperation(new BasicDoubleSymbol(0.2), new BasicDoubleSymbol(5.5)), new BasicDoubleSymbol(3.3)),
26+
"(0.2 ** 5.5) ** 3.3"));
27+
assertExprFmt(new ObjStringPair(new PowOperation(new BasicDoubleSymbol(0.2), new PowOperation(new BasicDoubleSymbol(5.5), new BasicDoubleSymbol(3.3))),
28+
"0.2 ** 5.5 ** 3.3"));
29+
}
30+
2131
@Test
2232
void test_calculateValue() {
2333
assertExprValue(-31.161, CommonData.getBigData1_minimumParens());

src/test/java/net/marcellperger/mathexpr/parser/ParserTest.java

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44
import net.marcellperger.mathexpr.util.Util;
55
import net.marcellperger.mathexpr.util.UtilCollectors;
66
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
78
import org.junit.jupiter.api.Test;
89

910
import java.lang.reflect.Field;
1011
import java.util.Arrays;
1112
import java.util.HashMap;
1213
import java.util.Map;
1314
import java.util.Map.Entry;
15+
import java.util.Optional;
1416

1517
import static org.junit.jupiter.api.Assertions.*;
1618

1719
class ParserTest {
18-
public static final int MUL_PREC = 2;
19-
public static final int ADD_PREC = 3;
20+
public static final int POW_PREC = SymbolInfo.POW.precedence;
21+
public static final int MUL_PREC = SymbolInfo.MUL.precedence;
22+
public static final int ADD_PREC = SymbolInfo.ADD.precedence;
2023

2124
boolean nocache = false;
2225

@@ -124,6 +127,16 @@ void parsePrecedenceLevel() {
124127
new DivOperation(new DivOperation(new BasicDoubleSymbol(.9), new BasicDoubleSymbol(2.)), new BasicDoubleSymbol(3.3)));
125128
}
126129

130+
@Test
131+
void parsePrecedenceLevel_pow() {
132+
assertInfixParsesTo("1.2**9.1", POW_PREC,
133+
new PowOperation(new BasicDoubleSymbol(1.2), new BasicDoubleSymbol(9.1)));
134+
assertInfixParsesTo("1.2**9.1**.3", POW_PREC,
135+
new PowOperation(new BasicDoubleSymbol(1.2), new PowOperation(new BasicDoubleSymbol(9.1), new BasicDoubleSymbol(.3))));
136+
assertInfixParsesTo("1.2**9.1+.3", ADD_PREC,
137+
new AddOperation(new PowOperation(new BasicDoubleSymbol(1.2), new BasicDoubleSymbol(9.1)), new BasicDoubleSymbol(.3)));
138+
}
139+
127140
@Test
128141
void parsePrecedenceLevel_nocache() {
129142
boolean origNocache = nocache;
@@ -160,15 +173,15 @@ public void close() {
160173
}
161174

162175

163-
Map<SymbolInfo, BinOpBiConstructor<?>> origCache = new HashMap<>();
176+
Map<SymbolInfo, Optional<BinOpBiConstructor<?>>> origCache = new HashMap<>();
164177
void clearCache() {
165178
Field cacheField = getBiConstructorCache();
166179

167-
origCache = Arrays.stream(SymbolInfo.values()).<Entry<SymbolInfo, BinOpBiConstructor<?>>>map(sym -> {
180+
origCache = Arrays.stream(SymbolInfo.values()).<Entry<SymbolInfo, Optional<BinOpBiConstructor<?>>>>map(sym -> {
168181
try {
169-
BinOpBiConstructor<?> cachedValue = (BinOpBiConstructor<?>)cacheField.get(sym);
182+
@Nullable BinOpBiConstructor<?> cachedValue = (BinOpBiConstructor<?>)cacheField.get(sym);
170183
cacheField.set(sym, null);
171-
return Util.makeEntry(sym, cachedValue);
184+
return Util.makeEntry(sym, Optional.ofNullable(cachedValue));
172185
} catch (IllegalAccessException e) {
173186
throw Util.excToError(e);
174187
}
@@ -179,7 +192,7 @@ void restoreCache() {
179192
Field cacheField = getBiConstructorCache();
180193
origCache.forEach((key, value) -> {
181194
try {
182-
cacheField.set(key, value);
195+
cacheField.set(key, value.orElse(null));
183196
} catch (IllegalAccessException ex) {
184197
throw Util.excToError(ex);
185198
}

0 commit comments

Comments
 (0)