All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.lab515.utils.Expression Maven / Gradle / Ivy

There is a newer version: 1.0.12
Show newest version
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();
    }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy