词法分析(Lexer)与四则运算java实现[附源码]

本文介绍了一种自定义词法分析器的设计与实现,该分析器用于解析字符串并提取单词,进一步实现了四则运算功能。文章详细展示了词法分析器的核心函数与四则运算接口,同时提供了内置函数以增强表达式处理能力,如数学函数、字符串处理等。测试代码及运行结果证明了方案的有效性。

动机

项目研发过程中经常会需要将业务逻辑外置,需要将业务逻辑和代码分离。一般面对这样的需求有以下几种解决办法:

  • 引入一个规则引擎,比如Drools。
  • 利用java的javax.script.ScriptEngineManager调用javascript脚本。
  • 利用antlr这样的开源项目定义自己的业务领域语言。
    笔者在开发过程中经常需要对字符串进行分析,比如:从一个字符串中取出第一个符合标识符的单词;所以决定自己写一个词法分析器(Lexer),有了词法分析器后觉得写一个四则运算也非常简单,这样在先腾框架(我的主要工作)就用这个四则运算作为规则引擎。虽然不像drools那么强大,但使用也算比较方便,并且经过项目的验证也算够用。词法分析器参见相关源码

词法分析器Lexer

虽然我们可以使用正则表达式来分析字符串,但是如果我们想从一个字符串或者文本文件中一个单词一个单词的分析,词法分析器是最合适的。一个词法分析器需要对应一个词法规则,设计在词法分析器遵循java和sql的词法,两个差别主要在注释方便,构造函数中需要区分。

    public Lexer(String sFormula,int langType){
        this.languageType = langType;
        setFormula(sFormula);
    }

词法分析器的核心函数是 public String getAWord( ); 它返回的内容可能是:

  • 一个标识符,变量或者内置函数。
  • 一个字符串。
  • 一个操作符。

public String getARawWord(){
    int sl = formulaSen.length();
    while((startPos < sl ) && (formulaSen.charAt(startPos) == ' ' || formulaSen.charAt(startPos) == 9 || formulaSen.charAt(startPos) == 10|| formulaSen.charAt(startPos) == 13)) startPos++;
    if(startPos >= sl) return ""; 
    int bp = startPos;
    // 数字
    if( (formulaSen.charAt(startPos)>='0' && formulaSen.charAt(startPos)<='9') || 
        //m_Formula.charAt(m_iStart)== '.' ||
        ( ! canAcceptOpt && (formulaSen.charAt(startPos)== '-' || formulaSen.charAt(startPos)== '+' ) ) ){
        startPos++;
        int nPoints = 0;
        while ( startPos < sl  && (
                ( formulaSen.charAt(startPos)>='0' && formulaSen.charAt(startPos)<='9') ||
                 formulaSen.charAt(startPos)=='.'  ))
        { 
            if( formulaSen.charAt(startPos)=='.' ){
                nPoints ++;
                if (nPoints>1)
                    break;
            }
            startPos ++;
        }
        canAcceptOpt = true;            
    // 标识符    
    } else if (( formulaSen.charAt(startPos)>='a' && formulaSen.charAt(startPos)<='z') ||
        ( formulaSen.charAt(startPos)>='A' && formulaSen.charAt(startPos)<='Z') ||
        formulaSen.charAt(startPos)=='_' ||
        formulaSen.charAt(startPos)=='@'  ){
        startPos++;
        while ( startPos < sl  && (
                ( formulaSen.charAt(startPos)>='0' && formulaSen.charAt(startPos)<='9') ||
                ( formulaSen.charAt(startPos)>='a' && formulaSen.charAt(startPos)<='z') ||
                ( formulaSen.charAt(startPos)>='A' && formulaSen.charAt(startPos)<='Z') ||
                  formulaSen.charAt(startPos)=='_' || formulaSen.charAt(startPos)=='.' ||
                  formulaSen.charAt(startPos)=='@' ) ) 
            startPos ++;
        canAcceptOpt = true;
    }else {
        canAcceptOpt = false;
        switch(formulaSen.charAt(startPos)){
        case '+':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '=') || 
                                 (formulaSen.charAt(startPos) == '+')  ) ) startPos ++;
            break;
        case '-':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '=') || 
                                 (formulaSen.charAt(startPos) == '-')  ) ) startPos ++;
            break;
        case '*':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '*') || 
                                 (formulaSen.charAt(startPos) == '=') ||
                                 (formulaSen.charAt(startPos) == '/')   ) ) startPos ++;
            break;    
        case '/':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '=') || 
                                 (formulaSen.charAt(startPos) == '/') ||
                                 (formulaSen.charAt(startPos) == '*')   ) ) startPos ++;
            break;    

        case '<':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '=') || 
                                 (formulaSen.charAt(startPos) == '>') ||
                                 (formulaSen.charAt(startPos) == '<')   ) ) startPos ++;
            break;    
        case '>':
            ++startPos;
            if((startPos<sl) && ((formulaSen.charAt(startPos) == '=') || 
                                 (formulaSen.charAt(startPos) == '>')  ) ) startPos ++;
            break;
        case ':':
            ++startPos;
            if((startPos<sl) && (formulaSen.charAt(startPos) == '=')) startPos ++;
            break;

        case '=':
        case '!':
            ++startPos;
            if((startPos<sl) && (formulaSen.charAt(startPos) == '=')) startPos ++;
            break;
        case '|':
            ++startPos;
            if((startPos<sl) && (formulaSen.charAt(startPos) == '|')) startPos ++;
            break;
        case '&':
            ++startPos;
            if((startPos<sl) && (formulaSen.charAt(startPos) == '&')) startPos ++;
            break;
        case '\"': //字符串
        case '\'': //字符串
            canAcceptOpt = true;
            startPos ++;
            break;
        case '.':
            ++startPos;
            while ( startPos < sl  && 
                    ( formulaSen.charAt(startPos)>='0' && formulaSen.charAt(startPos)<='9') )
            { 
                startPos ++;
            }                
            break;
        case ')':
            canAcceptOpt = true;
            startPos ++;
            break;
        default: //"+-*/"
            startPos ++;
            break;
        }
    }

    String str = formulaSen.substring(bp, startPos );
    return str;    
}    

带变量四则运算 VariableFormula

四则运行包括加减乘除、取模和与或非等等;表达式中的标识符对应的变量可以通过map传递给四则运算,也可以通过一个对象的属性传递。四则运算接口如下:

    public static Object calculate(String szExpress,Object varMap) {
        ...................
    }

变量对应的类型可以式任意类型,数字、字符串、日期、数组都可以。因为他们不仅可以传递到四则运行中还可以传递到四则运算的内置函数中。

内置函数 EmbedFunc

为了对四则运行进行扩展,引入了内置函数来丰富表达式的功能。内置函数分一下几类:

  • 数学函数;比如:取整、开平方、对数等等。
  • 字符串处理函数;查找子串、数字大写、补齐等等。
  • 统计函数;求和、计数、统计非空数量等等。
  • 日期函数;当前时间、时间的加减。
  • 条件函数(逻辑函数);if函数和case函数,不同的条件取对应的表达式结果。

测试与效果

测试代码

public static void testFormula3() {
        Map<String,Object> varMap = new HashMap<>();
        varMap.put("a", 10);
        varMap.put("b", 4);
        String formula = "(a*a-b)/b";
        Object s = VariableFormula.calculate(formula, varMap);
        System.out.println(StringBaseOpt.castObjectToString(s));
        System.out.println("Done!");
    }

运行结果

24
Done!

词法分析器参见相关源码,更多先腾框架项目参见先腾框架

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值