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

Parboiled解析SegmentSetOperations生成AST遇语法异常求助

修正Parboiled解析SegmentSetOperations生成AST的问题

问题说明

需要将SegmentSetOperations字符串转换为指定结构的抽象语法树(AST),规则为:单个或联合段需用括号包裹、支持任意并集/交集组合、忽略空格。已实现SegmentSetOperationsLexer生成Token列表,但编写Parboiled Parser时抛出异常:

org.parboiled.errors.GrammarException: 'class com.paytm.adtech.map.ruleengine.http.form.setoperations.SegmentSetOperationsToken$UnionToken' cannot be automatically converted to a parser Rule

相关类定义

SegmentSetOperationsToken.java

public interface SegmentSetOperationsToken {
  record IdentifierToken(Long value) implements SegmentSetOperationsToken {}
  record UnionToken() implements SegmentSetOperationsToken {
    public static final UnionToken INSTANCE = new UnionToken();
  }
  record IntersectToken() implements SegmentSetOperationsToken {
    public static final IntersectToken INSTANCE = new IntersectToken();
  }
  record LeftParenToken() implements SegmentSetOperationsToken {
    public static final LeftParenToken INSTANCE = new LeftParenToken();
  }
  record RightParenToken() implements SegmentSetOperationsToken {
    public static final RightParenToken INSTANCE = new RightParenToken();
  }
}

SegmentSetOperationsAST.java

public interface SegmentSetOperationsAST {
  record UnionOfTwoSegments(List<Long> values) implements SegmentSetOperationsAST {}
  record IntersectOfTwoSegments(List<Long> values) implements SegmentSetOperationsAST {}
  record IntersectionsOfUnions(List<SegmentSetOperationsAST> left, SegmentSetOperationsAST right) implements SegmentSetOperationsAST {}
  record UnionsOfIntersections(List<SegmentSetOperationsAST> left, SegmentSetOperationsAST right) implements SegmentSetOperationsAST {}
}

SegmentSetOperationsLexer.java

public class SegmentSetOperationsLexer {
  private static final Pattern WHITESPACE_PATTERN = Pattern.compile("[ \t\r\f\n]+");
  private static final Pattern IDENTIFIER_PATTERN = Pattern.compile("\\d+");
  private static final String UNION = "u";
  private static final String INTERSECT = "n";
  private static final String LEFT_PAREN = "(";
  private static final String RIGHT_PAREN = ")";
  public static Either<SegmentSetOperationsLexerError, List<SegmentSetOperationsToken>> tokenize(String expression) {
    try {
      expression = WHITESPACE_PATTERN.matcher(expression).replaceAll("");
      return Either.right(generateTokens(expression).stream().map(SegmentSetOperationsLexer::mapToToken).toList());
    } catch (IllegalArgumentException e) {
      return Either.left(new SegmentSetOperationsLexerError(e.getMessage()));
    }
  }
  private static List<String> generateTokens(String expression) {
    List<String> tokens = new ArrayList<>();
    Matcher matcher = Pattern.compile("(\\d+|[un()])").matcher(expression);
    while (matcher.find()) {
      tokens.add(matcher.group());
    }
    return tokens;
  }
  private static SegmentSetOperationsToken mapToToken(String token) {
    if (token.matches(IDENTIFIER_PATTERN.pattern())) {
      return new IdentifierToken(Long.parseLong(token));
    } else {
      return switch (token) {
        case UNION -> UnionToken.INSTANCE;
        case INTERSECT -> IntersectToken.INSTANCE;
        case LEFT_PAREN -> LeftParenToken.INSTANCE;
        case RIGHT_PAREN -> RightParenToken.INSTANCE;
        default -> throw new IllegalArgumentException("Unknown token: " + token);
      };
    }
  }
}

SegmentSetOperationsConverter.java

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SegmentSetOperationsConverter {
  public static Either<SegmentSetOperationsConverterError, SegmentSetOperationsAST> convertToAst(String expression) {
    Either<SegmentSetOperationsLexerError, List<SegmentSetOperationsToken>> tokens = SegmentSetOperationsLexer.tokenize(expression);
    if (tokens.isLeft()) {
      return Either.left(tokens.getLeft());
    } else {
      SegmentSetOperationsParser parser = Parboiled.createParser(SegmentSetOperationsParser.class);
      return parser.parseToAst(tokens.get());
    }
  }
}

修正后的SegmentSetOperationsParser实现

@BuildParseTree
public class SegmentSetOperationsParser extends BaseParser<SegmentSetOperationsAST> {

    Rule astParser() {
        return FirstOf(
            intersectionsOfUnions(),
            unionsOfIntersections(),
            unionOfTwoSegments(),
            intersectionOfTwoSegments()
        );
    }

    Rule unionOfTwoSegments() {
        Var<List<Long>> ids = new Var<>(new ArrayList<>());
        return Sequence(
            Match(LeftParenToken.INSTANCE),
            unionSequence(ids),
            Match(RightParenToken.INSTANCE),
            push(new UnionOfTwoSegments(new ArrayList<>(ids.get())))
        );
    }

    Rule intersectionOfTwoSegments() {
        Var<List<Long>> ids = new Var<>(new ArrayList<>());
        return Sequence(
            Match(LeftParenToken.INSTANCE),
            intersectionSequence(ids),
            Match(RightParenToken.INSTANCE),
            push(new IntersectOfTwoSegments(new ArrayList<>(ids.get())))
        );
    }

    Rule intersectionsOfUnions() {
        Var<List<SegmentSetOperationsAST>> leftNodes = new Var<>(new ArrayList<>());
        return Sequence(
            unionOfTwoSegments(),
            action(() -> leftNodes.get().add(pop())),
            ZeroOrMore(
                Match(IntersectToken.INSTANCE),
                unionOfTwoSegments(),
                action(() -> leftNodes.get().add(pop()))
            ),
            push(new IntersectionsOfUnions(new ArrayList<>(leftNodes.get().subList(0, leftNodes.get().size() - 1)), leftNodes.get().getLast()))
        );
    }

    Rule unionsOfIntersections() {
        Var<List<SegmentSetOperationsAST>> leftNodes = new Var<>(new ArrayList<>());
        return Sequence(
            intersectionOfTwoSegments(),
            action(() -> leftNodes.get().add(pop())),
            ZeroOrMore(
                Match(UnionToken.INSTANCE),
                intersectionOfTwoSegments(),
                action(() -> leftNodes.get().add(pop()))
            ),
            push(new UnionsOfIntersections(new ArrayList<>(leftNodes.get().subList(0, leftNodes.get().size() - 1)), leftNodes.get().getLast()))
        );
    }

    Rule unionSequence(Var<List<Long>> ids) {
        return Sequence(
            IdentifierToken.class,
            action(() -> ids.get().add(((IdentifierToken) pop()).value())),
            ZeroOrMore(
                Match(UnionToken.INSTANCE),
                IdentifierToken.class,
                action(() -> ids.get().add(((IdentifierToken) pop()).value()))
            )
        );
    }

    Rule intersectionSequence(Var<List<Long>> ids) {
        return Sequence(
            IdentifierToken.class,
            action(() -> ids.get().add(((IdentifierToken) pop()).value())),
            ZeroOrMore(
                Match(IntersectToken.INSTANCE),
                IdentifierToken.class,
                action(() -> ids.get().add(((IdentifierToken) pop()).value()))
            )
        );
    }

    public Either<SegmentSetOperationsConverterError, SegmentSetOperationsAST> parseToAst(List<SegmentSetOperationsToken> tokens) {
        ParsingResult<?> result = new ReportingParseRunner<>(astParser()).run(tokens);
        if (!result.hasErrors()) {
            return Either.right((SegmentSetOperationsAST) result.resultValue);
        } else {
            return Either.left(new SegmentSetOperationsParserError(result.parseErrors.getFirst().getErrorMessage()));
        }
    }
}

关键修正点

  1. Token匹配方式修正:Parboiled无法直接将Token类转换为Rule,需使用Match()方法匹配Token单例(如Match(UnionToken.INSTANCE)),替代原代码中直接使用UnionToken.class的错误写法。
  2. 标识符收集逻辑:在unionSequence和intersectionSequence中添加action(),将解析到的IdentifierToken的值存入Var变量,确保UnionOfTwoSegments和IntersectOfTwoSegments能正确获取ID列表。
  3. AST节点构建逻辑修复:
    • 在intersectionsOfUnions和unionsOfIntersections中,通过action()收集所有左侧节点,最后按照AST结构要求拆分左侧列表和右侧节点。
    • 调整astParser()中规则的优先级,优先解析组合操作(交集的并集、并集的交集),避免被单一操作规则提前匹配。
  4. ParseRunner输入修复:直接传入Token列表tokens,而非调用tokens.toString(),确保Parboiled能正确处理Token流。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:55:53