io.github.lab515.utils.Expression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qray Show documentation
Show all versions of qray Show documentation
remoting ondemand in java
package io.github.lab515.utils;
import java.util.*;
public class Expression {
public static interface APIs{
String handleCall(String name, String[] paras,Object uo);
String resolve(String name, Object uo);
}
static class Token{
String val;
int pri;
Token _next;
Token _last;
public Token operate(Token Op, Token Oper2) {
if (Op._next != null)
return this;
Op._next = null;
Op._last = this;
(_next != null ? _last : this)._next = Oper2;
_last = Oper2._next != null ? Oper2._last : Oper2;
_last._next = Op;
_last = Op;
return this;
}
public Token(String val, int pri){
this.val = val;
this.pri = pri;
}
public void set(String val, int pri){
this.val = val;
this.pri = pri;
}
public boolean is(String val, int pri){
if(val != null && !val.equals(this.val))return false;
return pri == this.pri;
}
public boolean isStopper(){
return pri == NA && val.length() == 1 && ",:)".indexOf(val.charAt(0)) >= 0;
}
}
// BOP is not used
static final int NA = -1, OPER = 0, PARA = 1, TRI = 2, OR = 3, AND = 4, BOP = 5, EQ = 6, CMP = 7, PM = 8, MD = 9, NOT = 10, CALL = 11;
static final int TOKEN = 0, COMMENT = 1, ESCAPE = 2;
static final String _ops = "#&|!()?:,=><+-/%*";
static final int[] _opPris = {NA, AND, OR, NOT, NA, NA, TRI, TRI, NA, EQ, CMP, CMP, PM, PM, MD, MD, MD};
static final Token _NULL = new Token("",NA);
private static Token parse(String expr) {
// no complex syntax, only support & | , !, ? : ()
LinkedList ret = new LinkedList<>();
StringBuilder tk = new StringBuilder();
int mode = TOKEN;
int v = -1;
char last = ' ';
for(char c : expr.toLowerCase().toCharArray()){
if(mode == COMMENT){
if(c == '\n') {
mode = TOKEN;
}
}else if(mode == ESCAPE){
if (c == 'n') {
tk.append('\n');
}else if(c == 't'){
tk.append('\t');
}else if(Character.isLetter(c)){
throw new RuntimeException("invalid escape char: \\" + c);
}else{
tk.append(c); // doesn't care what it is
}
}else{
if(c == '\\'){
mode = ESCAPE;
}else if(Character.isWhitespace(c)) {
if(tk.length() > 0) {
ret.add(new Token(tk.toString(),OPER));
tk.setLength(0);
}
}else if((v = _ops.indexOf(c)) >= 0){
if(tk.length() > 0) {
ret.add(new Token(tk.toString(),OPER));
tk.setLength(0);
}
if (c == '#') {
mode = COMMENT;
}else{
// combination handling
if((last == '!' || last == '=' || last == '>' || last == '<') && c == '='){
ret.get(ret.size()-1).set(last + "=",last == '>' || last == '<' ? CMP : EQ);
c = ' ';
}else {
ret.add(new Token(c + "", _opPris[v]));
}
}
}else{
tk.append(c);
}
}
last = c;
}
if(tk.length()>0){
ret.add(new Token(tk.toString(),OPER));
}
Token exprTk = parseExpr(ret, _NULL);
if(!ret.isEmpty())throw new RuntimeException("unexpected more tokens");
return exprTk;
}
private static boolean isVariableName(String t){
if(!(Character.isLetter(t.charAt(0)) || t.charAt(0) == '_'))return false;
for(char c : t.toCharArray()){
if(!(Character.isLetter(c) || Character.isDigit(c) || c == '_' || c == '.'))return false;
}
return true;
}
// function and not now is diferent
// !a => a ! ""
// a(b) => b call a
private static Token parseExpr(LinkedList tks, Token fromOp){
// only support & | ! ? :
Token t = tks.peek();
if(t == null)throw new RuntimeException("expect more tokens");
tks.pop();
Token next = null;
int paraC = 0;
if(t.pri != 0){
if(t.is("(", NA)){
next = parseExpr(tks, _NULL);
if((t = tks.peek()) == null || !t.is(")",NA)) {
throw new RuntimeException("not enclosed bracket");
}
tks.pop();
t = next;
}else if(t.is("!",NOT)){
tks.push(t);
t = new Token("",OPER); // nop is not necessary
}else if(t.is("+", PM) || t.is("-", PM)) {
t.pri = NOT;
tks.push(t);
t = new Token("",OPER);
}else{
throw new RuntimeException("invalid token, expect operand but " + t.val);
}
}
while(!tks.isEmpty()){
// since no func call allowed
next = tks.peek();
if(next.isStopper()){ // we don't do match the enclose
return t;
}else if(next.pri <= 0) {
if (next.is("(",NA) && t._last == null && isVariableName(t.val)) {
tks.pop();
next = null;
paraC = 0;
while (true) {
if (tks.isEmpty())
throw new RuntimeException("expect token as parameter");
if (tks.peek().is(")",NA) && next == null)
break;
paraC++;
if (next != null) {
next.operate(new Token(",", PARA), parseExpr(tks, _NULL));
} else {
next = parseExpr(tks, _NULL);
}
if (tks.isEmpty() || !tks.peek().is(",",NA))
break;
tks.pop();
}
if (tks.isEmpty() || !tks.peek().is(")",NA))
throw new RuntimeException("exepct ) at end of func call");
tks.pop();
if (next == null) {
next = new Token("", OPER);
}
t.pri = NA; // not a oper
t = next.operate(new Token(paraC+"",CALL), t);
} else {
throw new RuntimeException("expect op but " + next.val);
}
}else {
if (fromOp != null && fromOp.pri >= next.pri) {
break;
}
tks.pop();
if (next.is("?",NA)) { // no optimization jumps
t.operate(next, parseExpr(tks, _NULL));
if (tks.isEmpty() || !((next = tks.pop()).is(":", NA))) throw new RuntimeException("invalid ?: expr!");
}
t.operate(next, parseExpr(tks, next));
}
}
return t;
}
private Token expression = null;
private APIs apis = null;
private String exprStr = null;
private Object uo;
public Expression(String expr, APIs funcs, Object uo){
exprStr = expr;
if(exprStr != null){
expression = parse(expr);
}
this.apis = funcs;
this.uo = uo;
}
public String run(Map vars){
return evaluateExpr(expression,vars);
}
public boolean runBool(Map vars){
return evaluateBoolean(evaluateExpr(expression, vars));
}
public boolean isValid(){
return expression != null;
}
private boolean evaluateBoolean(String v){
if(v == null || v.length() < 1)return false;
if(v.equals("false") || v.equals("0") || v.equals("null"))return false;
return true;
}
private String evaluateExpr(Token expr, Map vars){
if(expr == null)throw new RuntimeException("no expression to run!");
if(vars == null)vars = new HashMap<>();
vars.put("true", "1");
vars.put("false", "");
Stack runStack = new Stack<>();
String oper1, oper2;
String[] paras = null;
long val = 0;
while(expr != null){
if(expr.pri <= OPER){
if(expr.pri < OPER)oper1 = expr.val; // only function call
else {
oper1 = vars.get(expr.val);
if (oper1 == null && apis != null){
oper1 = apis.resolve(expr.val, uo);
}
if(oper1 == null)oper1 = expr.val;
}
runStack.push(oper1);
}else{
oper2 = runStack.pop();
oper1 = runStack.pop();
switch (expr.pri){
case TRI: //? :
if(expr.val.equals("?")) {
if (evaluateBoolean(oper1)) oper2 = "?" + oper2; // reset
else oper2 = "";
}else{ // :
if(oper1.startsWith("?"))oper2 = oper1.substring(1);
}
break;
case OR:
if(evaluateBoolean(oper1))oper2 = oper1;
break;
case AND:
if(!evaluateBoolean(oper1))oper2 = "";
break;
case NOT:
oper2 = evaluateBoolean(oper2) ? "" : "1";
break;
case PM:
if(expr.val.equals("-")) {
oper2 = Long.toString(Long.parseLong(oper1) - Long.parseLong(oper2));
}else{
oper2 = Long.toString(Long.parseLong(oper1) + Long.parseLong(oper2));
}
break;
case MD:
break;
case EQ:
break;
case CMP:
val = Long.parseLong(oper1) - Long.parseLong(oper2);
if(expr.val.equals(">")){
oper2 = val > 0 ? "1" : "";
}else if(expr.val.equals(">=")){
oper2 = val >= 0 ? "1" : "";
}else if(expr.val.equals("<")){
oper2 = val < 0 ? "1" :"";
}else{
oper2 = val <= 0 ? "1" : "";
}
break;
case PARA:
runStack.push(oper1);
break;
case CALL:
paras = new String[Integer.parseInt(expr.val)];
if(paras.length > 0) {
paras[paras.length-1] = oper1;
for (int i = 1; i < paras.length; i++) {
paras[paras.length - 1 - i] = runStack.pop();
}
}
if(apis == null || (oper1 = apis.handleCall(oper2,paras, uo)) == null)throw new RuntimeException("function is not defined:" + oper2);
oper2 = oper1;
break;
case BOP:
default:
throw new RuntimeException("invalid op:" + expr.val);
}
runStack.push(oper2);
}
expr = expr._next;
}
if(runStack.size() != 1)throw new RuntimeException("invalid runstack size:" + runStack.size());
return runStack.pop();
}
}