表达式:四则运算/三元运算/最大最小运算

This commit is contained in:
ZZ 2022-01-10 14:43:57 +08:00
parent a402fea185
commit b334803cd1

View File

@ -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]);
}
}