至于为什么要使用状态,我们来看一个实际的例子:上面分析头文件的 时候采用了两个自定义的状态:InCLUDE状态和INCLUDE FILE状态 NCLUDE状态是当遇到了#include开始的,因为这个状态之后是尖括号 或者是双引号括起来的头文件名,在后面分析模板(使用尖括号)和分 析字符串(使用双引号)的时候也会遇到尖括号和双引号,因而需要区 分这两种情况,所以才需要使用INCLUDE状态,以此来区分是包含文件 还是模板成若是字符串了。这一点非常重要! 同样,状态_INCLUDE_FILE存在也是为了区分双引号包含的头文件名称 的,因为双引 号不】 尖括号,双引号 在头文件名的开始和结 束都是村 同的,因此为了区分头部和尾部的双引号,必须再增加一个状态。实际 上这可以用来简化词法分析器的编写,当您遇到这种类似的问题的时候 可以考虑再增加一种新的状态,通常来说就可以解决问题啦:) 不时坏右一占特别云要漏周的是当你成微所添加的状态太多了,出现了混 乱现象,就说明用Lx状态已经不大适合处理这种问题 ,就应该考虑 采用Yacc的一条独立的语法规则来进行处理了:)这也是Yacc语法文件 存在的原因,要不然全部都可以采用词法分析文件来解决啦,还要语法 分析文件干什么! 递归表达 这里需要特别注意的是:frame..y文件中p 的形式。从代码中可以看出 orogram可以是 是由现有的program内容再追加一条include_preprocess类构成。当 program内容为空的时候增加一条include_preprocess类就表示 program只有一条include_preprocess内容,当program己经有了一条 includepreprocess内容之后再增加一条include preprocess内容就 可以表示两条in 了,依次类推, 可以表达无数的 包含信息,从而表达了无限的内容了。特别需要注意的是,这里的 program表示的仅仅是现有的内容,包括但不限于include_preprocess 内容,还可以有其他的内容,这一点可以在增加其他内容的时候体现出 来,因为C/C+源代码不仅仅是由包含信息构成的嘛:) 特别需要注意的是,这甲要特表强调一下使用左递归,不是说右弟归不行 而 是出于程序运行效率考虑最好使用左递归。具体原因在后续的文档中 会有详细的说明的:) YYSTYPE,yylval,$$$1,$2,...$n 因为编写词法分析程序和语法分析程序的目的就是为了操作分析出来的 数据,所以就需要有一种比较方便的形式来表达这些分析出来的数据。 一种是词法分析程序使用的方式,叫做yylval: 一种是语法分析程序付 用的, 川做n 法分析程序和语法分析程序中已经可以看到 它们在相应的文件中的使用了
至 于为什么要使用状态,我们来看一个实际的例子:上面分析头文件的 时候 采用了两个自定义的状态:_INCLUDE_状态和_INCLUDE_FILE_状态, _INCLUDE_状态是当遇到了#include 开始的,因为这个状态之后是尖括号 或者 是双引号括起来的头文件名,在后面分析模板(使用尖括号)和分 析字符串 (使用双引号)的时候也会遇到尖括号和双引号,因而需要区 分这两种情况 ,所以才需要使用_INCLUDE_状态,以此来区分是包含文件 还是模板或者是字 符串了。这一点非常重要! 同样,状态 _INCLUDE_FILE_存在也是为了区分双引号包含的头文件名称 的, 因为双引号不同于尖括号,双引号在头文件名的开始和结束都是相 同的,因 此为了区分头部和尾部的双引号,必须再增加一个状态。实际 上这可以用来 简化词法分析器的编写,当您遇到这种类似的问题的时候 可以考虑再增加一 种新的状态,通常来说就可以解决问题啦:) 不过还有一点特别需要强调的是当您感觉所添加的状态太多了,出现了混 乱 现象,就说明用 Lex 状态已经不大适合处理这种问题了,就应该考虑 采用 Yacc 的一条独立的语法规则来进行处理了:)这也是 Yacc 语法文件 存在的原因,要 不然全部都可以采用词法分析文件来解决啦,还要语法 分析文件干什么! 递归表达 这 里需要特别注意的是:frame.y 文件中 program 的构成采用了左递归 的形 式。从代码中可以看出:program 可以是空(什么也没有)也可以 是由现有 的 program 内容再追加一条 include_preprocess 类构成。当 program 内容为 空的时候增加一条 include_preprocess 类就表示 program 只有一条 include_preprocess 内容,当 program 已经有了一条 include_preprocess 内 容之后再增加一条 include_preprocess 内容就 可以表示两条 include_preprocess 内容了,依次类推,可以表达无数的 包含信息,从而 表达了无限的内容了。特别需要注意的是,这里的 program 表示的仅仅是现 有的内容,包括但不限于 include_preprocess 内容,还可以有其他的内容 ,这一点可以在增加其他内容的时候体现出 来,因为 C/C++源代码不仅仅是 由包含信息构成的嘛:) 特别需要注意的是,这里要特表强调一下使用左递归,不是说右递归不行, 而 是出于程序运行效率考虑最好使用左递归。具体原因在后续的文档中 会有详细 的说明的:) YYSTYPE, yylval, $$,$1,$2,...$n 因 为编写词法分析程序和语法分析程序的目的就是为了操作分析出来的 数据 ,所以就需要有一种比较方便的形式来表达这些分析出来的数据。 一种是词 法分析程序使用的方式,叫做 yylval;一种是语法分析程序使 用的,叫做$n ,从上面的词法分析程序和语法分析程序中已经可以看到 它们在相应的文件 中的使用了
至于YYSTYPE那就更简单了,因为要表达词法分析程序和语法分析程序中 的数据,既然是数据,在C/C+中就有数据类型的概念,这里的YYSTYPE 就是yylval和$n的数据类型。 特别需要注意的是,语法分析程序中每一个语法规则冒号左边的类的值用 $$表示,而目号右边的第一顶用$1表示,第二项用$2表示,依次类推。 标记和值 标记指的是由%token定义的INCLUDE和HEADERFILE,他们都对应着一个 具体值,而且具体值类型还有可能完全不一样。这里需要特别强调的一 个标记都对应着一个值,你可以不使用,但是他就是确确实 实在者,而从室终省这对应:止的 INCLUDE标记 的值就没有使用,但是HEADERFILE标记的值就被使用了。在Lex和Yacc 中标记都是用一个C宏定义的一个整数,而标记的值都是由YYSTYPE定 义者的一个变量,这个变量的名字就是yylval,其中保存着相关的信息, 这个信息就是在词法分析文件中进行设置的,而在语法分析文件中就直 接采用了。 实际上%token还可以更进一步的简化Yacc语法程序的编写 从而避免 些不必要的错误。从上面的语法分析来看,对于不同的$,还需要记 住$加的精确类型和变量名,这一点其实是不必要的,可以通过%token Cheaderfile>HEADERFILE来声明标记,那么在Yacc程序的语法规则中 就可以直接使用3来表示yylval.headerfile了, 从而不需要记往 那些具体变量名啦: 值得注意的是,尽管标记是可以用%toke来定义,但是并不仅仅限于这 种方式,Yacc中还可以用%type来定义,采用%type来定义的目的就是 为那些不是标记的类也准备一个对应的值的,例如:完全可以为 include preprocess定义一个值,用来保存一些额外的信息,不过本文 中并不需要,后续的文档中就会需要这个功能了,在此先简单说明一下:) 词法动作 对于词法分析程序中的每一个正则表达式对应的规则,都有相应的C/C+ 语句来做一些领外的处理,这个额外的处理就是词法动作。 语法动作 对千语法分析程序中的每一个语法规则,都有相应的C/C++语来做一 外的处理, 这个额外的处理就是语法动作 不过语法动作和词法动 作的不同之处在于,语法动作允许嵌入式的语法动作,而词法动作不行。 至于什么是嵌入式的语法动作,在后续的文档中会有详细的说明的!
至于 YYSTYPE 那就更简单了,因为要表达词法分析程序和语法分析程序中 的数据 ,既然是数据,在 C/C++中就有数据类型的概念,这里的 YYSTYPE 就是 yylval 和 $n 的数据类型。 特别需要注意的是,语法分析程序中每一个语法规则冒号左边的类的值用 $$表 示,而冒号右边的第一项用$1 表示,第二项用$2 表示,依次类推。 标记和值 标 记指的是由%token 定义的 INCLUDE 和 HEADERFILE,他们都对应着一个 具体 值,而且具体值类型还有可能完全不一样。这里需要特别强调的一 点是: 每一个标记都对应着一个值,你可以不使用,但是他就是确确实 实存在着 ,而且从始至终都保持着这种对应。例如上面的 INCLUDE 标记 的值就没有使 用,但是 HEADERFILE 标记的值就被使用了。在 Lex 和 Yacc 中标记都是用一个 C 宏定义的一个整数,而标记的值都是由 YYSTYPE 定 义着的一个变量,这个 变量的名字就是 yylval,其中保存着相关的信息, 这个信息就是在词法分 析文件中进行设置的,而在语法分析文件中就直 接采用了。 实 际上%token 还可以更进一步的简化 Yacc 语法程序的编写,从而避免 一些不 必要的错误。从上面的语法分析来看,对于不同的$n,还需要记 住$n 的精确 类型和变量名,这一点其实是不必要的,可以通过%token <headerfile> HEADERFILE 来声明标记,那么在 Yacc 程序的语法规则 中 就可以直接使用$3 来表示 yylval.headerfile 了,从而也就不需要记住 那 些具体变量名啦:) 值得注意的是,尽管标记 是可以用%token 来定义,但是并不仅仅限于这 种方 式,Yacc 中还可以用%type 来定义,采用%type 来定义的目的就是 为那些不是 标记的类也准备一个对应的值的,例如:完全可以为 include_preprocess 定 义一个值,用来保存一些额外的信息,不过本文 中并不需要,后续的文档中 就会需要这个功能了,在此先简单说明一下:) 词法动作 对于词法分析程序中的每一个正则表达式对应的规则,都有相应的 C/C++ 语句来做一些额外的处理,这个额外的处理就是词法动作。 语法动作 对 于语法分析程序中的每一个语法规则,都有相应的 C/C++语句来做一 些额 外的处理,这个额外的处理就是语法动作。不过语法动作和词法动 作的不同 之处在于,语法动作允许嵌入式的语法动作,而词法动作不行。 至于什么是 嵌入式的语法动作,在后续的文档中会有详细的说明的!
好了,本章中还残留有一些问题故意没有解决,例如:包含文件的续行问题!留 个读者自己思考,可以在本文所讨论的基础上稍微改动一下就可以了。后续的 文档正在努力写出,敬请关注:》
好了,本章中还残留有一些问题故意没有解决,例如:包含文件的续行问题!留 个 读者自己思考,可以在本文所讨论的基础上稍微改动一下就可以了。后续的 文档正 在努力写出,敬请关注;)
5、开发Lex和Yacc程序的一般步骤 摘要 经过前面章节的准备 到目前为止一个完整的C+应用框架已经完整的搭建 起来了。现在的事情就是考虑如何利用这个框架来实现自己的目的功能程序了 在这一章并不涉及到实际的开发而是先学习一下简单的理论知识。本章将会根 据我的个人开发经验来说明一下开发Lex和Yacc程序的一般开发步骤,这里的 内容也会随着后续的开发逐渐的完善起来,当在后续的开发中遇到不明白的地 方可以回到这一章来看一看,也许就明白了:) 1.一般步骤 就我在开发Lex和Yacc程序的经验来说,如果要解析一个文件,那么必须经 过下面的步骤才能够写出正确的语法分析文件和词法分析文件: .将被解析的文本进行分类。 2 由分类构成 将类及子类分解为标记 4.完成了上面的所有步骤之后,我们就已经将被解析文本分解成功了,剩下 的事情就是为保存这些分解出来的信息而准备好一个树形数据结构,在 这个时候使用C+的优势就显现出来了,我们可以借用STL里面的很多 容器和算法的 2.第一步:将被解析的文本分类 将被解析的文本进行分类,实际上是一件非常重要的事情,分类的好坏直接 影响到了后续的开发,分类分的好能够使得保存信息的数据结构简单明了和高 效,但是如果分得不好,可能导致冗余数据结构,大量得冗余信息以及需要全 部修改代码。 先举个实际的何 子吧:C/C+ 原代码。怎样把C/C+源代码中的语法元素用最 少并且能够保存全部信息的数据结构来表示,这本身就是一种挑战。在CC 的发展历程中也经历过大大小小的变动,这说明要想对C/C+源代码中的语法 元素进行分类是一件非常烦杂的事情,不过对于我们来说,这些语法元素已经 存在了好多年了,我们不需要从来开始,而是可以直接利用已经存在的知识来 加快我们的开发 C/C+的源代码中的每一个语法元素可以作为 一个 分类,例如:关键字、预处理 宏、函数、变量、语句、结构体、类、联合体等等
5、开发 Lex 和 Yacc 程序的一般步骤 摘要 经过前面章节的准备,到目前为止一个完整的 C++应用框架已经完整的搭建 起来了。现在的事情就是考虑如何利用这个框架来实现自己的目的功能程 序了。 在这一章并不涉及到实际的开发而是先学习一下简单的理论知识。 本章将会根 据我的个人开发经验来说明一下开发 Lex 和 Yacc 程序的一般开发 步骤,这里的 内容也会随着后续的开发逐渐的完善起来,当在后续的开发 中遇到不明白的地 方可以回到这一章来看一看,也许就明白了:) 1. 一般步骤 就我在开发 Lex 和 Yacc 程序的经验来说,如果要解析一个文件,那么必须经 过 下面的步骤才能够写出正确的语法分析文件和词法分析文件: 1. 将被解析的文本进行分类。 2. 由分类构成 program。 3. 将类及子类分解为标记。 4. 完成了上面的所有步骤之后,我们就已经将被解析文本分解成功了,剩下 的 事情就是为保存这些分解出来的信息而准备好一个树形数据结构,在 这个时 候使用 C++的优势就显现出来了,我们可以借用 STL 里面的很多 容器和算法的 。 2. 第一步:将被解析的文本分类 将被解析的文本进行分类,实际上是一件非常重要的事情,分类的好坏直接 影响 到了后续的开发,分类分的好能够使得保存信息的数据结构简单明了和高 效,但 是如果分得不好,可能导致冗余数据结构,大量得冗余信息以及需要全 部修改代 码。 先举个实际的例子吧:C/C++源代码。怎样把 C/C++源代码中的语法元素用最 少并 且能够保存全部信息的数据结构来表示,这本身就是一种挑战。在 C/C++ 的发展历 程中也经历过大大小小的变动,这说明要想对 C/C++源代码中的语法 元素进行分类 是一件非常烦杂的事情,不过对于我们来说,这些语法元素已经 存在了好多年了, 我们不需要从来开始,而是可以直接利用已经存在的知识来 加快我们的开发。 C/C++的源代码中的每一个语法元素可以作为一个分类,例如:关键字、预处理 宏 、函数、变量、语句、结构体、类、联合体等等
但是,并不是说这里的开发是一帆风顺的,在本类文档的后续章节中也许会 对这里的类容进行修正,这只能说明我们对于C/C+的语法构成还是存在误区 而不是C/C+语法本身有问题 对我们熟悉的问题尚且如此 那么对于我们不 熟悉的问题更加如此了,所以后续的开发中对前述代码的修正也就难免了,这 就是重构,使得代码越来越合理,越来越高效。 3.第二步:由分类构成program 对于上一步已经做好了的分类,最终的目的就是要用这些分类来成功的构成 最终的C/C+源代码。通过前面的章节的学习,我们知道这里的progra表达的 就是C/C+源代码内容,当然你也可以用你自己喜欢的任何名字,例如 “source”,这个就看个人的喜好了,Lex和Yacc对这个没有特别的要求。 这里我们采用program代表C/C+源代码。上面的C/C+分类:关键字、预 处理宏、函数、变量、语句、结构体、类、联合体等等。在这一步里面就是考虑 如何利用这些分类来枸成完整的C/C+源代码。 这一步也充满了挑战,但是相对 于第一步来说就容易多了,因为有据可寻啊。我们可以遵循Lex和Yacc规范来 一步一步的将上面的分类组合起来构成C/C++源代码。 常见的问题包括: 1.如何用有限的分类来构成无限数量的文本(C/C++原代码) 如何避免移进和规约冲突 3. 如何设置标记(tokens 上面的概念暂时只需要初步的了解,所谓的标记就是能够直接表示文本文档 (C/C+源代码)其中内容的概念,例如关键字类可以通过int,long,char等等 直接表示,那么int,long,char就是标记的值,标记就是代表这些值的一个标 志而己,在Lex和Yacc中就是用C宏来表示标记的例如用INT宏表示int,LONG 宏表示1 ,CHAR宏表示char等等,但是INT宏,LOG宏和CHAR宏并不是直 为 对应的int,long,char的 而仅仅只是 个整数 另外 还需要强调 的是这里的1T宏,LOG宏和CHR宏是自己定义的,而不是Lex和Yacc内置 的,因此可以随心所欲的定义,你完全可以用INT表示char,LONG表示int, CHAR表示1og等,但是这样做并不好。关于这里的概念的详细内容会在后续 的开发中进行详细的解释。 4.第三步:将类分解为标记 在对文本(C/C+源代码)进行了分类之后能否直接用标记表达出来呢? 部分分类可以用标记直接表达出来了,还有一部分就不能或者不容易用标记表 达出来。对于这些不能或者不容易用标记表达出来的分类就还需要细分,这样 最终的目的就是将所有的类都能够用标记表达出来
但是,并不是说这里的开发是一帆风顺的,在本类文档的后续章节中也许会 对这里 的类容进行修正,这只能说明我们对于 C/C++的语法构成还是存在误区 而不是 C/C++ 语法本身有问题。对我们熟悉的问题尚且如此,那么对于我们不 熟悉的问题更加如 此了,所以后续的开发中对前述代码的修正也就难免了,这 就是重构,使得代码越 来越合理,越来越高效。 3. 第二步:由分类构成 program 对于上一步已经做好了的分类,最终的目的就是要用这些分类来成功的构成 最终的 C/C++源代码。通过前面的章节的学习,我们知道这里的 program 表达的 就是 C/C++ 源代码内容,当然你也可以用你自己喜欢的任何名字,例如 “source”,这个就看 个人的喜好了,Lex 和 Yacc 对这个没有特别的要求。 这里我们采用 program 代表 C/C++源代码。上面的 C/C++分类:关键字、预 处理宏、函数、变量、语句、结构体、类、联合体等等。在这一步里面就是考虑 如何利用这些分类来构成完整的 C/C++源代码。这一步也充满了挑战,但是相对 于第一步 来说就容易多了,因为有据可寻啊。我们可以遵循 Lex 和 Yacc 规范来 一步一步的 将上面的分类组合起来构成 C/C++源代码。 常见的问题包括: 1. 如何用有限的分类来构成无限数量的文本(C/C++源代码) 2. 如何避免移进和规约冲突 3. 如何设置标记(tokens) 上面的概念暂时只需要初步的了解,所谓的标记就是能够直接表示文本文档 (C/C++ 源代码)其中内容的概念,例如关键字类可以通过 int,long,char 等等 直接表示,那 么 int,long,char 就是标记的值,标记就是代表这些值的一个标 志而已,在 Lex 和 Yacc 中就是用 C 宏来表示标记的例如用 INT 宏表示 int,LONG 宏表示 long,CHAR 宏表示 char 等等,但是 INT 宏,LONG 宏和 CHAR 宏并不是直 接定义为对应的 int,long,char 的,而仅仅只是一个整数;另外还需要强调 的是这里的 INT 宏,LONG 宏和 CHAR 宏是自 己定义的,而不是 Lex 和 Yacc 内置 的,因此可以随心所欲的定义,你完全可以用 INT 表示 char,LONG 表示 int, CHAR 表示 long 等,但是这样做并不好。关于这里的概念的 详细内容会在后续 的开发中进行详细的解释。 4. 第三步:将类分解为标记 在对文本(C/C++源代码)进行了分类之后能否直接用标记表达出来呢?一 部分分类 可以用标记直接表达出来了,还有一部分就不能或者不容易用标记表 达出来。对于 这些不能或者不容易用标记表达出来的分类就还需要细分,这样 最终的目的就是将 所有的类都能够用标记表达出来