本文参考南京大学编译原理实验指导书!!NJU Complier

写在前面——熟悉一下你要编译的语言:c--!

如果你对指导书中c--文法列表式定义一头雾水,来看看陈老师的精简语法树!

实验环境的搭建

实验在linux(ubuntu)环境下进行,需要的工具有gcc、flex、bason。ubuntu的安装这里不做展开,后面三个工具的安装按照指导书操作即可。

词法分析器部分

根据实验指导书,理了一下各个文件or程序之间的依赖关系。粉色部分是你需要编写的。

关于yyin:flex定义的变量,指向标准输入流(键盘)或文件流。类型是FILE*。

关于yylex():从yyin指向的位置读取一个词法单元。

关于yytext:返回当前词法单元对应的词素。类型是char*,就是一个字符串。

关于yylen():就是strlen(yytext)。

如何编写Lexical.l?

根据指导书,这个文件有三个部分:

{defination} // 给正则表达式起别名,简化{rules}的表达

%%

{rules} // 当前词法单元匹配时,要执行的动作(c风格)

%%

{user subroutines} // 自定义部分

重点正则表达式的书写,即给你的测试文件所有可能出现的单词用正则表达式概括出来。在词法分析阶段,执行的动作就是打印种别码。测试文件可能出现的单词指导书中有总结,大部分都很简单。下面列举其中一些复杂的正则表达。

INT [0-9]|[1-9][0-9]*|0[0-7]*|0x[0-9a-fA-F]*

FLOAT ([0-9]+\.[0-9]+)|((([0-9]*\.[0-9]+)|([0-9]+\.))(E|e)(\+|\-)?[0-9]+)

rules部分样例:

{INT} { printf("INT\n");}

{FLOAT} { printf("FLOAT\n");}

大家自己写的时候多留意指导书中的 词法分析提示 标题下的内容,能避免踩很多坑!

如何编写main.c?

指导书上有,直接cv。

最终效果:

noname:Code>./scanner ../Test/test.cmm

TYPE

ID

LP

RP

LC

TYPE

ID

ASSIGNOP

INT

SEMP

TYPE

ID

ASSIGNOP

Error type A at Line 4: Mysterious characters '~'

ID

SEMP

RC

语法分析部分

关于函数yylex()在bison中的调用:yylex(在Bison yyparse 函数中调用)返回值可以理解分为两部分,一个是token本身(SEMI、TYPE、INT等), 另一个是与之一一对应的属性值yylval。因为我们要建立语法分析树,所以每个token都应该看作树的叶节点,所以yylval的属性值是树结点。我们需要自行设计(多叉)树结点的数据结构,以及后序建树(采用自底向上的方式)用到的插入算法、打印树用到的遍历算法。

关于yylval:类型是bison自定义的宏YYSTYPE。YYSTYPE是一个联合体,我们的程序中通过自定义这个宏,来声明所有语法、词法单元的属性。

// syntax.y //

%union{

struct MFT* node;

}

在设计树节点的数据结构时,有下两种参考:

struct node{

    char* name;

    ...

    int num_of_children;

    struct node* children[MAX];

}

struct node{

    char* name;

    ...

struct node* child; // node's first child

    struct node* brother; // node's next brother

}

考虑到程序健壮性,第一种方式子节点最大数目是常量,故采用第二种方式。

结点的数据结构除了指向它孩子和弟弟的指针外,还应该包括(line:当前节点所在行号、type:对不同类型的结点输出格式不同、name:当前节点的名称、val:当前节点的值)注意区分这里val的值和yylval的值,前者其实是yytext对应的字符串的实际意义,后者是一个token作为语法分析树的一个结点而拥有的值。比如0x34作为一个token,它的val是52, yylval是struct node。以下给出多叉树的头文件供参考。

#ifndef MFT_H

#define MFT_H

#include

#include

#include

#include

enum TYPE{

TOKEN_TYPE,

TOKEN_ID,

TOKEN_INT,

TOKEN_FLOAT,

TOKEN_OTHERS,

NOT_A_TOKEN,

NODE_NULL

};

struct MFT{

int line; //number of line where the node is located

char* name; // name of node

enum TYPE type; // type of node(terminator or non-terminator)(for printer)

union{ // value of node(if they have)

int i;

float f;

char* id;

};

struct MFT* child; // the first child of the node

struct MFT* brother; // the next brother of the node

};

int convert2binary(char* text);

// insert node(has children)

struct MFT* addNode(int line, char* name, enum TYPE type, int argc, ...);

// insert leaf(has not children)

struct MFT* addLeaf(int line, char* name, enum TYPE type, char* val);

// release memory

void release(struct MFT* root);

void preorder(struct MFT* root, int layer);

#endif

syntax.y文件的编写:

分三个部分:

抄写指导书的产生式,并指明相应动作(建树)。

对于有二义性的部分,bison给出的默认动作是:移入规约冲突先规约,规约规约冲突先选择排在前面的产生式。为使语法正确分析,通过指定算符优先级和结合性,改变bison的默认动作。对本实验而言,主要有两部分需要你来指定(详见指导书)

if else 的匹配

算术表达式的运算符号的匹配

错误恢复处理

TODO这部分还没学明白TAT,等过几天不忙了再补。

最后是一个令人头疼的问题是,这几个文件相互调用,头文件引用不当会使声明重复。善用ifndef。

给出各文件程序的依赖关系:

最终效果:就像指导书一样啦;)

推荐文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。