要理解解释器,做一个小解释器----小话c语言(20)

作者:陈曦

日期: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

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解释器,上面做的仅仅是很初步的功能,对于复杂语法解析还没有涉及;但可以肯定的是,如果继续去扩展,这只是时间的问题。



作者:陈曦

日期:2012-6-12   11:31:12

环境:[Mac 10.7.1  Lion  Intel-based  x64  gcc4.2.1  xcode4.2]  

转载请注明出处



  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值