作者:陈曦
日期:2012-6-12 11:31:12
环境:[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2]
转载请注明出处
Q: 解释器来源于什么?
A: 如果说是广义的解释器,那么可以把它理解成翻译器,只要能将一种被看成原始的东西翻译成需要的东西,处理的东西就可以被称为解释器。从编程语言角度,解释器更多地表达的含义是,将一种初始状态的数据(一般是文本)转换成另外一种通常来说是较为容易理解的文本或者一种执行过程。
解释器正来源于自然语言的机器不容易理解性。
Q: 我们尝试做个c语言解释器吧。
A: 罗马也不是一日建成的。我们先做个简单的,这个简单的解释器简单地让我们觉得可以不用做它,但是,我们还是要做它。
它主要实现以下几个简单的功能:
解释器名称为simple_interpreter, 命令行下执行它将运行解释器;
1、输入hello后,它会提示文本: hello, i am a interpreter!
2、输入ver后,它会提示文本: version: 1.0
3、输入print [字符串], 它会输出对应的字符串,字符串不需要任何分界符
4、输入exit或者quit后,解释器将关闭
5、如果输入其它命令,输出no such command
Q: 下面的代码是根据上面的需求做出的。
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "str_process.h" int main (int argc, const char * argv[]) { char buf[4096] = {0}; size_t buf_size = sizeof(buf); char* ret; // input from stdin while (1) { ret = fgets((char *)&buf, (int)buf_size, stdin); if(ret == NULL) // error or eof { if(ferror(stdin)) printf("error occurs...terminating now...\n"); else if(feof(stdin)) printf("eof occurs...terminating now...\n"); } else // input sth { // set the last '\n' to NULL if(buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; if(!strcmp(buf, "hello")) { printf("hello, i am a interpreter!\n"); } else if(!strcmp(buf, "ver")) { printf("version:1.0\n"); } else if(!strncmp(buf, "print", strlen("print"))) { char* temp = buf + strlen("print"); if(*temp == ' ') { cc_skip_blank(&temp); printf("%s\n", temp); } else if(*temp == '\0') { printf("\n"); } else { printf("no such command\n"); } } else if(!strcmp(buf, "exit") || !strcmp(buf, "quit")) { exit(0); } else { printf("no such command\n"); } } } return 0; }
保存为simple_interpreter.c;
str_process.h和str_process.c代码如下:
#ifndef CCSH_STR_PROCESS_H #define CCSH_STR_PROCESS_H #include <stdbool.h> bool cc_is_blank(char ch); char *cc_get_next_blank(const char *str); void cc_skip_blank(char **str); bool cc_str_is(const char *str1, const char *str2); bool cc_str_begin_with(const char *str, char ch); #endif
#include <stdio.h> #include "str_process.h" #include <string.h> inline bool cc_is_blank(char ch) { return ch == '\n' || ch == '\t' || ch == ' '; } char *cc_get_next_blank(const char *str) { while (!cc_is_blank(*str) && *str != '\0') ++str; return (char *)str; } void cc_skip_blank(char **str) { while(cc_is_blank(**str) && *str != '\0') { (*str)++; } } bool cc_str_is(const char *str1, const char *str2) { return strcmp(str1, str2) == 0; } inline bool cc_str_begin_with(const char *str, char ch) { return str[0] == ch; }
工程生成simple_interpreter, 运行:
A: 是的,上面的代码可以按要求执行。不过,它有它的缺点,一是格式固定的太死,如果多输入一个空格,可能造成no such command的错误; 二是它进行不同输入处理的代码过为集中,如果再增加一些,代码维护可能有很大的问题;三是上面的输入输出没有一个标志,很容易混淆。
Q: 如果需要解决第一个问题,那么需要对输入的文本进行初步解析,去除空格、TAB等信息,留下有用的信息;如果对第二个问题,可以用不同文件的单独函数来处理不同的条件分支;第三个问题,增加一个提示符,类似$.
A: 对于第一个问题,可采用如下的图示:
Q: 将缓冲区数据转换成参数列表,代码如下:
arglist.h:
#ifndef CCSH_ARGLIST_H #define CCSH_ARGLIST_H typedef struct _cc_arg_obj { char *str; size_t len; struct _cc_arg_obj *next; char *buf_pointer; // the pointer that points the buf, for possible use, eg. echo command }cc_arg_obj; typedef struct _cc_arg_list { cc_arg_obj *head; cc_arg_obj *tail; }cc_arg_list; cc_arg_obj *cc_arg_obj_make(const char *str, size_t len, cc_arg_obj *next, char *buf_pointer); void cc_arg_obj_free(cc_arg_obj *obj); cc_arg_list *cc_arg_list_make(cc_arg_obj *head); cc_arg_obj *cc_arg_list_append(cc_arg_list *list, cc_arg_obj *obj); void cc_arg_list_free(cc_arg_list *list); void cc_arg_list_show_all_args(cc_arg_list *list); #endif
#include <stdio.h> #include "arglist.h" #include "common.h" #include "error.h" cc_arg_obj *cc_arg_obj_make(const char *str, size_t len, cc_arg_obj *next, char *buf_pointer) { cc_arg_obj *obj = (cc_arg_obj *)malloc(sizeof(cc_arg_obj)); if(!obj) { cc_err(CC_ERR_NOMEM); return NULL; } char *obj_str = (char *)malloc(len + 1); if(!obj_str) { cc_err(CC_ERR_NOMEM); free(obj); return NULL; } strncpy(obj_str, str, len); obj->str = obj_str; obj->len = len; obj->next = next; obj->buf_pointer = buf_pointer; return obj; } void cc_arg_obj_free(cc_arg_obj *obj) { free(obj->str); free(obj); } cc_arg_list *cc_arg_list_make(cc_arg_obj *head) { cc_arg_list *list = (cc_arg_list *)malloc(sizeof(cc_arg_list)); if(!list) { cc_err(CC_ERR_NOMEM); return NULL; } list->head = list->tail = head; return list; } cc_arg_obj *cc_arg_list_append(cc_arg_list *list, cc_arg_obj *obj) { if(list->head == NULL) { list->head = list->tail = obj; return obj; } list->tail->next = obj; list->tail = obj; return obj; } void cc_arg_list_free(cc_arg_list *list) { cc_arg_obj *head = list->head; while(head) { cc_arg_obj *next = head->next; cc_arg_obj_free(head); head = next; } } void cc_arg_list_show_all_args(cc_arg_list *list) { cc_arg_obj *head = list->head; while (head != NULL) { printf("arg:%s", head->str); head = head->next; } }
#ifndef CCSH_BUF_TO_ARGLIST_H #define CCSH_BUF_TO_ARGLIST_H #include "arglist.h" cc_arg_list *cc_buf_to_arglist(const char *buf); #endif
#include <stdio.h> #include "buf_to_arglist.h" #include <stdlib.h> #include "error.h" #include "str_process.h" cc_arg_list *cc_buf_to_arglist(const char *buf) { char *temp = (char *)buf; cc_arg_list *list = cc_arg_list_make(NULL); if(!list) { cc_err(CC_ERR_NOMEM); return NULL; } while (*temp) { char *next_blank = cc_get_next_blank(temp); if(temp != next_blank) { size_t len = next_blank - temp; cc_arg_obj *obj = cc_arg_obj_make(temp, len, NULL, temp); if(!obj) { cc_err(CC_ERR_NOMEM); cc_arg_list_free(list); return NULL; } cc_arg_list_append(list, obj); } temp = next_blank; cc_skip_blank(&temp); } return list; }
#ifndef CCSH_COMMON_H #define CCSH_COMMON_H #include <stdlib.h> #include <stdbool.h> #include <string.h> #endif
#ifndef CCSH_ERROR_H #define CCSH_ERROR_H typedef enum { CC_OK, CC_ERR_NOMEM }CC_ERR; typedef struct { CC_ERR err_no; char *err_str; }cc_err_info; extern cc_err_info errs[]; // global error number extern int errno; void cc_err(CC_ERR err_no); #endif
#include <stdio.h> #include "error.h" cc_err_info errs[] = { { CC_OK, "no error"}, { CC_ERR_NOMEM, "no enough mem"} }; int errno; void cc_err(CC_ERR err_no) { printf("%s\n", errs[err_no].err_str); errno = CC_ERR_NOMEM; }
Q: 它是我的标志。
A: 那好吧。现在可以解决第二个问题了。
Q: 为了将不同的处理分离,下面首先把main函数的代码转移:
入口文件main.c:
#include <stdio.h> #include "internal_main.h" int main(int argc, const char * argv[]) { return cc_internal_main(argc, argv); }
internal_main.h:
#ifndef CCSH_INTERNAL_MAIN_H #define CCSH_INTERNAL_MAIN_H int cc_internal_main(int argc, const char *argv[]); static int cc_process_string(char *str); #endif
#include <stdio.h> #include "internal_main.h" #include <string.h> #include "buf_to_arglist.h" #include "error.h" #include "str_process.h" int cc_internal_main(int argc, const char *argv[]) { char buf[4096]; char *temp_buf = (char *)buf; repeat: cc_print_tipinfo(); memset(buf, 0, sizeof(buf)); temp_buf = fgets(buf, sizeof(buf)), stdin); if(temp_buf == NULL) { goto repeat; } else { cc_process_string(buf); goto repeat; } return 0; } static int cc_process_string(char *str) { if(str[0] == '\n') return 0; str[strlen(str) - 1] = '\0'; cc_arg_list *list = cc_buf_to_arglist(str); if(!list) { return errno; } // cc_arg_list_show_all_args(list); if(cc_str_is(list->head->str, "echo")) { cc_execute_echo(list, str); } cc_arg_list_free(list); return 0; }
tip_info.h:
#ifndef CCSH_TIP_INFO_H #define CCSH_TIP_INFO_H void cc_print_tipinfo(); #endif
#include <stdio.h> #include "tip_info.h" void cc_print_tipinfo() { printf("$"); }
echo.h:
#ifndef CCSH_ECHO_H #define CCSH_ECHO_H #include "arglist.h" int cc_execute_echo(cc_arg_list *arg_list, const char *buf); #endif
#include <stdio.h> #include "echo.h" #include "str_process.h" #include <string.h> int cc_execute_echo(cc_arg_list *arg_list, const char *buf) { cc_arg_obj *arg = arg_list->head->next; if(!arg) return 0; if(cc_str_begin_with(arg->str, '-')) { size_t len = strlen(arg->str); if(len != 2) { printf("%s\n", arg->buf_pointer); return 0; } else { if(arg->str[1] == 'n') { arg = arg->next; printf("%s", arg->buf_pointer); return 0; } else { printf("%s\n", arg->buf_pointer); return 0; } } } else { printf("%s\n", arg->buf_pointer); return 0; } return 0; }
A: 上面的代码是只处理了echo命令(它可以替代之前说的print命令),运行一下:
echo命令的-n参数表示不输出最后的换行。现在将之前的hello, ver和quit, exit命令都加上吧。
Q: version.h:
#ifndef CCSH_VERSION_H #define CCSH_VERSION_H void cc_show_version(); #endif
#include <stdio.h> #include "version.h" void cc_show_version() { printf("ccteam shell 1.0\n"); }
static int cc_process_string(char *str) { if(str[0] == '\n') return 0; str[strlen(str) - 1] = '\0'; cc_arg_list *list = cc_buf_to_arglist(str); if(!list) { return errno; } // cc_arg_list_show_all_args(list); if(cc_str_is(list->head->str, "echo")) // like print command { cc_execute_echo(list, str); } else if(cc_str_is(list->head->str, "hello")) { printf("hello, i am a interpreter!\n"); } else if(cc_str_is(list->head->str, "ver")) { cc_show_version(); } else if(cc_str_is(list->head->str, "quit") || cc_str_is(list->head->str, "exit")) { exit(0); } else { printf("no such command...\n"); } cc_arg_list_free(list); return 0; }
运行结果:
A: 不过,对于一个类似bash或者python解释器,上面做的仅仅是很初步的功能,对于复杂语法解析还没有涉及;但可以肯定的是,如果继续去扩展,这只是时间的问题。
作者:陈曦
日期:2012-6-12 11:31:12
环境:[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2]
转载请注明出处