http://blog.csdn.net/cxsjabcabc/article/details/7653020#
环境:[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
arglist.c:
- #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;
- }
- }
buf_to_arglist.h:
- #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
buf_to_arglist.c:
- #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;
- }
另外,common.h:
- #ifndef CCSH_COMMON_H
- #define CCSH_COMMON_H
- #include <stdlib.h>
- #include <stdbool.h>
- #include <string.h>
- #endif
error.h:
- #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
error.c:
- #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;
- }
A: 文件中函数前面的cc是什么?
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
interl_main.c:
- #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
tip_info.c:
- #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
echo.c:
- #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
version.c:
- #include <stdio.h>
- #include "version.h"
- void cc_show_version()
- {
- printf("ccteam shell 1.0\n");
- }
修改后的cc_process_string函数如下:
- 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;
- }
上面对于不同输入的具体处理,有一些已经分离出去了。工程保存为ccsh,
运行结果:
A: 不过,对于一个类似bash或者python解释器,上面做的仅仅是很初步的功能,对于复杂语法解析还没有涉及;但可以肯定的是,如果继续去扩展,这只是时间的问题。