利用ANTLR生成C++描述的分析程序

2023-05-16

摘要

ANTLRANother Tool for Language Recognition)是一种基于LLk)文法的语法分析程序(以下简称分析器)生成工具。其生成的分析器默认使用Java描述,而不是更高效的C++。本文介绍了在Windows平台下,借助VC6.0进行组织工程,使用ANTLR生成C++描述的分析器的方法,并给出了一个实例。最后,本文对ANTLR本身做出了一点小小的改进。

关键字

ANTLR,语法分析器,语法分析器生成工具

ANTLR简介

分析器的自动生成一直是编译理论研究的一个方向。早期的程序员手工编写分析器,不但费时费力,而且编写的分析器不稳定、不易修改和移植。在自动化大潮冲击之下,越来越多的程序员抛弃了这种手工做法。

由旧金山大学的Terence Parr 领导开发的ANTLR(以前叫做PCCTSPurdue Compiler Construction Tool Set,普渡大学编译器构建工具集)是一种分析器自动生成工具,它可以接受语言的文法描述,并能产生识别这些语言的程序。而且我们可以在文法描述中插入特定的语义动作,告诉ANTLR怎样去创建抽象语法树(AST)和怎样产生输出。

现在ANTLR越来越流行(有评论说ANTLR的出现是一个里程碑),不仅因为它功能更强、容易扩展、开源,而且ANTLR生成的代码和使用递归下降方法(手工生成分析器的主要方法)生成的代码很相似,易于阅读理解。与之相比,另外一种著名的分析器生成工具YACCYet Another Compiler-Compiler,基于LR分析方法)生成的程序就比较晦涩。

目前国内介绍ANTLR的文章不多,仅有的文章也是介绍使用ANTLR生成使用java描述的分析器。其实ANTLR也可以生成C++描述的源程序(从2.7.3版本开始,ANTLR开始支持C#,将来还会支持Python),不过需要一些准备工作。本文将详细地介绍其中的具体步骤。

最新版本的ANTLR可以去ANTLR的官方网站(http://www.ANTLR.org)下载。截止到20046月,ANTLR的最新版本是2.7.4。下载的文件是一个不到1.3Mtar.gz形式的压缩包,将其解压到某个目录(下文用<ANTLRpath>表示)。

ANTLR是使用Java开发的,需要JDK的支持。本文假设您的机器已经安装JDK,并正确设置了classpath

文法文件

文法就是语言识别的规则。它是ANTLR生成程序的依据。文法文件是ANTLR的核心,是程序员和ANTLR进行交流的接口。

文法文件的编写基本是面向被解决的问题的。程序员只需要集中精力思考解决问题的逻辑,而不是羁绊于某种程序设计语言的实现细节,因此降低了出现错误的可能性。

文法文件的语法简介

本文只是简单地介绍一个文法文件的语法,具体内容可以参阅ANTLR的相关文档。

文法文件一般包括header块、options块、文法分析器类(parser)及规则定义、词法分扫描器类(lexer)及token定义。其中最为重要的是规则和token的定义。

规则的定义形式和编译理论中的扩展巴科斯范式(EBNF)极为相似,包括规则名、规则体、一个用作结束标志的分号和异常处理部分(可省略)。例如如下的规则就描述了C语言中的赋值语句的语法:

assignment_stat:

     id '=' expr ';'

     ;

其意义是:一条赋值语句是由一个id、一个等号、一个表达式和一个分号顺序组成的。

Token的定义方法与规则类似。例如如下的token定义就表示一个十进制的整数:

NUM:

     ('1'..'9')('0'..'9')*

     ;

其意义是:数字(NUM)的第一字符是‘1’到‘9’中的一个字符,后面是0个或多个‘0’到‘9’之间的字符。

需要注意的一点是:规则的名字必须是小写字母开始,而token的名字则必须是大写字母开始。

设定ANTLR生成的语言

ANTLR有很多选项,可以通过在文法文件中的options块中进行设置,其中包括ANTLR最终生成的语言。如果要生成C++描述的分析器程序,就要如下设定:

options

{

language="Cpp";

// Other options

}

language选项的默认值是“Java”。如果您希望生成的程序是C#的,将language设为“Csharp”就可以了。

C++程序的例子

下面就给出一个ANTLR生成的C++描述的分析器的实例。该分析器的功能是分析用户输入的一个算术表达式,给出该表达式的最终结果。在该表达式中允许出现的运算符除了加减乘除之外,还包括求幂运算符“^”,以及sincostan三个三角函数。

在开始之前,我们首先要生成编译链接ANTLR生成的程序时需要的库文件。

构建静态链接库

构建(build)由ANTLR生成的C++程序需要一个运行库的支持。该运行库的源代码也是完全开放的,位于<ANTLRpath>/antlr-2.7.4/lib/cpp目录下。我们可以选择这些代码其编译为静态链接库或者动态链接库。对于2.7.4版本的ANTLR,编译动态链接库需要VC7.0以上的编译环境。这里我们将其编译为静态库。

首先使用VC6.0新建一个名为ANTLRLibWin32的静态链接库的工程,不要选择“Pre-compiled Header”和“MFC support”选项。

点击菜单“ProjectàAdd to ProjectàFiles…”,将Antlr-2.7.4/lib/cpp/src下面的除了dll.cpp之外的所有文件加入到工程中(注意一定不要加入dll.cpp,否则无法通过编译)。

为了让VC6.0找到所需要的头文件,需要将<ANTLRpath>/antlr-2.7.4/lib/cpp加入到头文件搜索路径中。具体方法是点击“ProjectàSettings…”,在弹出的对话框中选择“Debug”标签页,在下拉列表中选择“Preprocessor”,在“Additional include path”中,如图填入:

         <ANTLRpath>/antlr-2.7.4/lib/cpp

此时build整个工程,就可以生成ANTLR的运行库文件ANTLRLib.lib(有些文档说需要在工程设置中开启RTTI选项,但是似乎不这样做也没有太大的影响)。

编写文法文件

根据需求,不难写出如下的文法文件:

header{

#include <stdlib.h>

#include <stdio.h>

#include <math.h>

}

 

options

{

    language="Cpp";

}

 

class ExprParser extends Parser;

{

}

 

// rules

expr returns [double value=0]

{double x;}

    :

    value=term

    (

    PLUS x=term {value+=x;}

    |

    MINUS x=term{value-=x;}

    )*

    ;

exception

    catch [ANTLR_USE_NAMESPACE(antlr)ANTLRException& ex] {

        // catch all exceptions and report it

        reportError(ex.toString());

    }

 

term returns [double value=0]

{double x;}

    :

    value=factor

    (

    STAR x=factor {value*=x;}

    |

    SLASH x=factor { value /= x;}

    )*

    ;

 

factor returns [double value =0 ]

{double x;}

    :

    value = atom

    (

    TOK_POW x = atom { value = pow(value,x); }

    )*

    ;

 

atom returns [double value=0]

{double x;}

    :

    i:NUM

    {

        value=atof((i->getText()).c_str());

    }

    |

    TOK_SIN x = atom { value = sin (x);}

    |

    TOK_COS x = atom { value = cos (x);}

    |

    TOK_TAN x = atom { value = tan (x);}

    |

    LPAREN value=expr RPAREN

    ;

exception

    catch [ANTLR_USE_NAMESPACE(antlr)ANTLRException& ex] {

        reportError(ex.toString());

    }

 

class ExprLexer extends Lexer;

options{

    k=1;

    caseSensitive = false;

}

 

// tokens

LPAREN  :'(';

RPAREN  :')';

PLUS    :'+';

MINUS   :'-';

STAR    :'*';

SLASH   :'/';

NUM     :('0'..'9')('0'..'9')*('.'('0'..'9')*)?;

RETURN  :'/n';

 

// math token

TOK_SIN :"sin";

TOK_COS :"cos";

TOK_TAN :"tan";

TOK_POW :'^';

 

// white space

WS  :

    (

    ' '

    |

    '/t'

    )

    {$setType(ANTLR_USE_NAMESPACE(antlr)Token::SKIP);}

    ;

 

将该文件保存为test.g(‘g’是默认的文法文件的扩展名)。

在该文法文件中,定义了一个分析器类ExprParser和一个词法扫描器类ExprLexerANTLR会为两个类分别生成头文件和实现文件。

使用VC6.0组织工程

现在需要由文法文件生成分析器的源代码,然后再添加其他的一些代码,最后编译这些代码,过程略显繁琐。因此这里借助VC6.0作为开发环境来组织工程,简化步骤。

新建工程

使用VC6.0新建一个名为AntlrCppWin32控制台的项目。选择新建一个空的工程。点击“ProjectàAdd To ProjectàFiles”,将文法文件test.g添加到工程中。

VCFileView中右键点击该文法文件,在弹出菜单中选择“Settings…”,弹出如图所示的对话框,选择“Custom Build”标签页,如图所示:

在“Commands”里面填入调用ANTLR编译文法文件的命令:

         java -cp  <ANTLRpath>/antlr-2.7.4/antlr.jar antlr.Tool -o "$(WkspDir)" $(InputName).g

在“Outputs”里面填入ANTLR编译文法文件后要生成的所有文件的名字,如下:

         ExprLexer.cpp     

         ExprLexer.hpp

         ExprParser.cpp

         ExprParser.hpp

         ExprParserTokenTypes.hpp

         ExprParserTokenTypes.txt

生成分析器源代码

设置完成之后,就可以编译该文法文件了:选中该文法文件,按Ctrl+F7(或者点击工具栏中的编译按钮),执行编译操作。在VCOutput窗口里面会显示出如下的内容:

         --------------------Configuration: AntlrCpp - Win32 Debug--------------------

         Performing Custom Build Step on ./testjava.g

         ANTLR Parser Generator   Version 2.7.4   1989-2004 jGuru.com

        

         ExprLexer.cpp - 0 error(s), 0 warning(s)

此时,分析器的源代码已经生成。再次点击菜单“ProjectàAdd To ProjectàFiles”,将生成的所有cpp文件和hpp文件添加到工程中。

指定输入方式

ANTLR生成的源代码只是分析器的核心部分,程序员还需要指定分析器的输入。为此还需要新建一个main.cpp文件,指定分析器的输入。

若希望让分析器分析通过键盘输入的字符串,则代码如下:

         #include "ExprParser.hpp"

         #include "ExprLexer.hpp"

         #include <iostream>

         using namespace std;

         void main()

         {

              ExprLexer lexer( cin );

              ExprParser parser( lexer );

              double x = 0;

              x = parser.expr();

              cout << "The Result is :" << x << endl;

         }

若希望让分析器分析一个文件里面的字符串的话,则相应代码如下:

         #include "ExprParser.hpp"

         #include "ExprLexer.hpp"

         #include <iostream>

         #include <fstream>

         using namespace std;

         void main()

          {

               fstream from("test.in");

               ExprLexer lexer(from);

               ExprParser parser( lexer );

              double x = 0;

              x = parser.expr();

              cout << "The Result is :" << x << endl;

         }

编译得到最终的结果

在构建工程之前,也需要让VC6.0知道上哪里去找所需的头文件。添加头文件搜索路径的方法前面已经介绍过,不再赘述,如图所示。

如果现在就Build工程的话,您一定会看到很多的链接错误。这是因为编译后的目标文件没有和运行库链接在一起。指定链接库的方法是在上面的设置工程属性的对话框中选择“Link”标签页,在“Object/library modules:”中加入“antlrlib.lib”(要注意正确的路径)。

此时就可以Build整个工程了。生成的可执行文件运行的效果如图所示:

ANTLR一个小小改进

ANTLR的下载文件中包括了全部的源代码,而且允许修改。我们可以针对自己的特殊需要改进ANTLR

问题的提出

如果在某条规则(经过ANTLR编译后就会变为一个函数)中,指定了参数的默认值,那么在生成的程序中,我们可能看到相应函数的实现中,隐去了该参数的默认值(否则就会违反C++的语法)。但是考虑到程序的可读性,我们希望做出一点改进:在函数实现中,将参数的默认值使用注释符号(“/*”和“*/”)括起来,而不是完全的隐去。

代码的修改

找到<ANTLRpath>/antlr-2.7.4/antlr/cppcodegenerator.java文件,修改其3502行到3526行。原来的程序为:

String oldarg = rblk.argAction;

String newarg = "";

 

String comma = "";

int eqpos = oldarg.indexOf( '=' );

if( eqpos != -1 )

{

     int cmpos = 0;

     while( cmpos != -1 )

     {

         newarg = newarg + comma + oldarg.substring( 0, eqpos ).trim();

         comma = ", ";

         cmpos = oldarg.indexOf( ',', eqpos );

         if( cmpos != -1 )

         {

              // cut off part we just handled

              oldarg = oldarg.substring( cmpos+1 ).trim();

              eqpos = oldarg.indexOf( '=' );

         }

     }

}

else

     newarg = oldarg;

 

println( newarg );

修改如下:

String oldarg = rblk.argAction;

String newarg = "";

 

String comma = "";

int eqpos = oldarg.indexOf( '=' );

if( eqpos != -1 )

{

     int cmpos = 0;

     while( cmpos != -1 )

     {

         newarg = newarg + comma + oldarg.substring( 0, eqpos ).trim() + " /* ";

         comma = ", ";

         cmpos = oldarg.indexOf( ',', eqpos );

         if( cmpos != -1 )

         {

              // get the default value of the argument

              newarg = newarg + oldarg.substring( eqpos, cmpos ) + " */ ";

              // cut off part we just handled

              oldarg = oldarg.substring( cmpos+1 ).trim();

              eqpos = oldarg.indexOf( '=' );

         }else {

              newarg = newarg + oldarg.substring( eqpos ).trim() + " */";

         }

     }

}

else

     newarg = oldarg;

 

println( newarg );

重新编译ANTLR

重新编译ANTLR的步骤是:

开启一个命令行的环境,改变当前路径为<ANTLRpath>/antlr-2.7.4/antlr/build,输入如下命令:

         javac *.java

然后改变当前路径为<ANTLRpath>/ antlr-2.7.4,输入如下命令,编译源程序:

         java -cp  <ANTLRpath>/antlr-2.7.4/antlr.jar antlr.build.Tool build

最后重新生成jar文件,输入如下命令:

         java -cp  <ANTLRpath>/antlr-2.7.4/antlr.jar antlr.build.Tool jar

至此,ANTLR已经被更新。

总结

经过本文的介绍,相信您对ANTLR有了更多的了解。充分利用ANTLR可以极大的减轻编写分析的负担。结合高效的C++ANTLR生成的分析器的效率和手工编写的分析器相差不多。相信ANTLR会有美好的明天。

编译环境

Windows2000 SP4VC6.0ANTLR 2.7.4J2SDK 1.4.1

参考文献

ANTLR-2.7.4文档

www.antlr.org

ANTLR: A Predicated-LL_k_ Parser GeneratorT. J. PARRSOFTWARE-PRACTICE AND EXPERIENCE, VOL. 25(7), 789–810(JULY1995)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

利用ANTLR生成C++描述的分析程序 的相关文章

  • 如何使用 C# 的 ANTLR 获取 Lexer 和 Parser?

    似乎 ANTLR 支持 C 语言 但我不知道如何生成相关类 我搜索并看到存在 Visual Studio 扩展 但我不支持 2015 那么如何使用 ANTLR 手动为 C 生成词法分析器和解析器 VS 扩展主要用于语法突出显示和编辑器细节
  • ANTLR如何在输入结束时检测垃圾数据

    当使用 ANTLR 编写的语法时 解析器可以正确识别输入流中的数据 但如果我在输入末尾有一些垃圾文本 不应该由语法解析 解析器不会抱怨 我想这种行为是可以的 我的意思是解析器完成了它的工作并解析了我所说的它应该解析的任何内容 但是有什么技巧
  • ANTLR 隐式乘法

    我是 ANTLR 的新手 我正在尝试扩展所提供的简单计算器的示例here https stackoverflow com a 1932664 具体来说 我尝试添加一些简单的函数 负数等 以熟悉 ANTLR 然而 我在尝试实现 隐式 乘法时遇
  • ANTLR 4 树注入/重写运算符

    在 ANTLR 3 中您可以执行以下操作 andExpression andnotExpression gt andnotExpression AND a andnotExpression gt AndNode andExpression
  • ANTLR 3 中 wikitext-to-HTML 的工作示例

    我试图在 ANTLR 3 中充实一个 wikitext to HTML 翻译器 但我一直陷入困境 您知道我可以检查的工作示例吗 我尝试了 MediaWiki ANTLR 语法和 Wiki Creole 语法 但无法让它们在 ANTLR 3
  • 如何使用ANTLR生成的语法文件?

    我认为这是一个愚蠢的问题 但我刚刚开始使用 ANTLR 我将他们教程中的 SimpleCalc 语法放在一起 并以 C 作为目标语言生成它 我得到了 SimpleCalcParser c h 和 SimpleCalcLexer c h 作为
  • 如何使用Antlr实现函数调用,以便在定义之前就可以调用它?

    一旦构建了 AST 实现树遍历器以便可以按任意顺序定义和调用函数的最佳方法是什么 例如 这在 PHP 中是有效的 我猜想一定有第二遍 或者树转换 但我在这个主题上找不到任何有趣的东西 这个问题可能不是 Antlr 特有的问题 但如果你能给我
  • 将简单的 Antlr 语法转换为 Xtext

    我想将一个非常简单的Antlr语法转换为Xtext 所以没有句法谓词 https stackoverflow com questions 5728659 translate antlr grammar into xtext grammar
  • ANTLR 歧义问题

    我有这个语法 grammar MyGrammar prog lexeme lexeme TOK INTLIT 0 9 Identifiers Letter Letter Digit fragment Letter a zA Z fragme
  • 有谁知道在 ANTLRWorks 中调试树语法的方法

    ANTLR 使用的推荐模式是让解析器构造一个抽象语法树 然后构建树遍历器 又称树语法 来处理它们 我试图弄清楚为什么我的树语法不起作用 并且希望使用 ANTLRWorks 的调试器 就像我将其用于解析器本身一样 解析器的输入是 源代码 但树
  • 在 ANTLR 3 中,如何在运行时而不是提前生成词法分析器(和解析器)?

    我想在运行时生成 antlr 词法分析器 也就是说 生成语法并从语法生成词法分析器类及其在运行时的支持位 我很高兴将它输入到 java 编译器中 它可以在运行时访问 这是一种快速但肮脏的方法 生成一个combined ANTLR 语法 g给
  • 将 ANTLR 语法翻译为 XText 语法:如何删除句法谓词

    我对 Xtext 和 ANTLR 都很陌生 我需要将 ANTLR g 语法转换为 XTEXT xtext 语法 在 ANTLR 语法中 存在 Xtext 不支持的语法谓词 有没有办法删除 翻译这些谓词 Thanks EDIT 我尝试翻译的
  • ANTLRWorks 1.4.3 无法正确读取扩展 ASCII 字符

    我正在开发一个相当标准的编译器项目 我选择 ANTLR 作为解析器生成器 在将现有语法从 v2 更新到 v3 时 我注意到 ANTLRWorks ANTLR 的官方 IDE 无法正确显示文件中的任何扩展 ASCII 字符 即使使用 Note
  • C# ANTLR 语法?

    我正在寻找交钥匙ANTLR http www antlr org C 语法 生成可用的抽象语法树 AST 并且与后端语言无关或以 C C C 或 D 为目标 它不需要支持错误报告 附 我不愿意做任何修复 因为替代方案并不难 这可能太晚了 但
  • Antlr4中有哪些加速解析的方法?

    我对Antlr4的性能有些怀疑 我目前正在使用 Python 和 Antlr4 与 Java 相比 它非常慢 使用 Antlr4 IntelliJ 插件验证 由于我需要使用更大的代码进行解析 因此我计划切换到 Antlr 最快的语言 例如
  • 自动解析 PHP,将 PHP 代码与 HTML 分离

    我正在开发一个大型 PHP 代码库 我想将 PHP 代码与 HTML 和 JavaScript 分开 我需要对 PHP 代码进行多次自动搜索和替换 对 HTML 进行不同的搜索和替换 对 JS 进行不同的自动搜索和替换 有没有一个好的解析器
  • 如何在 Antlr4 中为零参数函数编写语法

    我的函数具有参数语法 如下面的词法分析器和解析器 MyFunctionsLexer g4 lexer grammar MyFunctionsLexer FUNCTION FUNCTION NAME A Za z0 9 DOT COMMA L
  • 使用 ANTLR 为 java 源代码生成抽象语法树

    如何使用 ANTLR 从 java src 代码生成 AST 有什么帮助吗 好的 步骤如下 前往ANTLR站点 http www antlr org 并下载最新版本 下载Java g和JavaTreeParser g文件来自here htt
  • ANTLR 中的布尔和算术表达式语法

    我正在尝试编写算术和布尔表达式的语法 我不明白我做错了什么 对于我的语法 ANTLR 说 致命 规则logic atom 由于可从 alts 1 2 到达的递归规则调用而具有非 LL 决策 通过左分解或使用语法谓词或使用 backtrack
  • 在简单整数列表语法中使用 AntLR4 中的访问者

    我是 AntLR 的新手 我使用的是AntLR4版本 我编写了以下属性语法 它识别整数列表并在末尾打印列表的总和 list g4 grammar list header import java util List import java u

随机推荐