Skip to content

Commit 52b3071

Browse files
committed
Started jobs to upgrade the parsing algorithm
1 parent e3166f0 commit 52b3071

File tree

1 file changed

+112
-129
lines changed

1 file changed

+112
-129
lines changed

jcalc/src/main/java/cu/lt/joe/jcalc/algorithms/ShuntingYardAlgImpl.java

Lines changed: 112 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,6 @@
1717
*/
1818
public class ShuntingYardAlgImpl extends AlgorithmImplementation
1919
{
20-
/**
21-
* Takes a Math expression and returns a {@link String} containing the Math expression after
22-
* applying some common fixes to it. It returns {@code null} if the provided expression is
23-
* either {@code null} or empty.
24-
*
25-
* @param mathExpression the Math expression to clean
26-
* @return A {@link String} containing the given Math expression with some fixes
27-
* @author <a href="https://github.com/jr20xx">jr20xx</a>
28-
* @since 2.0.6
29-
*/
30-
private static String cleanMathExpression(String mathExpression)
31-
{
32-
if (mathExpression == null || mathExpression.isEmpty()) return null;
33-
String cleanedMathExpression = mathExpression.matches(".*\\s+.*") ?
34-
mathExpression.replaceAll("\\s+", "") : mathExpression;
35-
if (cleanedMathExpression.isEmpty()) return null;
36-
return cleanedMathExpression;
37-
}
38-
3920
/**
4021
* Takes a Math operator as a String and returns its precedence.
4122
*
@@ -76,150 +57,152 @@ private static int getOperatorPrecedence(String operator)
7657
*/
7758
public static String solveMathExpression(String mathExpression, boolean balanceParentheses, int precision)
7859
{
79-
mathExpression = cleanMathExpression(mathExpression);
80-
if (mathExpression == null) return null;
81-
8260
ArrayDeque<BigDecimal> output = new ArrayDeque<>();
8361
ArrayDeque<String> operators = new ArrayDeque<>();
8462
StringBuilder numberBuilder = new StringBuilder();
8563
balanceParentheses = balanceParentheses && (mathExpression.contains("(") || mathExpression.contains(")"));
8664
int openParenthesesCount = 0, actualExpressionLength = mathExpression.length() - 1;
8765

66+
char previouslyFoundChar = '\u0000';
8867
for (int i = 0; i <= actualExpressionLength; i++)
8968
{
90-
char currentChar = mathExpression.charAt(i), previousChar = i > 0 ? mathExpression.charAt(i - 1) : '\u0000',
91-
nextChar = i + 1 < mathExpression.length() ? mathExpression.charAt(i + 1) : '\u0000';
92-
if (isMathConstant(currentChar))
69+
char currentChar = mathExpression.charAt(i);
70+
if (!Character.isWhitespace(currentChar))
9371
{
94-
if (previousChar == ')' || previousChar == '!' || isPartOfANumber(previousChar) || isMathConstant(previousChar))
95-
operators.push("*");
96-
output.push(BigDecimal.valueOf(currentChar == 'e' ? Math.E : Math.PI));
97-
}
98-
else if (isPartOfANumber(currentChar))
99-
{
100-
for (; i < mathExpression.length() && isPartOfANumber(currentChar); i++,
101-
currentChar = i < mathExpression.length() ? mathExpression.charAt(i) : '\u0000',
102-
nextChar = i + 1 < mathExpression.length() ? mathExpression.charAt(i + 1) : '\u0000')
72+
if (isMathConstant(currentChar))
73+
{
74+
if (previouslyFoundChar == ')' || previouslyFoundChar == '!' || isPartOfANumber(previouslyFoundChar) || isMathConstant(previouslyFoundChar))
75+
operators.push("*");
76+
output.push(BigDecimal.valueOf(currentChar == 'e' ? Math.E : Math.PI));
77+
}
78+
else if (isPartOfANumber(currentChar))
10379
{
104-
if ((currentChar == '.' || currentChar == ',') && numberBuilder.indexOf(".") != -1)
105-
throw new SyntaxErrorException("A number can only contain a single decimal point");
106-
else if (currentChar == 'E' && !(nextChar == '+' || nextChar == '-' || Character.isDigit(nextChar)))
107-
throw new SyntaxErrorException("Found unexpected character '" + mathExpression.charAt(++i) + "' after E");
108-
else if (currentChar == 'E' && (nextChar == '+' || nextChar == '-' || Character.isDigit(nextChar)))
109-
numberBuilder.append(currentChar).append(mathExpression.charAt(++i));
80+
for (; i < mathExpression.length(); i++)
81+
{
82+
currentChar = mathExpression.charAt(i);
83+
if (!Character.isWhitespace(currentChar))
84+
{
85+
if (currentChar == '.' || currentChar == ',')
86+
if (numberBuilder.indexOf(".") == -1)
87+
numberBuilder.append('.');
88+
else
89+
throw new SyntaxErrorException("A number can contain only a single decimal separator");
90+
else if (previouslyFoundChar == 'E')
91+
if (currentChar == '+' || currentChar == '-' || Character.isDigit(currentChar))
92+
numberBuilder.append(previouslyFoundChar).append(currentChar);
93+
else
94+
throw new SyntaxErrorException("Wrong usage of the E notation detected");
95+
else if (Character.isDigit(currentChar))
96+
numberBuilder.append(currentChar);
97+
else if (!isPartOfANumber(currentChar))
98+
{
99+
previouslyFoundChar = mathExpression.charAt(--i);
100+
break;
101+
}
102+
previouslyFoundChar = currentChar;
103+
}
104+
}
105+
String numberStr = numberBuilder.toString();
106+
if (isNumber(numberStr))
107+
{
108+
if (previouslyFoundChar == ')' || previouslyFoundChar == '!' || isMathConstant(previouslyFoundChar))
109+
operators.push("*");
110+
output.push(new BigDecimal(numberStr));
111+
numberBuilder.setLength(0);
112+
while (!operators.isEmpty() && (isUnaryOperator(operators.peek()) && !operators.peek().equals("u-")))
113+
performStacking(output, operators.pop());
114+
}
110115
else
111-
numberBuilder.append(currentChar == ',' ? '.' : currentChar);
116+
throw new SyntaxErrorException("Found an invalid number \"" + numberStr + "\" while parsing the given expression");
117+
continue;
112118
}
113-
String numberStr = numberBuilder.toString();
114-
if (isNumber(numberStr))
119+
else if (isSquareRootOperator(currentChar + ""))
115120
{
116-
if (previousChar == ')' || previousChar == '!' || isMathConstant(previousChar))
121+
if (previouslyFoundChar == ')' || isFactorialOperator(previouslyFoundChar + "") || isMathConstant(previouslyFoundChar))
117122
operators.push("*");
118-
output.push(new BigDecimal(numberStr));
119-
numberBuilder.setLength(0);
120-
i--;
123+
operators.push(currentChar + "");
124+
}
125+
else if (isFactorialOperator(currentChar + ""))
126+
{
127+
if (output.isEmpty())
128+
throw new SyntaxErrorException("Factorial operator '!' has no preceding number");
121129
while (!operators.isEmpty() && (isUnaryOperator(operators.peek()) && !operators.peek().equals("u-")))
122130
performStacking(output, operators.pop());
131+
performStacking(output, currentChar + "");
123132
}
124-
else
125-
throw new SyntaxErrorException("Found an invalid number \"" + numberStr + "\" while parsing the given expression");
126-
}
127-
else if (isSquareRootOperator(currentChar + ""))
128-
{
129-
if (i < actualExpressionLength)
130-
{
131-
if (previousChar == ')' || isFactorialOperator(previousChar + "") || isMathConstant(previousChar))
132-
operators.push("*");
133-
if (Character.isLetterOrDigit(nextChar) || nextChar == '(' || isSquareRootOperator(nextChar + "") || isMathConstant(nextChar))
134-
operators.push(currentChar + "");
135-
else
136-
throw new SyntaxErrorException("Identified unary operator '" + currentChar + "' with an invalid character after it: '" + nextChar + "'");
137-
}
138-
}
139-
else if (isFactorialOperator(currentChar + ""))
140-
{
141-
if (output.isEmpty())
142-
throw new SyntaxErrorException("Factorial operator '!' has no preceding number");
143-
while (!operators.isEmpty() && (isUnaryOperator(operators.peek()) && !operators.peek().equals("u-")))
144-
performStacking(output, operators.pop());
145-
performStacking(output, currentChar + "");
146-
}
147-
else if ((currentChar == '-' || currentChar == '+') && (i == 0 || previousChar == '(' || (isOperator(previousChar + "") && !isFactorialOperator(previousChar + ""))))
148-
{
149-
if (Character.isLetterOrDigit(nextChar) || nextChar == '(' || isSquareRootOperator(nextChar + "") || isMathConstant(nextChar))
133+
else if ((currentChar == '-' || currentChar == '+') && (i == 0 || previouslyFoundChar == '(' || (isOperator(previouslyFoundChar + "") && !isFactorialOperator(previouslyFoundChar + ""))))
150134
{
151135
if (currentChar == '-') operators.push("u-");
152136
}
153-
else
154-
throw new SyntaxErrorException("Identified unary operator '" + currentChar + "' with an invalid character after it: '" + nextChar + "'");
155-
}
156-
else if (currentChar == '(')
157-
{
158-
if (previousChar == ')' || isFactorialOperator(previousChar + "") || isMathConstant(previousChar) || (Character.isDigit(previousChar) && !operators.isEmpty() && !operators.peek().equals("log2")))
159-
operators.push("*");
160-
operators.push(currentChar + "");
161-
if (balanceParentheses) openParenthesesCount++;
162-
}
163-
else if (currentChar == ')')
164-
{
165-
if (isOperator(previousChar + "") && previousChar != '!')
166-
throw new SyntaxErrorException("Unexpected character ')' found after an operator");
167-
else if (previousChar == '(')
168-
output.push(BigDecimal.ONE);
169-
while (!operators.isEmpty() && !operators.peek().equals("("))
170-
performStacking(output, operators.pop());
171-
if (operators.isEmpty() && !balanceParentheses)
172-
throw new UnbalancedParenthesesException("Parentheses are not well placed");
173-
if (!operators.isEmpty())
137+
else if (currentChar == '(')
174138
{
175-
operators.pop();
176-
if (balanceParentheses) openParenthesesCount--;
139+
if (previouslyFoundChar == ')' || isFactorialOperator(previouslyFoundChar + "") || isMathConstant(previouslyFoundChar) || (Character.isDigit(previouslyFoundChar) && !operators.isEmpty() && !operators.peek().equals("log2")))
140+
operators.push("*");
141+
operators.push(currentChar + "");
142+
if (balanceParentheses) openParenthesesCount++;
177143
}
178-
if (!operators.isEmpty() && isUnaryOperator(operators.peek()))
179-
performStacking(output, operators.pop());
180-
}
181-
else if (isOperator(currentChar + ""))
182-
{
183-
if ((isOperator(previousChar + "") || previousChar == '(') && previousChar != '!')
184-
throw new SyntaxErrorException("Unexpected character '" + currentChar + "' found after '" + previousChar + "'");
185-
if (i < actualExpressionLength)
144+
else if (currentChar == ')')
186145
{
187-
currentChar = currentChar == '×' ? '*' : currentChar == '÷' ? '/' : currentChar;
188-
while (!operators.isEmpty() && !operators.peek().equals("(") && getOperatorPrecedence(operators.peek()) >= getOperatorPrecedence(currentChar + "") && currentChar != '^')
146+
if (isOperator(previouslyFoundChar + "") && previouslyFoundChar != '!')
147+
throw new SyntaxErrorException("Unexpected character ')' found after an operator");
148+
else if (previouslyFoundChar == '(')
149+
output.push(BigDecimal.ONE);
150+
while (!operators.isEmpty() && !operators.peek().equals("("))
151+
performStacking(output, operators.pop());
152+
if (operators.isEmpty() && !balanceParentheses)
153+
throw new UnbalancedParenthesesException("Parentheses are not well placed");
154+
if (!operators.isEmpty())
155+
{
156+
operators.pop();
157+
if (balanceParentheses) openParenthesesCount--;
158+
}
159+
if (!operators.isEmpty() && isUnaryOperator(operators.peek()))
189160
performStacking(output, operators.pop());
190-
operators.push(currentChar + "");
191161
}
192-
}
193-
else if (Character.isLetter(currentChar))
194-
{
195-
if (i == 0 || isNumber(previousChar + "") || isOperator(previousChar + "") || previousChar == ')' || previousChar == '(')
162+
else if (isOperator(currentChar + ""))
196163
{
197-
StringBuilder unaryOperatorBuilder = new StringBuilder();
198-
for (; i < mathExpression.length() && Character.isLetter(currentChar); i++,
199-
currentChar = i < mathExpression.length() ? mathExpression.charAt(i) : '\u0000')
200-
unaryOperatorBuilder.append(currentChar);
201-
String assembledUnaryOperator = unaryOperatorBuilder.toString();
202-
unaryOperatorBuilder.setLength(0);
203-
if (isUnaryOperator(assembledUnaryOperator))
164+
if ((isOperator(previouslyFoundChar + "") || previouslyFoundChar == '(') && !isFactorialOperator(previouslyFoundChar + ""))
165+
throw new SyntaxErrorException("Unexpected character '" + currentChar + "' found after '" + previouslyFoundChar + "'");
166+
if (i < actualExpressionLength)
204167
{
205-
if (isNumber(previousChar + "") || previousChar == ')')
206-
operators.push("*");
207-
if (currentChar == '2' && assembledUnaryOperator.equals("log"))
208-
operators.push("log2");
209-
else
168+
currentChar = currentChar == '×' ? '*' : currentChar == '÷' ? '/' : currentChar;
169+
while (!operators.isEmpty() && !operators.peek().equals("(") && getOperatorPrecedence(operators.peek()) >= getOperatorPrecedence(currentChar + "") && currentChar != '^')
170+
performStacking(output, operators.pop());
171+
operators.push(currentChar + "");
172+
}
173+
}
174+
else if (Character.isLetter(currentChar))
175+
{
176+
if (i == 0 || isNumber(previouslyFoundChar + "") || isOperator(previouslyFoundChar + "") || previouslyFoundChar == ')' || previouslyFoundChar == '(')
177+
{
178+
StringBuilder unaryOperatorBuilder = new StringBuilder();
179+
for (; i < mathExpression.length() && Character.isLetter(currentChar); i++,
180+
currentChar = i < mathExpression.length() ? mathExpression.charAt(i) : '\u0000')
181+
unaryOperatorBuilder.append(currentChar);
182+
String assembledUnaryOperator = unaryOperatorBuilder.toString();
183+
unaryOperatorBuilder.setLength(0);
184+
if (isUnaryOperator(assembledUnaryOperator))
210185
{
211-
operators.push(assembledUnaryOperator);
212-
i--;
186+
if (isNumber(previouslyFoundChar + "") || previouslyFoundChar == ')')
187+
operators.push("*");
188+
if (currentChar == '2' && assembledUnaryOperator.equals("log"))
189+
operators.push("log2");
190+
else
191+
{
192+
operators.push(assembledUnaryOperator);
193+
i--;
194+
}
213195
}
196+
else
197+
throw new SyntaxErrorException("Found invalid token \"" + assembledUnaryOperator + "\" while parsing the expression");
214198
}
215199
else
216-
throw new SyntaxErrorException("Found invalid token \"" + assembledUnaryOperator + "\" while parsing the expression");
200+
throw new SyntaxErrorException("Found misplaced character '" + currentChar + "' after '" + previouslyFoundChar + "'");
217201
}
218202
else
219-
throw new SyntaxErrorException("Found misplaced character '" + currentChar + "' after '" + previousChar + "'");
203+
throw new SyntaxErrorException("Illegal character '" + currentChar + "' found while parsing the expression");
204+
previouslyFoundChar = currentChar;
220205
}
221-
else
222-
throw new SyntaxErrorException("Illegal character '" + currentChar + "' found while parsing the expression");
223206
}
224207

225208
if (output.isEmpty()) return null;

0 commit comments

Comments
 (0)