【问题标题】:C# and ANTLR4: Handling "include" directives when parsing a fileC# 和 ANTLR4:解析文件时处理“包含”指令
【发布时间】:2021-05-29 16:21:15
【问题描述】:

我的情况是,使用 ANTLR,我正在尝试解析包含对其中其他文件的引用的输入文件,就像 C 语言的 #include "[insert file name]"

一种建议的方法是:

  1. 解析根文件,将所述引用保存为节点(因此,特定语法规则)
  2. 访问树搜索“参考”节点
  3. 对于每个引用节点,解析引用的文件并将节点替换为新生成的树
  4. 以递归方式重复此过程,以处理多个级别的包含

这个解决方案的问题是引用的文件可能是完全部分的(请参阅 C 函数主体内的包含)。为了解析这些文件,我必须实现一个不同的解析器来处理碎片化的语法。

是否有任何有效/建议的方法来(字面上)在正在进行的解析过程中注入新文件?

【问题讨论】:

    标签: c# parsing include antlr4 antlr4cs


    【解决方案1】:

    可以通过覆盖扫描器的行为,特别是NextToken() 方法来解决这个问题。 这是必要的,因为 ANTLR 词法分析器语法(据我所知)和任何操作都无法处理 EOF 令牌 附加到识别 EOF 的词法分析器规则被简单地忽略(如下面的代码所示)。因此,有必要 将此行为直接实现到扫描仪方法中。

    假设我们有一个解析器语法

    parser grammar INCParserGrammar;
    
    @parser::members {
            public static Stack<ICharStream> m_nestedfiles = new Stack<ICharStream>();
    }
    
    options { tokenVocab = INCLexerGrammar; }
    
    /*
     * Parser Rules
     */
    
    compileUnit
        :   (include_directives | ANY )+ ENDOFFILE
        ;
    
    include_directives : INCLUDEPREFIX FILE DQUOTE
                         ;
    

    应该在语法的成员中引入static public Stack&lt;ICharStream&gt;(即mySpecialFileStack)。此堆栈将用于存储与参与解析的文件关联的角色 Steam。字符流被推送到 包含语句遇到此堆栈作为新文件

    和词法分析器语法

       lexer grammar INCLexerGrammar;
    
       @lexer::header {
        using System;
        using System.IO;
       }
    
       @lexer::members { 
        string file;
        ICharStream current;
        
       }
    
    
    /*
     * Lexer Rules
     */
    INCLUDEPREFIX : '#include'[ \t]+'"' {                                                 
                                          Mode(INCLexerGrammar.FILEMODE);
                                        };
    
    // The following ruls has always less length matched string that the the rule above
    ANY : ~[#]+ ;
    
    
    ENDOFFILE : EOF { // Any actions in the this rule are ignored by the ANTLR lexer };
    
    
    ////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    mode FILEMODE;
    FILE : [a-zA-Z][a-zA-Z0-9_]*'.'[a-zA-Z0-9_]+ {  file= Text;
                                                    StreamReader s = new StreamReader(file);
                                                    INCParserGrammar.m_nestedfiles.Push(_input);                                                
                                                    current =new AntlrInputStream(s);           
                                                
                                                 };
    DQUOTE: '"'  {  
                    this._input = current;
                    Mode(INCLexerGrammar.DefaultMode);  };
    

    NextToken() 方法的覆盖主体将放置在用于扩展的 .g4.cs 文件中 鉴于生成的扫描仪类用“partial”关键字修饰,生成的扫描仪类

    在生成与给定语法关联的部分扫描器类后,导航到 ANTLR 运行时中给出的 ANTLR4 词法分析器类,并将所有原始代码复制到这个新方法中,并且, 在中间的 do-while 块(紧跟在 try-catch 块之后)添加以下代码:

    if (this._input.La(1) == -1)
    {
        if ( mySpecialFileStack.Count == 0 )
            this._hitEOF = true;
        else
            this._input = mySpecialFileStack.Pop();
    }
    

    NextToken() 方法重写的完整主体是

    public override IToken NextToken() {
                int marker = this._input != null ? this._input.Mark() : throw new InvalidOperationException("nextToken requires a non-null input stream.");
                label_3:
                try {
                    while (!this._hitEOF) {
                        this._token = (IToken)null;
                        this._channel = 0;
                        this._tokenStartCharIndex = this._input.Index;
                        this._tokenStartCharPositionInLine = this.Interpreter.Column;
                        this._tokenStartLine = this.Interpreter.Line;
                        this._text = (string)null;
                        do {
                            this._type = 0;
                            int num;
                            try {
                                num = this.Interpreter.Match(this._input, this._mode);
                            } catch (LexerNoViableAltException ex) {
                                this.NotifyListeners(ex);
                                this.Recover(ex);
                                num = -3;
                            }
    
                            if (this._input.La(1) == -1) {
                                if (INCParserGrammar.m_nestedfiles.Count == 0 ) {
                                    this._hitEOF = true;
                                }
                                else
                                {
                                    this._input = INCParserGrammar.m_nestedfiles.Pop();
                                }
                            }
    
                            if (this._type == 0)
                                this._type = num;
                            if (this._type == -3)
                                goto label_3;
                        }
                        while (this._type == -2);
                        if (this._token == null)
                            this.Emit();
                        return this._token;
                    }
                    this.EmitEOF();
                    return this._token;
                } finally {
                    this._input.Release(marker);
                }
            }
    
    

    现在,当您在代码中识别出需要解析的文件时,只需添加以下操作

    FILE
        : [a-zA-Z][a-zA-Z0-9_]*'.'[a-zA-Z0-9_]+ {
            StreamReader s = new StreamReader(Text);
            mySpecialFileStack.Push(_input);                                                
            _input = new AntlrInputStream(s);                                               
        };
        
    DQUOTE: '"'  {  this._input = current;
                Mode(INCLexerGrammar.DefaultMode);  };
    //***Warning:***
    // Be careful when your file inclusion is enclosed inside quotes or other symbols, or if  
    // the filename-to-be-included is not the last token that defines an inclusion: `_input`  
    // should only be switched AFTER the inclusion detection is completely found (i.e. after  
    // the closing quote has been recognized).  
    
    

    下面给出了主程序,很明显根文件是首先添加到 ICharStream 堆栈中的

     static void Main(string[] args) {
                var a = new StreamReader("./root.txt");
                var antlrInput = new AntlrInputStream(a);
                INCParserGrammar.m_nestedfiles.Push(antlrInput);
                var lexer = new INCLexerGrammar(antlrInput);
                var tokens = new BufferedTokenStream(lexer);
                var parser = new INCParserGrammar(tokens);
                parser.compileUnit();
                
            }
    

    【讨论】:

      【解决方案2】:

      阅读 Grigoris 先生的回答帮助我发现了另一种可能的解决方案:

      在试图弄清楚建议的解决方案如何工作时,我偶然发现了public virtual IToken EmitEOF() 方法。如果将 Grigoris 先生提供的代码放入此函数中(稍作更改),一切似乎都按预期工作。

      这让我有机会直接从 @members 词法分析器块中覆盖 EmitEOF() 的功能,而无需创建一个全新的文件或了解我当前的解析器的 NextToken()方法有效。

      词法分析器语法:

      lexer grammar INCLexerGrammar;  
        
      @lexer::header {  
          using System;  
          using System.IO;  
          using System.Collections.Generic;  
      }  
        
      @lexer::members {   
        
          private Stack<ICharStream> _nestedFiles = new Stack<ICharStream>();  
            
          public override IToken EmitEOF(){  
              if (_nestedFiles.Count == 0 ) {  
                  return base.EmitEOF();  
              };  
              this._hitEOF = false;  
              this._input = _nestedFiles.Pop();  
              return this.NextToken();  
          }  
      }  
        
      /////////////////////////////////////////////////////////////////////////////////////  
      // Default Mode /////////////////////////////////////////////////////////////////////  
      /////////////////////////////////////////////////////////////////////////////////////  
        
      // Skipped because we don't want to hide INCLUDEPREFIX's existance from parser  
      INCLUDEPREFIX : '#include'[ \t]+'"' { Mode(INCLexerGrammar.FILEMODE); } -> skip;  
      
      // This is the only valid token our Grammar accepts
      ANY : ~[#]+ ;  
        
      /////////////////////////////////////////////////////////////////////////////////////  
      mode FILEMODE; //////////////////////////////////////////////////////////////////////  
      /////////////////////////////////////////////////////////////////////////////////////  
        
      // Skipped because we don't want to hide FILE's existance from parser  
      FILE : [a-zA-Z][a-zA-Z0-9_]*'.'[a-zA-Z0-9_]+ {   
        
          // Create new StreamReader from the file mentioned  
          StreamReader s = new StreamReader(Text);  
            
          // Push the old stream to stack  
          _nestedFiles.Push(_input);  
            
          // This new stream will be popped and used right after, on DQUOTE.  
          _nestedFiles.Push(new AntlrInputStream(s));  
      
      } -> skip;  
        
      // Skipped because we don't want to hide DQUOTE's existance from parser  
      DQUOTE: '"' { 
      
          // Injecting the newly generated Stream.  
          this._input = _nestedFiles.Pop();
      
          Mode(INCLexerGrammar.DefaultMode);
      
      } -> skip;
      

      解析器语法:

      parser grammar INCParserGrammar;  
        
      options { tokenVocab = INCLexerGrammar; }  
        
      // Our Grammar contains only ANY tokens. Include directives  
      // and other Tokens exists only for helping lexer to  
      // inject the contents of other files inside the current  
      // scanning process.  
        
      compileUnit  
        :  ANY+ EOF  
        ;
      

      执行调用:

      // [...]
      
      var myRootFile = new StreamReader("./root.txt");
      var myAntlrInputStream = new AntlrInputStream(myRootFile);
      
      var lexer = new INCLexerGrammar(myAntlrInputStream);
      var tokens = new BufferedTokenStream(lexer);
      var parser = new INCParserGrammar(tokens);
      
      parser.compileUnit();
      
      // [...]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-06-22
        • 2022-11-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-23
        相关资源
        最近更新 更多