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

