表达式:四则运算/三元运算/最大最小运算
This commit is contained in:
parent
a402fea185
commit
b334803cd1
@ -0,0 +1,371 @@
|
||||
package com.xhpc.order.service.impl;
|
||||
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StreamTokenizer;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Calc {
|
||||
|
||||
public static void main(String[] argv) {
|
||||
|
||||
test("-1 + 2", 1);
|
||||
test("2 * (3 * (4 + 5)) / 9 / 6", 1);
|
||||
test("max(1, 2 + 3)", 5);
|
||||
test("min(3 - 1, 1)", 1);
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("totalPower", new BigDecimal("400000.000000"));
|
||||
StandardEvaluationContext stdContext = new StandardEvaluationContext();
|
||||
stdContext.setVariables(map);
|
||||
String ruleExpression = "#totalPower >= 500000 ? 'e1' : 'e2'";
|
||||
// Evaluate the SpEL expression
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression expression = parser.parseExpression(ruleExpression);
|
||||
String rs = expression.getValue(stdContext, String.class);
|
||||
System.out.println(rs);
|
||||
}
|
||||
|
||||
private static void test(String s, double x) {
|
||||
|
||||
double v = eval(s);
|
||||
System.out.println((v == x) + ": " + s + " = " + v);
|
||||
}
|
||||
|
||||
private static double eval(String s) {
|
||||
|
||||
Parser p = Parser.parse(s);
|
||||
double v = p.value();
|
||||
|
||||
if (p.isValid()) {
|
||||
return v;
|
||||
} else {
|
||||
throw new ArithmeticException("error check: " + p.error() + ": " + s);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse via recursive descent using the following LL(1) grammar:
|
||||
* <code>
|
||||
* expr = [addop] term {(addop) term} end
|
||||
* term = factor {(mulop) factor} end
|
||||
* factor = word | number | "(" expr ")" end
|
||||
* word = name ["(" expr] ["," expr] ")"] end
|
||||
* addop = "+" | "-"
|
||||
* mulop = "*" | "/"
|
||||
* </code>
|
||||
*
|
||||
* @param s the string to be evaluated.
|
||||
* @see <a href="http://en.wikipedia.org/wiki/Recursive_descent_parser">
|
||||
* Recursive descent parser</a>.
|
||||
*/
|
||||
class Parser extends Object {
|
||||
|
||||
private final StreamTokenizer tokens;
|
||||
private int token;
|
||||
private final double value;
|
||||
private String error;
|
||||
|
||||
private Parser(String s) {
|
||||
|
||||
Reader reader = new StringReader(s);
|
||||
tokens = new StreamTokenizer(reader);
|
||||
tokens.ordinaryChar(Symbol.SLASH.toChar());
|
||||
|
||||
getToken();
|
||||
value = expr();
|
||||
if (!tokenIs(Symbol.END))
|
||||
putError("syntax error");
|
||||
}
|
||||
|
||||
public static Parser parse(String s) {
|
||||
|
||||
return new Parser(s);
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
|
||||
return error == null;
|
||||
}
|
||||
|
||||
public double value() {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public String error() {
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
// expr = [addop] term {(addop) term} end
|
||||
private double expr() {
|
||||
|
||||
BigDecimal sign = BigDecimal.ONE;
|
||||
accept(Symbol.PLUS);
|
||||
if (accept(Symbol.MINUS)) {
|
||||
sign = BigDecimal.valueOf(-1);
|
||||
}
|
||||
BigDecimal value = sign.multiply(term());
|
||||
while (Symbol.isAddOp(token)) {
|
||||
if (accept(Symbol.PLUS)) {
|
||||
value = value.add(term());
|
||||
}
|
||||
if (accept(Symbol.MINUS)) {
|
||||
value = value.subtract(term());
|
||||
}
|
||||
}
|
||||
return value.doubleValue();
|
||||
}
|
||||
|
||||
// term = factor {(mulop) factor} end
|
||||
private BigDecimal term() {
|
||||
|
||||
BigDecimal value = factor();
|
||||
while (Symbol.isMulOp(token)) {
|
||||
if (accept(Symbol.STAR)) {
|
||||
value = value.multiply(factor());
|
||||
}
|
||||
if (accept(Symbol.SLASH)) {
|
||||
value = value.divide(factor());
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// factor = word | number | "(" expr ")" end
|
||||
private BigDecimal factor() {
|
||||
|
||||
BigDecimal value = BigDecimal.ZERO;
|
||||
if (tokenIs(Symbol.WORD)) {
|
||||
value = word();
|
||||
} else if (tokenIs(Symbol.NUMBER)) {
|
||||
value = new BigDecimal(tokens.nval);
|
||||
getToken();
|
||||
} else if (accept(Symbol.OPEN)) {
|
||||
value = BigDecimal.valueOf(expr());
|
||||
expect(Symbol.CLOSE);
|
||||
} else {
|
||||
putError("factor error");
|
||||
getToken();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// word = name ["(" expr] ["," expr] ")"] end
|
||||
private BigDecimal word() {
|
||||
|
||||
BigDecimal value = BigDecimal.ZERO;
|
||||
String name = tokens.sval;
|
||||
FunctionAdapter fa = Function.lookup(name);
|
||||
getToken();
|
||||
if (fa != null) {
|
||||
int count = fa.getCount();
|
||||
if (count == 0) {
|
||||
value = BigDecimal.valueOf(fa.eval());
|
||||
} else if (accept(Symbol.OPEN)) {
|
||||
double[] args = new double[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
args[i] = expr();
|
||||
if (i < count - 1)
|
||||
expect(Symbol.COMMA);
|
||||
}
|
||||
value = BigDecimal.valueOf(fa.eval(args));
|
||||
expect(Symbol.CLOSE);
|
||||
} else putError("missing " + Symbol.OPEN.toChar());
|
||||
} else putError("undefined " + name);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the next token in the stream.
|
||||
*/
|
||||
private void getToken() {
|
||||
|
||||
try {
|
||||
token = tokens.nextToken();
|
||||
} catch (IOException e) {
|
||||
putError("i/o error " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the current token matches the given symbol.
|
||||
*/
|
||||
private boolean tokenIs(Symbol symbol) {
|
||||
|
||||
return token == symbol.token();
|
||||
}
|
||||
|
||||
/**
|
||||
* Require a matching symbol; gerate an error if it's unexpected.
|
||||
*/
|
||||
private void expect(Symbol symbol) {
|
||||
|
||||
if (accept(symbol)) return;
|
||||
putError("missing " + symbol.toChar());
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance if the current token matches the given symbol.
|
||||
*/
|
||||
private boolean accept(Symbol symbol) {
|
||||
|
||||
if (tokenIs(symbol)) {
|
||||
getToken();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an error; ignore line numbers.
|
||||
*/
|
||||
private void putError(String s) {
|
||||
|
||||
if (error == null)
|
||||
error = s + " at " + tokens.toString().replaceAll(",.*$", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the terminal symbols recognized by the Parser.
|
||||
* Each symbol's token field is initialized with a value that
|
||||
* matches one returned by StreamTokenizer's nextToken() method.
|
||||
*/
|
||||
enum Symbol {
|
||||
PLUS('+'), MINUS('-'), STAR('*'), SLASH('/'),
|
||||
OPEN('('), CLOSE(')'), COMMA(','),
|
||||
END(StreamTokenizer.TT_EOF),
|
||||
WORD(StreamTokenizer.TT_WORD),
|
||||
NUMBER(StreamTokenizer.TT_NUMBER);
|
||||
|
||||
private final int token;
|
||||
|
||||
Symbol(int token) {
|
||||
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return this symbol's token.
|
||||
*/
|
||||
public int token() {
|
||||
|
||||
return this.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* If printable, return character for this symbol's token.
|
||||
*/
|
||||
public char toChar() {
|
||||
|
||||
if (this.token < 32) return '\ufffd';
|
||||
else return (char) this.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the given token matches PLUS or MINUS.
|
||||
*/
|
||||
public static boolean isAddOp(int token) {
|
||||
|
||||
return token == PLUS.token || token == MINUS.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if the given token matches STAR or SLASH.
|
||||
*/
|
||||
public static boolean isMulOp(int token) {
|
||||
|
||||
return token == STAR.token || token == SLASH.token;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the available functions. Although an enum may define
|
||||
* <i>constant-specific</i> methods, an included adpter or interface
|
||||
* is perhaps easier to read, initialize and maintain.
|
||||
*/
|
||||
enum Function {
|
||||
MAX(new Max()),
|
||||
MIN(new Min());
|
||||
|
||||
private final FunctionAdapter fa;
|
||||
|
||||
/**
|
||||
* Construct a Function with the specified adapter.
|
||||
**/
|
||||
Function(FunctionAdapter fa) {
|
||||
|
||||
this.fa = fa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Function's adapter by name; null if unknown.
|
||||
*/
|
||||
public static FunctionAdapter lookup(String name) {
|
||||
|
||||
Function f;
|
||||
try {
|
||||
f = Enum.valueOf(Function.class, name.toUpperCase());
|
||||
} catch (RuntimeException e) {
|
||||
return null;
|
||||
}
|
||||
return f.fa;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt to functions with a variable number of arguments.
|
||||
* Concrete implementations should override getCount() to indicate
|
||||
* the expected number of arguments. The default is one.
|
||||
*/
|
||||
abstract class FunctionAdapter {
|
||||
|
||||
public int getCount() {
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
abstract double eval(double... args);
|
||||
|
||||
}
|
||||
|
||||
class Max extends FunctionAdapter {
|
||||
|
||||
public int getCount() {
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
public double eval(double... args) {
|
||||
|
||||
return Math.max(args[0], args[1]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Min extends FunctionAdapter {
|
||||
|
||||
public int getCount() {
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
public double eval(double... args) {
|
||||
|
||||
return Math.min(args[0], args[1]);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user