Electronic Joint Business

Solution for E-Business

编译器

Flex 和 Bison 简介 (三) 支持 C++

和一些收费软件相比,Flex 和 Bison 在某一些方面还是相对比较的弱的,比如说 Flex 对 C++ 的支持还处在实验状态,这意味着在将来的版本中有可能发生较大的改动,而且目前的解决方案在一些细节上处理得还并不好,但作为一个免费软件,Flex 和 Bison 总体上还是非常不错的软件,而且生成的词法生成器不依赖于其他库文件,可移植性比较好。

如果你更倾向于 Flex 和 Bison ,而不是用我们在第二节末尾提到的商业收费软件 Parser Generator 来生成资源模板解析器,那么请跟随本文来了解一下 Flex 和 Bison 是如何支持 C++ 的。

Flex 对 C++ 支持的基本是通过继承来完成的。在头文件 FlexLexer.h中定义了两个类 FlexLexer 和 yyFlexLexer。前者是一个抽象类,定义了 Flex 所生成的词法分析器的接口(interface),比如yylex();后者则继承自FlexLexer,是词法分析器的实现类,封装了词法分析器用到的状态变量等。因此,在默认的 C++ 模式下,Flex 的任务就是根据 “.l” 源文件自动生成 yyFlexLexer 中各成员函数的定义。规则的动作代码自然是被合成为 yyFlexLexer::yylex() 的实现啦,因此在规则动作中我们访问的变量和函数实际都是 yyFlexLexer 类的成员(或者是yylex()的参数 ),而不再是全局变量或全局函数。这便是 Flex 对 C++ 解决方案的基本原理。

这里还有一个问题。即在默认情况下,Flex 把生成的代码都放在了 yyFlexLexer 这一个类里,而事实上,实现类是应该有多个的,尤其是当我们的程序需要多个词法分析器的时候,每个词法分析器都应该对应一个实现类。Flex 是怎么解决这个问题的呢?事实上,在包含进这个头文件之前,”yyFlexLexer” 已经被定义为一个宏,其默认值为”yyFlexLexer”,而 Flex 提供了一些机制来重新定义这个宏,这样就能避免命名冲突的问题啦。关于这个问题我们先讲这么多,下面我们先详细讨论在默认情况下的具体做法,再回来讨论定制的问题。

为了让它跑起来,有两件最基本的事情是必须做的。首先,要在文件的定义段打开 c++ 选项,即添加 “%option c++” 一行。 或者在命令行使用 “-+” 选项也是一样的。打开这个选项之后,Flex 就会生成 C++ 代码,生成的代码会默认输出到 lex.yy.cc文件中(后缀不是”.c”啦)。可是如果这个时候直接编译(使用命令$ flex abc.l && g++ lex.yy.cc),则会收到链接出错的提示:”undefined reference to yyFlexLexer::yywrap()”。怎么回事呢?是不是忘记添库文件啦?仔细想一想,这个错误其实是在抱怨yyFlexLexer::yywrap()函数没有定义。原来,Flex 忘记帮我们生成一个默认的 yywrap() 的定义啦。其实这也不能怪 Flex 粗心,因为即便在 c 的情况下,它也不会自动合成这个函数的定义,我们之所以使用 C 语言时没有收到这个链接错误,只是因为在 fl 库中提供了一个默认的实现而已,因此如果我们自己不写,链接器就会把 fl 库中的那一个链接进来啦。可是,这种方法在 C++ 的环境下就不工作,因为这个是一个类的成员函数,而不是全局的啦。

>>> 阅读全文

 

, ,

编写 GCC 前端

文章评价:
一、简介
这篇文章演示了如何为 GCC 创建新的前端的基本步骤,它可以帮你在 GCC 编译工具链的基础上创建自己的编译器。另外本文还囊括了一些基础工具的使用方法,如 Bison 和 Flex。阅读本文需要 C 语言的基础知识。你还可以参考介绍编译器基本原理的文章,这可以让你更好地理解相关的内容。1

本文会演示如何在 GCC 编译工具链中添加一种新的迷你语言。虽然 GNU Internal 这部手册已经详细介绍了 GCC 的内部机理,但对新手来说,其内容实在多得可怕。通过本文中的例子,新手也可以开始着手摆弄复杂的 GCC 基础代码。

二、编译器基础知识
编译器本质上就是个翻译器,它读入程序的源代码并将其转换成目标语言,目标语言通常是真实(或虚拟)处理器的汇编代码。设计一个真正的编译器是项复杂的工作,需要有计算机科学和数学的正规课程的背景。

整个编译过程可分为一定量的子任务,也被称为阶段(phase)。通常会涉及以下阶段:

  • 词法分析 – Lexical analysis
  • 语法分析 – Syntax analysis
  • 生成中间代码 – Intermediate code generation
  • 代码优化 – Code Optimization
  • 代码生成 – Code generation

上述所有阶段都会用到符号表错误处理器。我们先来看一下这些阶段:

>>> 阅读全文

 

, , ,

Flex 和 Bison 简介 (二)

在上一篇文章中,我们讨论了如何用 flex 和 Bison 来 创建词法分析器(标记识别器)和解析器。本文是本系列文章的第二篇,我们将会看到如何用这些工具来创建可以读取 Visual Studio 6 资源文件的解析器。本文将重点关注于词法分析器。

不过考虑到这两个工具的密切联系,我们会在解析器和词法分析器之间来回跳转。在本文的最后,你可以下载到词法分析器和解析器的源代码。

如我们在第一篇文章中讨论的,词法分析器负责从某处读取输入流,并将其分解成一连串的标记。每个标记代表着最底层的构造块,用于表示诸如字符串、 数字或关键字等等东西。词法分析器通过将输入数据和一系列正则表达式 (规则) 相匹配来实现实现这一点。当它找到和特定规则的一个匹配时,它会执行将程序员所编写的一些执行代码,并向解析器返回一个标记,用于表明哪个规则被匹配了。它还可能会返回一些与规则关联的数据。

例如下面的正则表达式。

/* Decimal numbers */
-?[09]+    {
        yylval.ival = atoi(yytext);
        return NUMBER;
    }

该表达式会匹配以负号开头的包含至少一个数的十进制数序列。(有关正则表达式的含义和格式,你可以参考:http://www.monmouth.com/~wstreett/lex-yacc/flex_1.html 并搜索的”模式”)。当找到匹配时,它会调用 atoi 函数并将字符串 yytext 传递给它作为参数,yytext 正好是符合规则的字符串。另一方面,atoi 的返回值被赋给在解析器中定义的联合 yylval 的成员 ival。最后该操作会返回一个 NUMBER 标记给解析器并终止。

>>> 阅读全文

 

, , , , ,

Flex 和 Bison 简介(一)

文章评价:
我最近有个项目需要一个对话框编辑器,客户要求该编辑器能够加载对话框模板,并显示出相应的对话框。

具体的实现方法有简单的,也有复杂的。简单的方法只需将资源脚本编译成 .res 文件,然后让程序读取这些编译过的模板,由此得到对话框模板的句柄,并传递给 CreateDialogIndirect 函数。这样就可以用代码来检查每个控件的属性。但这种方法有个缺点:一旦修改过模板就必须重新编译资源脚本,否则将导致严重的逻辑错误。

有鉴于此,我舍易求难:实现一个能读取 Visual Studio6 资源文件 (.rc 文件)的解析器来检查每个控件的属性,然后再返回编译过的对话框模板。

如果你浏览过资源模板文件的源文件,你会发现自己动手从头开始实现这样一个解析器是不可能的。资源文件包含各种文本块,其中有注释、工具栏资源、菜单、对话框等等。每种文本块都有固定的格式。要用面向过程的方法来编写解析这些文本块的程序,要解决的问题实在多如牛毛。还好我们有 Flex 和 Bison 来提供支持。

什么是 Flex 和 Bison?
既然提到 Flex 和 Bison 就不能不说到其前身 Yacc 和 Lex 。Yacc 和 Lex 来自 UNIX。Yacc 是 “yet another compiler compiler” 的缩写,而 Lex 则是 ‘lexical analyser’ 的简称。仅仅它们的名称就已经说明了很多问题 !你可以参阅 compilertools 站点 ,该站点提供了适合不同水平用户的范例,而且还有很详细的说明和解释。

>>> 阅读全文

 

, , , , ,

Python编写的强大的通用解析器

Spark 是一种用 Python 编写的强大的、通用的解析器/编译器框架。在某些方面,Spark 所提供的比 SimpleParse 或其它 Python 解析器要多得多。不过由于它完全是用 Python 编写的,所以速度也会比较慢。

我将在本文中继续介绍一些解析的基本概念,并对 Spark 模块进行了讨论。解析框架是一个内容丰富的主题,它值得多花时间去全面了解;这篇文章为读者和我自己都开了一个好头。

在日常的编程中,我经常需要标识存在于文本文档中的部件和结构,这些文档包括:日志文件、配置文件、定界的数据以及格式更自由的(但还是半结构化的)报表格式。所有这些文档都拥有它们自己的“小语言”,用于规定什么能够出现在文档内。我编写这些非正式解析任务的程序的方法总是有点象大杂烩,其中包括定制状态机、正则表达式以及上下文驱动的字符串测试。这些程序中的模式大概总是这样:“读一些文本,弄清是否可以用它来做些什么,然后可能再多读一些文本,一直尝试下去。”

解析器将文档中部件和结构的描述提炼成简明、清晰和说明性的规则,确定由什么组成文档。大多数正式的解析器都使用扩展巴科斯范式(Extended Backus-Naur Form,EBNF)上的变体来描述它们所描述的语言的“语法”。基本上,EBNF 语法对您可能在文档中找到的部件赋予名称;另外,较大的部件通常由较小的部件组成。小部件在较大的部件中出现的频率和顺序由操作符指定。

举例来说,清单 1 是 EBNF 语法 typographify.def,我们在 SimpleParse 那篇文章中见到过这个语法(其它工具运行的方式稍有不同):
  

>>> 阅读全文

 

, , , , , ,

.NET编译器构造工具Irony

简介
Irony(不是 IronRuby),是个全新的 .NET 开源项目,其设计目的是为了提供一整套库和工具,以方便在 .NET 平台上实现自己的语言。目前它处于开发的第一阶段,即包括构造编译器的两个前端模块 — 扫描器和解析器。本文将对其技术稍作概述,并着重关注用 Irony 实现解析器。你可以在 CodePlex上找到该项目

Irony 在编译器构造方面引入了许多规则创新。和广泛使用的解析器创建工具类似,Irony 也基于语法规范来产生出可工作的解析器。不同的是,Irony 并不采用独立的元语言来编码目标语法。反之, 在 Irony 中,语法元素被表示为 .NET 对象,并用 C# 使用类 BNF 的表达式对目标语法进行编码。另外,Irony 不会生成额外代码 — Irony 的 LALR 解析器使用源自 C# 语法的编码信息来控制解析过程。

示例
本文所附的下载包中包含了 Irony 的核心装配件、语法浏览器和一系列解析器样例:包括数学表达式的解析器、Schema、Python 和 Ruby 的语法简化版解析器。利用语法浏览器,你可以查看语法案例的分析数据,并利用代码例子来测试解析器。下图是语法浏览器解析语法树的场景:

如果你想体验一下 Irony 项目,你只需要:

  • 1. 下载并解压源代码
  • 2. 用 Visual Studio 打开 Irony_all.sln 方案文件
  • 3. 将 GrammarExplorer 项目设为启动项目
  • 4. 按 F5 创建并运行应用
  • 5. 当语法浏览器的窗口出现的时候,在顶部的组合框内选择”grammer”,并浏览语法数据
  • 6. 从 Irony.Samples\SourceSamples 文件夹中加载源代码示例
  • 7. 解析源代码并浏览输出的语法树

背景
许多新潮的应用都用了这样或者那样的解析技术。比如编译器的开发就是严重依赖于解析器的一个现实例子。其他例子还包括了脚本引擎、表达式计算器(evaluator)、源代码分析和重构工具、基于模板的代码生成、格式化和彩色显示、数据导入和转换工具等等。因此,对于开发者来说,拥有快速直接的方法来实现所需的解析器就很重要了。不过不幸的是,就目前技术而言实现解析器仍然是个重大挑战。

>>> 阅读全文

 

, , , ,