yacc语法分析与lex词法分析相结合

语法

对于某些应用,我们所完成的简单的词类识别也许足够用了;而另一些应用需要识别特殊的标记序列并执行适当的动作。传统上,对这样的一套动作描述成为语法。

使用右箭头”->”意味着可以用一个新的符号取代一套特殊的标记。

例如:

subject ->noun\pronoun 指示一个新的符号subject是名词或代词。

词法分析程序和语法分析程序的通信

当一起使用lex扫描程序和yacc语法分析程序的时候,语法分析程序是比较高级别的例程。当他需要来自输入的标记时,就调用词法分析程序yylex()。然后,词法分析程序从头到尾扫描输入识别标记。他一找到对u语法分析程序有意义的标记就返回到语法分析程序,将返回标记的代码作为yylex()的值。

词法分析程序和语法分析程序必须对标记代码的内容达成一致。通过让yacc定义标记代码来解决这个问题。在我们的语法中,标记的词性是:NOUN,PRONOUN,VERB,ADVERB,ADJECTIVE,PREPOSITION和CONJUNCTION。yacc使用预处理程序#define将他们每一个都定义为小的整数。下面是一个示例:


#define NOUN 257
#define PRONOUN 258
#define VERB 258
#define ADVERB 260
#define ADJECTIVE 261
#define PREPOSITION 262
#define CONJUNCTION  263

输入的逻辑结束总是返回标记代码零。

下面这一段程序展示了新的词法分析程序的声明和规则段

名称为:forth.lex

%{

/*
* 我们现在构建一个由高级语法分析程序使用的词法分析程序
*/

#include "y.tab.h"  /* 来自语法分析程序的标记代码 */
#define LOOKUP 0 /* 默认情况 - 不是一个定义的单词类型 */

int state;

%}

%%

\n { state = LOOKUP; }

\.\n { state = LOOKUP;
    return 0; /* 句子结尾 */
}

^verb { state = VERB; }
^adj  { state = ADJECTIVE;  }
^adv  { state = ADVERB;  }
^noun { state = NOUN; }
^prep { state = PREPOSITION;}
^pron { state = PRONOUN; }
^conj { state = CONJUNCTION; }

[a-zA-Z]+ {
    if(state != LOOKUP){
            add_word(state,yytext);
    }else{

        switch(lookup_word(yytext)){
            case VERB:
                return(VERB);
            case ADJECTIVE:
                return(ADJECTIVE);
            case ADVERB:
                return(ADVERB);
            case NOUN:
                return(NOUN);
            case PRONOUN:
                return(PRONOUN);
            case CONJUNCTION:
                return(CONJUNCTION);
            default:
                printf("%s: do not recognize!\n",yytext); /* 不返回 忽略 */
        }
    }
}

. ;

%%

/* 定义一个单词和类型的链表 */
struct word{
    char *word_name;
    int word_type;
    struct word *next;
};

struct word *word_list; /* 单词链表中的第一个元素 */
extern void *malloc();

int add_word(int type,char *word)
{
    struct word *wp;
    if(lookup_word(word) != LOOKUP){
        printf("!! warning: word %s already defined\n",word);
        return 0;
    }

    /* 单词不在那里,分配一个新的条目并将它链接到链表上 */
    wp = (struct word *)malloc(sizeof(struct word));
    wp->next = word_list;

    /* 还必须复制单词本身 */
    wp->word_name = (char *)malloc(strlen(word)+1);
    strcpy(wp->word_name,word);
    wp->word_type = type;
    word_list = wp;

    return 1;  /* 添加成功 */
}

int lookup_word(char *word)
{
    struct word *wp = word_list;

    /* 向下搜索列表以寻找单词 */
    for(;wp;wp = wp->next){
        if(strcmp(wp->word_name,word) == 0)
            return wp->word_type;
    }

    return LOOKUP;
}

int yywrap()
{
    return 1;
}

他和之前的词法分析程序有一下几个区别:

1:词法分析程序中使用的词性名字改变为与词法分析程序中的标记名字相一致

2:添加return语句将所识别的单词的标记代码传递给语法分析程序

3:词法分析程序中定义新单词的标记没有任何return语句,因为语法分析程序不“关心”它们。

其中,返回语句表明yylex()操作类似与协同程序。每次语法分析程序调用他时,

都在他停止的那一点进行处理。这样就允许我们渐进地检查和操作输入流。

同时还增加了一条规则来标记句子的结尾:

\.\n { state = LOOKUP;
          return 0; /* 句子结尾 */
}

句号前面的反斜杠引用这个句号,所以这条规则与后跟一个换行的句号匹配。对词法分析程序所做的另一个改变是省略

目前语法分析程序中提供的main()例程。

yacc语法分析程序

下面程序介绍了yacc语法中的第一步。

forth.y

%{
/*
 * 用于识别英文句子基本语法的词法分析程序
 */
#include <stdio.h>

%}

%token NOUN PRONOUN VERB ADVERB ADJECTIVE PREPOSITION CONJUNCTION 

%%

sentence: subject VERB object { printf("Sentence is valid.\n"); }
    ;

subject:    NOUN
    |   PRONOUN
    ;

object: NOUN
    ;

%%

extern FILE *yyin;

int main()
{
    yyparse();
    while(!feof(yyin)){
        yyparse();
    }

    return 0;
}   

yyerror(s)
char *s;
{
    fprintf(stderr,"%s\n",s);
}

yacc语法分析程序的结构类似与lex语法分析程序的结构。使用%{ %}括起的部分为定一段,使用%% %%括起的为规则段,%token 表示采用8个标记,通常规定标记名都用大写字母,而语法分析程序中的其他名字大部分或完全是小写字母.

最终要的子程序main(),重复调用yyparse()函数知道词法分析程序的输入文件结束,例程yyparse()是由yacc生成的语法分析程序,当词法分析程序看到行的结尾处的句号时返回零标记,表示当前分析的输入已经完成。

规则段将实际语法描述为一套产生式规则或简称为规则。每条规则由符号”:”操作符左侧的一个名字,右侧的符号列表和动作代码以及指示规则结尾的分号组成。默认情况下,第一条规则是最高级别的规则。典型的简单的规则的右侧有一个符号。规则左侧的符号在其他规则中能像标记一样使用。

语法中使用特殊字符”|”,表示或;规则的动作部分由C块组成,以”{}”括起。因为sentence是最高层的符号,所以整个输入必须匹配sentence。当词法分析程序报告输入结束的时候,分析程序返回到他的调用程序。在该情况下就是主程序。随后对yyparse()的调用重置状态并再次开始处理。如果看到输入标记的”subject VERB object”列表,则示例打印一条消息,如果不匹配的话,则会调用yyerror()函数,识别特殊的规则error。可以提供错误恢复代码。尝试将分析程序返回到能够继续分析的状态。如果错误恢复失败,即没有错误恢复代码,yyparse()在发现错误后,返回调用程序。

下面是我们的Makefile程序:

all:
    lex forth.lex
    yacc -d forth.y
    gcc -c lex.yy.c y.tab.c
    gcc -o hello lex.yy.o y.tab.o -ll

clean:
    rm lex.yy.o y.tab.o lex.yy.c y.tab.c y.tab.h hello

下面我们使用命令:

make

对文件进行编译,编译完成之后,我们运行该程序:

./hello

程序的输出结果,我们来测试一下,运行之后,我们输入下面的语句:

verb are
noun you man
you are man

下面是程序的输出结果:

下面一篇博客将会使用该代码进行扩展,扩展一个简单的小学英语语法分析程序。

时间: 2024-08-29 06:24:06

yacc语法分析与lex词法分析相结合的相关文章

软件构造—— 实验二 lex词法分析

实验题目: 拷贝一个C文件,将其中的关键字int替换成float. 代码: 1 %{ 2 %} 3 %% 4 //表示如果是在双引号(")中(即为字符串),则照常打印,编译时请删除此注 5 \".*\" {printf("%s",yytext);} 6 [^ \t\n]+ {printf("%s",yytext);} 7 //表示如果遇到float,且附加模式是后面跟有空白符,则将float替换为double,编译时请删除此注释 8 i

Yacc 与 Lex 快速入门(词法分析和语法分析)

我们知道,高级语言,一般的如c,Java等是不能直接运行的,它们需要经过编译成机器认识的语言.即编译器的工作. 编译器工作流程:词法分析.语法分析.语义分析.IR(中间代码,intermediate Representation)产生.IR优化.代码产生.最终优化: 我们这里主要介绍的是语法分析: Lex 代表 Lexical Analyzar.Yacc 代表 Yet Another Compiler Compiler. 让我们从 Lex 开始吧. Lex Lex 是一种生成扫描器的工具.扫描器

Yacc 与 Lex 快速入门

Yacc 与 Lex 快速入门 Lex 与 Yacc 介绍 Lex 和 Yacc 是 UNIX 两个非常重要的.功能强大的工具.事实上,如果你熟练掌握 Lex 和 Yacc 的话,它们的强大功能使创建 FORTRAN 和 C 的编译器如同儿戏.Ashish Bansal 为您详细的讨论了编写自己的语言和编译器所用到的这两种工具,包括常规表达式.声明.匹配模式.变量.Yacc 语法和解析器代码.最后,他解释了怎样把 Lex 和 Yacc 结合起来. 5 评论 Ashish Bansal ([ema

Lex与Yacc学习(三)之符号表

符号表 列举单词表的方式虽然简单但是不全面,如果在词法分析程序运行时可以构建一个单词表,那么就可以在添加新的单词时不用修改词法分析程序. 下面示例便利用符号表实现,即在词法分析程序运行时从输入文件中读取声明的单词时允许动态的声明单词.声明以词性的名字开始,后面跟着要声明的单词. 添加符号表可以完全的改变词法分析程序,不必在词法分析程为每个要匹配的单词放置独立的模式,只要有一个匹配任意单词的模式,再查阅符号表就能决定所找到的词性. lex程序ch1-04.l %{ /* *带符号表的词法分析程序

自己动手写编译器之TINY编译器词法分析

TINY是<编译原理与实践>一书中介绍的教学编程语言,该语言缺少真正程序设计语言的主要特征,但足以例证编译器的主要特征了.本文将介绍该编译器的实现过程,完整的实现代码loucomp_linux中,供编译原理初学者参考. 小试牛刀: 下载源码后,进入loucomp_linux, 在命令行输入 $make 便生成tiny程序,然后输入 $tiny sample.tny tiny 将sample.tny中的TINY源码生成tm指令.tm指令是TM虚拟机的汇编代码,TM虚拟机的源码在tm.c中,输入如

哈工大软件学院编译原理实验1——词法分析

这次实验被"过来人"们定位非常easy,实验内容例如以下: ----------------------------------------------------------------------------------- 对例如以下工作进行展开描写叙述 (1) 给出语言的词法规则描写叙述 · 标识符.keyword.整常数.字符常数.浮点常数 · 单界符:+,-,×,;,- · 双界符:/*,:=,>=,<=,!=,- · 凝视 (2) 针对这样的单词的状态转换图和程

编译原理-如何使用flex和yacc工具构造一个高级计算器

Flex工具的使用方法 Lex 是一种生成扫描器的工具. Lex是Unix环境下非常著名的工具,主要功能是生成一个扫描器(Scanner)的C源码. 扫描器是一种识别文本中的词汇模式的程序. 这些词汇模式(或者常规表达式)在一种特殊的句子结构中定义.一种匹配的常规表达式可能会包含相关的动作.这一动作可能还包括返回一个标记. 当 Lex 接收到文件或文本形式的输入时,它试图将文本与常规表达式进行匹配. 它一次读入一个输入字符,直到找到一个匹配的模式. 如果能够找到一个匹配的模式,Lex 就执行相关

Lua4.0 词法分析

在说语法分析之前,先说一下词法分析. 因为语法分析时会调用词法分析. 词法分析相关的文件为 llex.h,llex.c. 先来看一下词法分析的头文件. RESERVED 保留字枚举. Token 结构体,词法分析就是把输入源分析成一个个的 token. 这个词比较常见,不再翻译成中文,因为我也不知道它的准确的中文叫什么. LexState 结构体,词法分析状态机,记录当前词法分析的状态以及一些相关的环境. 下面的是几个词分析的方法,注意 lua 中的命名约定. lua*_ 开头的都是 lua 内

初识 lex

一.背景 从零开始学习下lex和yacc      1. 基础 lex只有状态和状态转换,没有栈,善于模式匹配:yacc能处理带栈的FSA(有限状态机),更适合更复杂的任务. 模式匹配原语 元字符 匹配说明 . 任意字符( 除了换行) \n 换行 * 0次或者多次重复前面的表达式 + 1次或者多次重复前面的表达式 ? 0次或者1次重复前面的表达式 ^ 行的开始 $ 行的结尾 a|b a or b (ab)+ 1次或者多次重复组ab [...] 任意一个出现的字符 一些匹配的例子 表达式 匹配说明