You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java自定义布尔逻辑表达式解析器开发求助

布尔逻辑表达式解析器实现问题求助

问题背景

我正在开发一个布尔逻辑表达式解析器,用来判断表达式中的词汇是否匹配文档内容。之前完全不懂解析器相关理论,查了很多资料还是搞不定——既没法写出无左递归的有效语法,也实现不了能正确运行的解析器,课堂又没讲过相关内容,实在头疼。

表达式规则与示例

需要解析的表达式示例:({w1 w2 w3} & !w4) | (w5 & "mark likes food")

  • {}:内部词汇必须全部出现在文档中
  • "":必须完全匹配的字符串字面量

已定义的Token

[AND, OR, NOT, LPAREN, RPAREN, LSET, RSET, LSEQ, RSEQ, WRD]
比如表达式(w1 & {w2 w2})会被分词为[LPAREN WRD AND LSET WRD WRD RSET RPAREN]

尝试过的无效语法

S -> E
E -> T AND T | T OR T | T
T -> LSET W RSET | LSEQ W RSEQ | LPAREN E RPAREN | NOT E | WRD
W -> WRD*  // 表示接受直到RSET或RSEQ前的所有WRD Token数组(不确定正式写法)

这个语法存在诸多问题:表达式无法完全求值(首次返回就停止)、括号处理错误等。

当前代码(分词器已验证正常)

public class BoolExprParser {

    private final String expression;
    private final BoolExprTokenizer tokenizer;
    private BoolExprTokenizer.Token currentToken;

    private void advance() {
        currentToken = tokenizer.getNext();
    }

    private boolean currentEquals(BoolExprTokenizer.Token t) {
        return currentToken == t;
    }

    private boolean parse(Document doc) {
        advance();
        boolean val = expr(doc);
        if (!currentEquals(BoolExprTokenizer.Token.END)) {
            // error
        }

        return val;
    }

    private boolean expr(Document doc) {
        boolean leftExpr = subExpr(doc);
        switch (currentToken) {
            case AND:
                advance();
                boolean rightExpr = subExpr(doc);
                return leftExpr && rightExpr;
            case OR:
                advance();
                rightExpr = subExpr(doc);
                return leftExpr || rightExpr;
            case END:
                return leftExpr;
            default:
                //error
        }

        return false;
    }

    private boolean subExpr(Document doc) {
        switch (currentToken) {
            case NOT:
                advance();
                boolean result = expr(doc);
                return !result;
            case WRD:
                advance();
                return doc.isWord(tokenizer.getWord());
            case LSet:
                advance();
                boolean wordsInsideSet = wordsSet(doc);
                if (!currentEquals(BoolExprTokenizer.Token.RSet)) {
                    // error
                } else {
                    advance();
                }
                return wordsInsideSet;
            case LP:
                advance();
                boolean exprInside = expr(doc);
                if (!currentEquals(BoolExprTokenizer.Token.RP)) {
                    // error
                } else {
                    advance();
                }
                return exprInside;
            default:
                // error
        }

        return false;
    }

    private boolean wordsSet(Document doc) {
        boolean validToken = currentEquals(BoolExprTokenizer.Token.WRD);
        boolean isInDoc = true;

        while (validToken) {
            if (isInDoc) isInDoc = doc.isWord(tokenizer.getWord());
            advance();
            validToken = currentEquals(BoolExprTokenizer.Token.WRD);
        }

        if (!currentEquals(BoolExprTokenizer.Token.RSet)) {
            // error
        }

        return isInDoc;
    }
}

问题分析与修复方案

1. 语法设计:无左递归且支持运算符优先级

你原来的语法无法处理连续的AND/OR运算(比如A & B | C),正确的无左递归语法需要按运算符优先级(NOT > AND > OR)拆分:

S → E
E → E OR T | T          // 处理OR连续运算,优先级最低
T → T AND F | F          // 处理AND连续运算,优先级高于OR
F → NOT F | LPAREN E RPAREN | LSET WRD* RSET | LSEQ WRD* RSEQ | WRD

这种右递归结构既能避免左递归,又能支持任意长度的连续布尔运算。

2. 代码核心问题修复

你的代码存在三个关键问题:expr()只处理单次AND/OR、缺少字符串字面量(LSEQ/RSEQ)处理、错误处理不明确。以下是修复后的代码:

public class BoolExprParser {

    private final BoolExprTokenizer tokenizer;
    private BoolExprTokenizer.Token currentToken;

    public BoolExprParser(BoolExprTokenizer tokenizer) {
        this.tokenizer = tokenizer;
        this.currentToken = tokenizer.getNext(); // 初始化当前Token
    }

    private void advance() {
        currentToken = tokenizer.getNext();
    }

    private boolean match(BoolExprTokenizer.Token expected) {
        if (currentToken == expected) {
            advance();
            return true;
        }
        return false;
    }

    public boolean parse(Document doc) {
        boolean result = expr(doc);
        if (currentToken != BoolExprTokenizer.Token.END) {
            throw new IllegalArgumentException("表达式末尾存在无效Token: " + currentToken);
        }
        return result;
    }

    // 处理OR运算(优先级最低)
    private boolean expr(Document doc) {
        boolean result = term(doc);
        while (currentToken == BoolExprTokenizer.Token.OR) {
            advance();
            result = result || term(doc);
        }
        return result;
    }

    // 处理AND运算(优先级高于OR)
    private boolean term(Document doc) {
        boolean result = factor(doc);
        while (currentToken == BoolExprTokenizer.Token.AND) {
            advance();
            result = result && factor(doc);
        }
        return result;
    }

    // 处理原子表达式:NOT、括号、集合、字符串、单个词
    private boolean factor(Document doc) {
        switch (currentToken) {
            case NOT:
                advance();
                return !factor(doc); // NOT作用于原子表达式,符合优先级
            case LPAREN:
                advance();
                boolean result = expr(doc);
                if (!match(BoolExprTokenizer.Token.RPAREN)) {
                    throw new IllegalArgumentException("缺少闭合括号");
                }
                return result;
            case LSET:
                advance();
                boolean allInSet = true;
                while (currentToken == BoolExprTokenizer.Token.WRD) {
                    allInSet = allInSet && doc.isWord(tokenizer.getWord());
                    advance();
                }
                if (!match(BoolExprTokenizer.Token.RSET)) {
                    throw new IllegalArgumentException("缺少集合闭合符}");
                }
                return allInSet;
            case LSEQ:
                advance();
                StringBuilder literal = new StringBuilder();
                while (currentToken == BoolExprTokenizer.Token.WRD) {
                    if (literal.length() > 0) {
                        literal.append(" ");
                    }
                    literal.append(tokenizer.getWord());
                    advance();
                }
                if (!match(BoolExprTokenizer.Token.RSEQ)) {
                    throw new IllegalArgumentException("缺少字符串闭合引号");
                }
                return doc.containsLiteral(literal.toString()); // 假设Document有此方法
            case WRD:
                String word = tokenizer.getWord();
                advance();
                return doc.isWord(word);
            default:
                throw new IllegalArgumentException("遇到无效Token: " + currentToken);
        }
    }
}

3. 关键改进说明

  • 拆分expr()/term()/factor()对应语法层级,确保运算符优先级正确
  • 用循环处理连续AND/OR,解决表达式无法完全求值的问题
  • 新增字符串字面量处理逻辑,拼接后匹配文档
  • 替换注释为明确的异常抛出,便于调试
  • 调整NOT的作用对象为原子表达式,符合布尔运算优先级规则

内容的提问来源于stack exchange,提问作者Jafeth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 15:45:50