Chinaunix首页 | 论坛 | 博客
  • 博客访问: 984555
  • 博文数量: 150
  • 博客积分: 3017
  • 博客等级: 少校
  • 技术积分: 3829
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-19 14:40
个人简介

Now in Baidu WISE team

文章分类

全部博文(150)

文章存档

2014年(8)

2013年(31)

2012年(111)

分类: C/C++

2013-01-27 16:12:33

(作者码字辛苦,转载请以超链接形式注明出处)

最近在学习编译原理,于是准备自己动手写一个脚本语言。准备用一些文章记录其中遇到的问题和解决的方法。这些文章需要有一些编译原理,YACC, LEX的储备知识。关于这些知识,使用方面的部分可以简单的通过网络上的文章得到,更详细的原理,需要参考《编译原理》(龙书)。当然,后者不是必须的。推荐以下几个Link,可以获得一些基础的知识。


YACC和LEX快速入门:http://www.ibm.com/developerworks/cn/linux/sdk/lex

Lex和Yacc应用方法(二).再识Lex与Yacc  http://blog.csdn.net/liwei_cmg/article/details/1530999 

lex与yacc(二)计算器的实现 http://blog.csdn.net/ecbtnrt/article/details/6817937

Lex和Yacc使用教程(五).再识语法树 http://blog.csdn.net/liwei_cmg/article/details/1626129





通过一些练习,我们已经可以写出简单的程序。但是对于函数的定义和调用,相关的轻量点的源码并没有找到合适的文章和例子,所以花了很多时间去思考和尝试。 
为了简单起见,我们简化情况至如下: 
    1.函数内部只有简单的赋值语句,且赋值都为Integer
    2.函数没有参数列表 
问题的分析:
 1.如何处理函数的定义? 
    对于函数的定义,最大问题是在定义阶段,我们是不执行的。和计算器的程序相比,在计算器程序中我们一旦发现一个语句可以归约,就立即执行,计算。 
    例如 1*(2+3) 我们将最后一个括号进栈后,立即计算了2+3,并返回给1*expr。所以情况比较简单。 而对于函数的定义,我们需要将2+3这个子表达式,存储起来,并不立即进行计算。因为这个时候2+3很可能变成了a+b,具体计算的值就需要根据变量的值 所以,我们需要一个数据结构,来存放以后要执行的东西的信息,但是不进行计算。 
    一个天然的想法是,读取定义的时候,并不处理其内部行为,而是记录两个指针,一个指向函数定义的开始,一个指向函数定义的结尾,到调用该函数的时候,我们将yacc的指针移动到函数体的开始,再继续分析,直到函数结束,再跳回原来。也就是说相当于每次调用的时候,都将这个函数体复制一遍,替换当前读到的调用语句,类似c语言的宏。 这样就可以用类似计算器的简单逻辑来处理它。 但是,这种方法一方面yacc似乎并不支持,再次,每次调用都要重新做语法和词法解析,明显是低效率的。 
    所以我们需要一个数据结构,来存储函数的定义但是不执行。 接下来我们需要用到一些编译原理的知识。如果你了解Lisp,也是可以的。 对于一般的情况,我们在函数体中的语句分为两种。其语法表达式如下。
直接调用: callFunc();                             E    ->  funcname '(' ')' ';'
赋值语句: i = 2 + 4;                              E    -> varname '=' expr 
                                                            expr -> INTEGER '+ 'expr' ';' 
                                                                     | INTEGER ';'
其实我们发现,任意一个操作,其实都可以分解成4个部分, 目标, 操作符, 操作对象,返回值,即在(目标)和(操作对象)之间执行(操作符),并返回(返回值)


        对于一个简单的赋值语句 i = 1; 目标是i,操作符是'=',操作对象为 '1'。 在(i)和(1)之间执行(赋值)操作,并返回(空)
        对于一个直接的调用callFunc(),我们可以认为他没有目标和操作符,只有对象callFunc,也可以认为是 var = callFunc();,但是var并没有用,被丢弃了。 
        对于一个复杂的复制语句 i = 1 + 2, 我们可以划分成一个3层的树形结构,见下面的示意图。我们执行的时候,就是递归的调用, 对于 1+2,计算出1+2的值,返回给上一层,然后这个临时变量赋给expr,再执行赋值i=expr,完成。
       每个节点,每种操作都有相应的返回值和,对于加法,返回的就是表达式的值,对于赋值,语句,无需返回,也可以认为返回为NULL。如何返回,由操作符来定义
       题外话,所以Lisp可以用无限的括号表示所有的计算,就是这个道理。这个赋值语句,可以用LISP表达为: (= i (+ 1 2))
       返回 : NULL
       目标:i 
       操作符: ‘=’
       操作对象:        ------------------》  返回: calculate result 

                                                     目标: 1 

                                                     操作符: +

                                                     操作对象:                 -----------------》返回:intValue

                                                                                                         目标: 2 

                                                                                                         操作符: NONE 

                                                                                                         操作对象: NULL 

所以有如下的结构定义: 

          typedef enum {NONE, Assign, Add} opEnum;  

          typedef struct { 

                     char* varName; //如果是目标是变量,我们用varName表示 

                     int intValue; //如果是目标是值,我们用intValue表示 

                     Node* child; //操作对象

                     opEnum op; //操作符,假设我们只支持‘=’和+

                     int ret; 

          }Node

定义了3种操作: 

      NONE:直接返回int,

      Assign:赋值,返回0xffff

      Add:加法,返回计算结果

这里 intValue和varName可以用Union来写. 因为我们赋值语句可能为i = 1+2, 也有可能i = 1+a

假设没有返回值的时候,ret为0xFFFF(仅为方便)

typedef enum {NONE, Assign, Add} opEnum;  我们用一个数组int VARLIST[65536],来存储所有变量(假定都为int,且每个变量名都可以通过getHash计算唯一的在VARLIST中的下标)


    这样我们可以定义对于一个结点的值,可以由如下递归方法计算:

int calNode(Node* node){

            if( node->op == Assign){

                      //update the variable

                      VARLIST[ getHash(node->varName) ] = calNode(node->child);

                      return 0xFFFF;

            }

            if( node->op == Add){

                      if(charName!=NULL) //如果是变量的加法,如 a+2 

                               return VARLIST[getHash(node->varName)] + calNode(node->child);

                      else //如果是直接可计算出的表达式 如 1+2 

                               return node->intValue+ calNode;

            }

            if( node->op == None)

                     return node->intValue;

}

其实,这本质上又回归到了LISP的表达式,通过递归的方式计算出一个语句的值。这是一种通用描述,对于每种语句,其实最终都可以通过以上的形式的方式计算,各种循环和判断无一例外。 也因此,LISP可以用极简的方式表达出现代高级编程语言各种复杂的语法。


2.怎样存储的函数的定义?

我们可以定一个全局的哈希表,key为函数的名字,value为Node*, 这样我们就可以将每一个函数存储在我们表中。

这里,我们简化情况,只定义一个Node*,也就是假设我们只有一个函数。


3.怎样调用函数?

由于我们已经存储了函数的定义,所以调用时,只需要从方法列表中,找出这个函数的item,利用calNode就可以执行这个节点代表的语句序列了


以上我们实际已经处理了要自己实现一个语言中的基础性结构,在这种逻辑的基础上增加完成度和优化,最终应该可以完成一个基本完整的解释器。


Sample Code:

将下面的6个文件放入文件夹中,然后执行shell文件refresh.

这个例子仅为示例,所以只完成了最简单的逻辑。未实现变量哈希表,函数哈希表。仅用一个函数指针记录唯一的一个函数。仅示意了赋值语句的执行。因为并没有记录变量表,也只是输出到屏幕,表示该函数被执行了。

调用函数时使用关键字 run

ryt.l


%{
#include "y.tab.c"  
#include 
#include 

void debugmsg(char* msg);
%}

delim       [ \\t\\n]
ws          {delim}+
letter      [A-Za-z]
digit       [0-9]
assign      =
id          {letter}({letter}|{digit})*
strvalue    \\"({letter}|{digit})*\\"
end         ;
number      {digit}+
fun         fun

%%
{ws}              {}
"run"             { debugmsg("RUN"); return RUN;}
"fun"             { debugmsg("FUN"); return FUN;}
{id}              { yylval.str = strdup(yytext); debugmsg(yytext); return symbol; }
{assign}          { return ASSIGN; }
{end}             { return SEMC;}
{number}          { yylval.value = atoi(yytext); debugmsg(yytext); return expr;}
"{"               { return BLOCKSTART;}
"}"               { return BLOCKEND;}
%%
int yywrap()
{
		return 1;
}
void debugmsg(char* msg){
	if(DEBUG & LEXDEBUG)
		printf("[LEX] %s\\n", msg);
}


ryt.y


%{
#include 
#include 
#include 
#include "utility.h"
Node* fun;

Node* installSymbolAssign(char* id, int exp);
Node* installFun(char* id, Node* body);
void  execute();
void  yyerror(char*s);

void* cur = NULL;
char* curFun;

%}
%union {
	char* str;
	int value;
	Node *node; /* 结点地址 */
}
%token  ASSIGN  SEMC FUN BLOCKSTART BLOCKEND RUN
%token   symbol
%token  expr
%type   stmtlist stmt
%%
prog           :   fundefs RUN SEMC                                       { execute();}
               ;

fundefs        :   fundefs fundef
               |   fundef 
			   ;

fundef         :   FUN symbol BLOCKSTART stmtlist BLOCKEND                {printf("fundef: %s \\n", $2);
installFun($2, $4);
}
               ;
stmtlist       :   stmtlist stmt                                          {printf("!!!!!!!statement\\n");}

               |   stmt                                                   {}
               ;
stmt           :       symbol ASSIGN expr SEMC                                {printf("add stmt: ASSIGN %s \\n", $1);
                                                                           $$ = installSymbolAssign($1,$3);
			   }
			   ;
%%

Node* installSymbolAssign(char* id, int exp){
	printf("install symbol assign --- %s %d\\n", id, exp);
	Node* ret = (Node*)malloc(sizeof(Node));
	ret->varName = id;
	ret->op = AssignOp;
	ret->intValue = exp;
	return ret;
}
Node* installFun(char* id, Node* body){
	printf("install function  %s: %p\\n", id, body);
	fun = body;
}
void execute(){
	printf("Executing function %p \\n", fun);
	ex(fun);
}
int main(){
	yyparse();
	return 0;
}
void yyerror(char *s) { 
	    fprintf(stdout, "%s\\n", s); 
}


utility.h


/*
 * =====================================================================================
 *
 *       Filename:  utility.h
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  01/26/2013 12:26:49 AM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  royn.wang.renyuan@gmail.com (), 
 *   Organization:  
 *
 * =====================================================================================
 */
#define LEXDEBUG 1
#define YACCDEBUG 2
typedef enum {NONE, AssignOp, AddOp} opEnum;
typedef struct _exUnit{
	char* varName;
	int intValue;
	opEnum op;
	struct _exUnit* next;
	int ret;
} Node;
int VarList[256];
int DEBUG;

int ex(Node* node);


utility.c


/*
 * =====================================================================================
 *
 *       Filename:  utility.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  01/26/2013 01:58:35 AM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  royn.wang.renyuan@gmail.com (), 
 *   Organization:  
 *
 * =====================================================================================
 */
#include 
#include 
#include "utility.h"

int DEBUG = 3;
int ex(Node* node){
	printf("[3] Calculating ");
	if(node->op == AssignOp){
		printf("ASSIGN ");
		printf("SYMBOL::%s value:: %d\\n",node->varName, node->intValue);
		return 0xFFFF;
	}
	if(node->op == AddOp){
		return node->intValue + ex(node);
	}
	if(node->next != NULL)
		ex(node->next);
}


refresh


echo "refresh yac ... ..."
yacc ryt.y
echo "refresh lex ... ..."
flex ryt.l
echo "re-compile lex.yy.c:   gcc lex.yy.c -o parser -ll"
gcc lex.yy.c utility.c -o parser -ll


echo "try parse test.ryl"
./parser



test.ryl

fun test{ var=12;} 
run;

参考链接:

阅读(4575) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~