【问题标题】:Parse arbitrary delimiter character using Antlr4使用 Antlr4 解析任意分隔符
【发布时间】:2016-07-06 07:45:57
【问题描述】:

我尝试在 Antlr4 中创建一个语法,它接受 由任意字符分隔的正则表达式(类似于 Perl)。我怎样才能做到这一点?

要明确:我的问题不是正则表达式本身(我实际上不在 Antlr 中处理,而是在访问者中处理),而是分隔符。我可以轻松地为词法分析器定义以下规则:

REGEXP: '/' (ESC_SEQ | ~('\\' | '/'))+ '/' ;
fragment ESC_SEQ: '\\' . ;

这将使用正斜杠作为分隔符(就像在 Perl 中常用的那样)。不过,我也希望能够将正则表达式写成m~regexp~(在 Perl 中也可以)。

如果我必须使用正则表达式本身来解决这个问题,我会使用这样的反向引用:

m(.)(.+?)\1

(这是一个“m”,后接任意字符,后接表达式,后接相同的任意字符)。但是在 Antlr4 中似乎没有反向引用。

如果我可以使用成对的括号会更好,即m(regexp)m{regexp}。但由于可能的括号类型的数量非常少,这可以通过简单地枚举所有不同的变体来解决。

这可以用 Antlr4 解决吗?

【问题讨论】:

    标签: regex antlr4


    【解决方案1】:

    你可以这样做:

    lexer grammar TLexer;
    
    REGEX
     : REGEX_DELIMITER ( {getText().charAt(0) != _input.LA(1)}? REGEX_ATOM )+ {getText().charAt(0) == _input.LA(1)}? .
     | '{' REGEX_ATOM+ '}'
     | '(' REGEX_ATOM+ ')'
     ;
    
    ANY
     : .
     ;
    
    fragment REGEX_DELIMITER
     : [/~@#]
     ;
    
    fragment REGEX_ATOM
     : '\\' .
     | ~[\\]
     ;
    

    如果你运行以下类:

    public class Main {
    
      public static void main(String[] args) throws Exception {
    
        TLexer lexer = new TLexer(new ANTLRInputStream("/foo/ /bar\\ ~\\~~ {mu} (bla("));
    
        for (Token t : lexer.getAllTokens()) {
          System.out.printf("%-20s %s\n", TLexer.VOCABULARY.getSymbolicName(t.getType()), t.getText().replace("\n", "\\n"));
        }
      }
    }
    

    您将看到以下输出:

    REGEX                /foo/
    ANY                   
    ANY                  /
    ANY                  b
    ANY                  a
    ANY                  r
    ANY                  \
    ANY                   
    REGEX                ~\~~
    ANY                   
    REGEX                {mu}
    ANY                   
    ANY                  (
    ANY                  b
    ANY                  l
    ANY                  a
    ANY                  (
    

    {...}? 称为谓词:

    ( {getText().charAt(0) != _input.LA(1)}? REGEX_ATOM )+ 部分告诉词法分析器继续匹配字符,只要REGEX_DELIMITER 匹配的字符不在字符流中的前面。 {getText().charAt(0) == _input.LA(1)}? . 确保确实有一个与第一个字符匹配的结束分隔符(当然是 REGEX_DELIMITER)。

    使用 ANTLR 4.5.3 测试

    编辑

    并且要获得一个以m + 一些可选空格开头的分隔符,您可以尝试这样的事情(未经测试!):

    lexer grammar TLexer;
    
      @lexer::members {
        boolean delimiterAhead(String start) {
          return start.replaceAll("^m[ \t]*", "").charAt(0) == _input.LA(1);
        }
      }
    
      REGEX
       : '/' ( '\\' . | ~[/\\] )+ '/'
       | 'm' SPACES? REGEX_DELIMITER ( {!delimiterAhead(getText())}? ( '\\' . | ~[\\] ) )+ {delimiterAhead(getText())}? .
       | 'm' SPACES? '{' ( '\\' . | ~'}' )+ '}'
       | 'm' SPACES? '(' ( '\\' . | ~')' )+ ')'
       ;
    
      ANY
       : .
       ;
    
      fragment REGEX_DELIMITER
       : [~@#]
       ;
    
      fragment SPACES
       : [ \t]+
       ;
    

    【讨论】:

    • 这看起来很有希望。但是,还有一个小问题:如果我想模仿 Perl 的语法,那么带有除斜杠以外的分隔符的正则表达式 必须在字符“m”(和可选空格)之前。所以以下应该是有效的:m ~foo~ 但是我不能再使用getText().charAt(0) 因为分隔符不在位置0。由于可选空格,它也不在位置1。事实上,我无法预测完全没有位置,那怎么改写呢?
    • 我有想法使用一个动作将分隔符存储在局部变量中,但是词法分析器规则中不允许使用局部变量。
    • @chschroe,检查我的编辑
    • 绝招!我做了一些小的修改,但遵循了基本思想。最后一个问题:通过使用自定义函数,我似乎被绑定到一种目标语言(在本例中为 Java)。如果我想支持多种目标语言(例如 JavaScript 和 Java),哪种方法是正确的?
    • 如果您要重定向到另一种语言,除了重写 @members 块和谓词块中的代码之外别无他法:{...}?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-31
    相关资源
    最近更新 更多