Author: Chen Xi
Date: 11:31:12
Environment: [Mac 10.7.1 lion intel-based x64 gcc4.2.1 xcode4.2]
Reprinted please indicate the source
Q: What does the interpreter come from?
A: If it is a broad interpreter, you can understand it as a translator, as long as you can translate a thing that is regarded as original into what you need, processing can be called an interpreter. From the programming language perspective, the interpreter expresses more in the meaning that data in an initial state (generally text) conversion to another type of text or execution process that is generally easier to understand.
The interpreter is derived from natural language machines and is not easy to understand.
Q: Let's try a C language interpreter.
A: Rome was not built in one day. Let's make a simple one. This simple interpreter makes us feel that we don't need to do it, but we still need to do it.
It mainly implements the following simple functions:
The interpreter name is simple_interpreter. Execute it in the command line to run the interpreter;
1. After Entering hello, it will prompt the text: Hello, I am a interpreter!
2. After you enter the ver, it will prompt the text: Version: 1.0.
3. Input print [String], which outputs the corresponding string without any delimiters.
4. After you enter exit or quit, the interpreter will be disabled.
5. If other commands are input, no such command is output.
Q: The following Code is based on the above requirements.
#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;}
Save as simple_interpreter.c;
The str_process.h and str_process.c codes are as follows:
#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;}
The project generates simple_interpreter and runs:
A: Yes. The above code can be executed as required. However, it has its disadvantages. First, the format is too fixed. If you enter another space, it may cause the error of no such command; second, the codes for different input processing are too concentrated. If some code is added, code maintenance may be very problematic. Third, the input and output above does not have a flag, it is easy to confuse.
Q: If you need to solve the first problem, you need to parse the input text, remove spaces, tabs, and other information, and leave useful information. If you want to solve the second problem, you can use separate functions of different files to process different conditional branches. The third problem is to add a prompt, similar to $.
A: The following figure shows the first problem:
Q: Convert the buffer data to a parameter list. The Code is as follows:
Arglist. h:
#ifndef CCSH_ARGLIST_H#define CCSH_ARGLIST_Htypedef 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;}
In addition, 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_Htypedef 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 numberextern 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: What is CC before the function in the file?
Q: It is my logo.
A: Okay. Now we can solve the second problem.
Q: In order to separate different processes, the code of the main function is first transferred:
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_Hint 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;}
The third question is that the prompt is displayed:
Tip_info.h:
#ifndef CCSH_TIP_INFO_H#define CCSH_TIP_INFO_Hvoid 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: The above Code only processes the echo command (it can replace the print command mentioned earlier). Run:
The-N parameter of the ECHO command does not output the final line feed. Add the preceding hello, Ver, quit, and Exit commands.
Q: version. h:
#ifndef CCSH_VERSION_H#define CCSH_VERSION_Hvoid cc_show_version();#endif
Version. C:
#include <stdio.h>#include "version.h"void cc_show_version(){ printf("ccteam shell 1.0\n");}
The modified cc_process_string function is as follows:
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;}
The specific processing of different inputs has been separated. Save the project as ccsh,
Running result:
A: however, for a bash or Python interpreter, the above is just a very preliminary function, not involved in complex Syntax Parsing; but surely, if you continue to expand, this is only a matter of time.
Author: Chen Xi
Date: 11:31:12
Environment: [Mac 10.7.1 lion intel-based x64 gcc4.2.1 xcode4.2]
Reprinted please indicate the source