c语言函数指针做解释器,自己动手写解释器(1):函数定义和调用
(作者码字辛苦,转载请以超链接形式注明出处)
最近在学习编译原理,于是准备自己动手写一个脚本语言。准备用一些文章记录其中遇到的问题和解决的方法。这些文章需要有一些编译原理,YACC, LEX的储备知识。关于这些知识,使用方面的部分可以简单的通过网络上的文章得到,更详细的原理,需要参考《编译原理》(龙书)。当然,后者不是必须的。推荐以下几个Link,可以获得一些基础的知识。
通过一些练习,我们已经可以写出简单的程序。但是对于函数的定义和调用,相关的轻量点的源码并没有找到合适的文章和例子,所以花了很多时间去思考和尝试。
为了简单起见,我们简化情况至如下:
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"
./parsertest.ryl
fun test{ var=12;}
run;
参考链接:
c语言函数指针做解释器,自己动手写解释器(1):函数定义和调用相关推荐
- C语言 函数指针做函数参数(即回调函数)
文章目录 函数指针做函数参数(回调函数) 回调函数概念 一般有三种调用方式 回调函数的作用 回调函数调用时刻 回调函数的语法: 1.简单的函数类型为:无参数.无返回值的函数. 2.完全形式的回调函数 ...
- C++基础8【难】 回顾:数组指针,函数指针,函数指针做函数参数 C语言多态
1,数组指针语法梳理 回顾,如何定义数组数据类型: 回顾,如何定义指针类型数组: 回顾,如何直接定义 一个指向数组类型的指针: 2,函数指针语法梳理 1)如何定义一个函数类型 2)如何定义一个函数指针 ...
- 函数指针做函数参数 使用总结及其意义
目录 1.函数指针 2.函数指针做函数参数 3.函数指针做函数参数在实际项目中的运用. 第一种 正向调用 第一步 Load DLL 第二步 声明函数指针类型 第三步 实现函数的调用. 完整代码如下: ...
- 函数指针--Nginx和Redis中两种回调函数写法
1.Nginx和Redis中两种回调函数写法 #include <stdio.h>//仿Nginx风格 //结构外声明函数指针类型 typedef void (*ngx_connectio ...
- 函数指针及其的运用(上)——何为函数指针
=========================引子========================= 我们都知道,数组名就是指向数组第一个元素的常量指针(详见<数组拾遗>).同理,对于 ...
- 自己动手写一个strlen()函数
strlen()函数: strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回 ...
- c语言二重指针做参数,C语言二重指针的运用
『摘要』本文主要通过实例展示C/C++中二重指针的用法和用途,对于诸如二叉树等递归定义的数据结构有一定的指导作用. [关键字]:C/C++.二重指针.递归 本人最近想实现一个B+树,虽然对B+树的理论 ...
- (十三)函数指针做函数形参
#include <stdio.h> #include <math.h> void fun1(int para1,void (*pfun)(int a),int b)//b为p ...
- RCC——系统时钟函数分析时钟的配置流程和自己动手写时钟配置函数配置时钟,实现超频
下面是SystemInit(void)函数的源代码:重点和时钟配置有关的是SetSysClock()函数 void SystemInit (void) {/* Reset the RCC clock ...
最新文章
- python连接mysql
- Elasticsearch 2.2.0 索引配置详解
- python的raw_ input是什么意思-对python中raw_input()和input()的用法详解
- 通过operator部署redis集群(ucloud版)
- 转再次写给我们这些浮躁的程序员
- 纪中B组模拟赛总结(2020.2.7)
- Silverlight 5 Beta新特性[4]文本缩进控制
- php守护进程热更新,服务器编程--守护进程
- 图解算法系列笔记(二)
- 阿里开发规范_字字珠玑,高级技术专家带你了解阿里的开发流程规范
- 软件测试跟踪工具Bugzilla的安装 - Linux版本
- 7-4 用天平找小球 (10 分)
- 【51单片机】通过定时器中断 在8位数码管显示时间
- PWM、PPM、SBUS、DSM2这四种协议到底是什么鬼?
- idea中module项目没有蓝色小方块问题
- 梦幻西游代理途径有哪些?需要注意什么
- Unity(入门、中级、中高级、高级)
- 剑与家园服务器信息,《剑与家园》合服规则公示
- 计算机专业顶级学术会议
- Mybatis的collection和association
热门文章
- 比特序列的XOR运算
- Python+OpenCV3.3图像处理视频教程 贾志刚 代码笔记3
- 《linux iptables使用入门》(转载)
- deep_learning_初学neural network
- 【面试】Java 反射机制(常见面试题)
- 2019上海大学计算机考研群,上海大学2019年考研复试分数线已公布
- 【039】SylixOS支持多核RISC-V处理器
- [数学竞赛] 历年题回顾
- 新浪哪些信息微博服务器被覆盖,新浪微博自动发布评论9.8(新浪微博助手)黄金版...
- 阿拉伯数字转中文数字 数字转换