You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
184 lines
4.3 KiB
184 lines
4.3 KiB
package net.mightypork.rcalc;
|
|
|
|
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
import net.mightypork.rcalc.numbers.Fraction;
|
|
import net.mightypork.rcalc.tokens.*;
|
|
|
|
|
|
/**
|
|
* Utility for converting an expression from String to a list of tokens
|
|
*
|
|
* @author Ondrej Hruska
|
|
*/
|
|
public class Tokenizer implements IDebugable {
|
|
|
|
/** Debug mode flag */
|
|
private boolean debug = false;
|
|
|
|
/** Deep debug mode flag */
|
|
private boolean deepDebug = false;
|
|
|
|
|
|
@Override
|
|
public boolean isDebug() {
|
|
|
|
return debug;
|
|
}
|
|
|
|
|
|
@Override
|
|
public void setDebug(boolean debug) {
|
|
|
|
this.debug = debug;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get if deep debug mode is enabled
|
|
*
|
|
* @return is in deep debug mode
|
|
*/
|
|
public boolean isDeepDebug() {
|
|
|
|
return deepDebug && debug;
|
|
}
|
|
|
|
|
|
/**
|
|
* Enable / disable deep debug mode
|
|
*
|
|
* @param debug
|
|
*/
|
|
public void setDeepDebug(boolean debug) {
|
|
|
|
this.deepDebug = this.debug = debug;
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse a string to a list of expression tokens
|
|
*
|
|
* @param input string with expression to parse
|
|
* @return token list
|
|
*/
|
|
public TokenList tokenize(String input) {
|
|
|
|
// discard whitespace
|
|
input = input.replaceAll("\\s", "");
|
|
|
|
// check parentheses
|
|
int level = 0;
|
|
for (char c : input.toCharArray()) {
|
|
if (c == '(') level++;
|
|
if (c == ')') level--;
|
|
|
|
if (level < 0) throw new ParseError("Unbalanced parentheses.");
|
|
}
|
|
if (level != 0) throw new ParseError("Unbalanced parentheses.");
|
|
|
|
// translate ** to ^
|
|
input = input.replaceAll("[*]{2}", "^");
|
|
if (debug && deepDebug) System.out.println("\nTranslate '**' to '^'\n" + input);
|
|
|
|
// enclose ^ left operand in parentheses
|
|
input = input.replaceAll("([0-9.]+[!]?)\\^", "($1)^");
|
|
if (debug && deepDebug) System.out.println("\nWrap left numeric operand of '^' with parentheses\n" + input);
|
|
|
|
// add multiplication operator for implicit multiplication
|
|
input = input.replaceAll("(?<=[0-9)!])(?=[(])", "*"); // )( -> )*(
|
|
input = input.replaceAll("(?<=[)!])(?=[0-9(])", "*");
|
|
if (debug && deepDebug) System.out.println("\nApply implicit multiplication\n" + input);
|
|
|
|
// normalize series of '+' and '-'
|
|
StringBuffer outputBuffer = new StringBuffer();
|
|
Matcher matcher = Pattern.compile("([+\\-]{2,})").matcher(input);
|
|
while (matcher.find()) {
|
|
int minus = 0;
|
|
for (char c : matcher.group().toCharArray()) {
|
|
if (c == '-') minus++;
|
|
}
|
|
minus = minus % 2;
|
|
matcher.appendReplacement(outputBuffer, minus == 0 ? "+" : "-");
|
|
}
|
|
matcher.appendTail(outputBuffer);
|
|
input = outputBuffer.toString();
|
|
if (debug && deepDebug) System.out.println("\nNormalize sequences of '+' and '-'\n" + input);
|
|
|
|
// turn '-' to '+-' between numbers
|
|
input = input.replaceAll("(?<=[0-9)!])-(?=[0-9.])", "+-");
|
|
if (debug && deepDebug) System.out.println("\n'-' to '+-' between numbers\n" + input);
|
|
|
|
// convert '-' to '+-1*' in front of non-numbers
|
|
input = input.replaceAll("-(?=[^0-9.])", "+-1*");
|
|
if (debug && deepDebug) System.out.println("\n'-' to '+-1*' in front of non-numbers\n" + input);
|
|
|
|
// discard useless + signs
|
|
input = input.replaceAll("(?<=[^)0-9!]|^)\\+", "");
|
|
if (debug && deepDebug) System.out.println("\nDiscard '+' at beginning of scope\n" + input);
|
|
|
|
if (debug) System.out.println("\nParsing tokens...");
|
|
|
|
TokenList list = new TokenList();
|
|
|
|
String buffer = "";
|
|
|
|
boolean parsingNumber = false;
|
|
|
|
for (Character c : input.toCharArray()) {
|
|
if (c.toString().matches("[\\-0-9.]")) {
|
|
buffer += c;
|
|
parsingNumber = true;
|
|
} else {
|
|
|
|
if (parsingNumber) {
|
|
list.add(new Fraction(buffer));
|
|
buffer = "";
|
|
parsingNumber = false;
|
|
}
|
|
|
|
switch (c) {
|
|
case '(':
|
|
list.add(new TokenParenthesisLeft());
|
|
break;
|
|
case ')':
|
|
list.add(new TokenParenthesisRight());
|
|
break;
|
|
case '*':
|
|
list.add(new TokenOperatorMultiply());
|
|
break;
|
|
case '/':
|
|
list.add(new TokenOperatorDivide());
|
|
break;
|
|
case '+':
|
|
list.add(new TokenOperatorAdd());
|
|
break;
|
|
case '!':
|
|
list.add(new TokenOperatorFactorial());
|
|
break;
|
|
case '%':
|
|
list.add(new TokenOperatorModulo());
|
|
break;
|
|
case '^':
|
|
list.add(new TokenOperatorPower());
|
|
break;
|
|
default:
|
|
throw new ParseError("Unknown token " + c);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// include possible last number
|
|
if (parsingNumber) {
|
|
list.add(new Fraction(buffer));
|
|
}
|
|
|
|
if (debug) System.out.println("\nToken list:\n" + list);
|
|
|
|
|
|
return list;
|
|
}
|
|
}
|
|
|