2013年(24)
分类: C/C++
2013-03-02 10:36:55
原文地址:自己动手写解释器(1):函数定义和调用 作者:runningdark
(作者码字辛苦,转载请以超链接形式注明出处)
最近在学习编译原理,于是准备自己动手写一个脚本语言。准备用一些文章记录其中遇到的问题和解决的方法。这些文章需要有一些编译原理,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
对于一个简单的赋值语句 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;
参考链接: