如何向Lexer中的userdefinedValue规则动态注入运行时确定的字符串?
动态向ANTLR Lexer注入UserdefinedValue规则的字符串
我来给你几个实用的方案,都是在ANTLR里实现运行时动态注入自定义值的常用手段,你可以根据自己的场景选最合适的:
方案一:语义谓词+动态词汇表(最常用)
这个方法的核心是在Lexer里维护一个动态集合,然后用ANTLR的语义谓词判断当前匹配的字符串是否在这个集合里。
步骤1:修改Lexer的G4文件
首先调整你的TransitionLexer.g4,把UserdefinedValue规则改成带语义谓词的形式,注意要把它放在NAME规则前面(因为ANTLR词法规则是按顺序匹配最长串,优先级靠前的会先匹配):
lexer grammar TransitionLexer; // 先定义字符集 fragment ALPHA: [a-zA-Z]; fragment CHINESE: [\u4E00-\u9FA5]; fragment DIGIT: [0-9]; // 动态规则放在NAME前面 UserdefinedValue: (ALPHA | CHINESE | '_')(ALPHA | CHINESE | '_' | DIGIT)* {getUserDefinedValues().contains(getText())}?; // 语义谓词判断是否在动态集合里 BOOLEAN: 'true' | 'false'; IF: 'if'; THEN: 'then'; ELSE: 'else'; NAME: (ALPHA | CHINESE | '_')(ALPHA | CHINESE | '_' | DIGIT)*;
步骤2:修改生成的Lexer类(或自定义基类)
ANTLR生成的Lexer类是Java文件(假设你用Java),你可以直接修改它,或者在生成时指定基类。给Lexer加一个存储动态值的集合,并提供setter方法:
public class TransitionLexer extends Lexer { private Set<String> userDefinedValues = new HashSet<>(); // 保留原有的构造方法 public TransitionLexer(CharStream input) { super(input); } // 提供设置动态值的方法 public void setUserDefinedValues(Set<String> values) { this.userDefinedValues = values; } // 提供给语义谓词调用的方法 public Set<String> getUserDefinedValues() { return userDefinedValues; } // 其他自动生成的代码... }
步骤3:运行时注入动态值
在你的业务代码里,初始化Lexer时传入动态值即可:
// 模拟运行时获取的动态值 Set<String> dynamicValues = new HashSet<>(Arrays.asList("myVar", "自定义变量", "user_123")); // 初始化输入流(比如从字符串或文件读取) CharStream input = CharStreams.fromString("if myVar then 自定义变量 else false"); // 创建Lexer并注入动态值 TransitionLexer lexer = new TransitionLexer(input); lexer.setUserDefinedValues(dynamicValues); // 后续生成TokenStream、Parser等流程不变 CommonTokenStream tokens = new CommonTokenStream(lexer); TransitionParser parser = new TransitionParser(tokens); // ... 执行解析逻辑
方案二:重写Lexer的nextToken方法(无需修改G4)
如果不想改动G4文件,你可以重写Lexer的nextToken方法,手动拦截并识别动态值:
public class TransitionLexer extends Lexer { private Set<String> userDefinedValues = new HashSet<>(); public TransitionLexer(CharStream input) { super(input); } public void setUserDefinedValues(Set<String> values) { this.userDefinedValues = values; } @Override public Token nextToken() { int startIndex = getCharIndex(); int startLine = getLine(); int startPos = getCharPositionInLine(); StringBuilder sb = new StringBuilder(); // 先读取符合UserdefinedValue格式的字符 while (true) { int la = _input.LA(1); if (la == Token.EOF) break; char c = (char) la; if (Character.isLetter(c) || c == '_' || isChinese(c)) { // 匹配允许的字符 sb.append(c); _input.consume(); } else { break; } } // 如果读取到的字符串在动态集合里,返回UserdefinedValue Token if (sb.length() > 0 && userDefinedValues.contains(sb.toString())) { CommonToken token = new CommonToken(UserdefinedValue, sb.toString()); token.setStartIndex(startIndex); token.setStopIndex(getCharIndex() - 1); token.setLine(startLine); token.setCharPositionInLine(startPos); return token; } else { // 回退输入,让默认逻辑处理 _input.seek(startIndex); return super.nextToken(); } } // 判断是否为中文字符的辅助方法 private boolean isChinese(char c) { return c >= '\u4E00' && c <= '\u9FA5'; } // 其他自动生成的代码... }
这种方法不需要修改G4,但需要自己处理字符回退和Token的位置信息,适合不想改动原有语法文件的场景。
方案三:动态生成Lexer(极端场景)
如果你的动态值需要完全自定义词法规则(比如带有特殊符号),可以在运行时动态生成G4文件,然后调用ANTLR的工具类编译并加载Lexer。不过这种方法开销大,一般不推荐,除非必须:
// 动态生成G4内容 String lexerG4 = "lexer grammar DynamicTransitionLexer;\n" + "BOOLEAN: 'true' | 'false';\n" + "IF: 'if';\n" + "THEN: 'then';\n" + "ELSE: 'else';\n" + "UserdefinedValue: 'myVar' | '自定义变量' | 'user_123';\n" + // 动态注入的值 "NAME: (ALPHA | CHINESE | '_')(ALPHA | CHINESE | '_' | DIGIT)*;\n" + "fragment ALPHA: [a-zA-Z];\n" + "fragment CHINESE: [\\u4E00-\\u9FA5];\n" + "fragment DIGIT: [0-9];"; // 保存临时G4文件 File tempG4 = File.createTempFile("DynamicTransitionLexer", ".g4"); Files.write(tempG4.toPath(), lexerG4.getBytes(StandardCharsets.UTF_8)); // 调用ANTLR编译 Tool antlrTool = new Tool(new String[]{"-o", tempG4.getParent(), tempG4.getAbsolutePath()}); antlrTool.process(); // 加载生成的Lexer类 URLClassLoader classLoader = new URLClassLoader(new URL[]{tempG4.getParentFile().toURI().toURL()}); Class<? extends Lexer> lexerClass = (Class<? extends Lexer>) classLoader.loadClass("DynamicTransitionLexer"); // 反射创建Lexer实例 Lexer lexer = lexerClass.getConstructor(CharStream.class).newInstance(input);
注意事项
- 规则顺序:不管用哪种方案,
UserdefinedValue的匹配优先级一定要高于NAME,否则符合条件的动态值会被NAME先匹配。 - 性能优化:如果动态值数量很大,用HashSet可能不够快,可以换成Trie树来优化字符串查找速度。
- 字符集一致性:确保
UserdefinedValue的字符范围和NAME一致,避免出现漏匹配或误匹配的情况。
内容的提问来源于stack exchange,提问作者lution
相关产品推荐
相关产品推荐

