微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

C# 和 ANTLR4:在解析文件时处理“包含”指令

如何解决C# 和 ANTLR4:在解析文件时处理“包含”指令

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

一种建议的方法是:

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

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

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

解决方法

可以通过覆盖 Scanner 的行为,特别是 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<ICharStream>(即 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 文件中,目的是扩展 生成的扫描器类,因为生成的扫描器类用“部分”关键字修饰

生成与给定语法关联的部分 Scanner 类后,导航到 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();
            
        }
,

阅读 Grigoris 先生的回答帮助我找到了另一个可能的解决方案:

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

这让我有机会直接从词法分析器的 EmitEOF() 块中覆盖 @members 的功能,而无需创建一个全新的文件或了解我当前的解析器的 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();

// [...]

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。