
用C写个迷你解释器从零构建你的第一个编程语言解释器在编程学习过程中我们经常使用各种语言的解释器但很少深入理解它们的工作原理。本文将通过C语言实现一个迷你解释器帮助你理解解释器的核心机制掌握词法分析、语法分析和执行引擎的设计思路。1. 解释器基础概念1.1 什么是解释器解释器是一种能够直接执行源代码的程序它逐行读取源代码分析其含义并立即执行相应的操作。与编译器不同解释器不会将源代码转换为机器码而是在运行时动态解析和执行。解释器的核心工作流程通常包括词法分析将源代码分解为有意义的标记tokens语法分析根据语法规则构建抽象语法树AST语义分析检查代码的逻辑正确性执行引擎按照AST的结构执行代码1.2 解释器与编译器的区别虽然解释器和编译器都是语言处理器但它们在处理方式上有本质区别编译器将整个源代码一次性翻译成目标代码如机器码生成可执行文件解释器逐行读取源代码并立即执行不生成独立的可执行文件解释器的优势在于开发调试方便跨平台性好劣势是执行效率相对较低。1.3 迷你解释器的设计目标我们的迷你解释器将实现以下基本功能支持简单的算术运算加、减、乘、除支持变量声明和赋值支持基本的控制结构条件判断提供清晰的错误提示信息代码结构简洁便于理解和扩展2. 环境准备与开发工具2.1 开发环境要求为了完成本教程你需要准备以下环境操作系统Windows、Linux或macOS均可C编译器GCC推荐或Clang代码编辑器VS Code、Vim、或任何你熟悉的文本编辑器调试工具GDB可选用于调试复杂问题2.2 验证开发环境在开始编码前先验证你的C环境是否正常工作# 检查GCC版本 gcc --version # 简单的测试程序 echo #include stdio.h int main() { printf(环境准备就绪\n); return 0; } test.c gcc test.c -o test ./test如果看到环境准备就绪的输出说明环境配置正确。2.3 项目目录结构建议按以下结构组织项目文件mini_interpreter/ ├── src/ │ ├── lexer.c # 词法分析器 │ ├── parser.c # 语法分析器 │ ├── eval.c # 表达式求值 │ └── main.c # 主程序 ├── include/ │ ├── lexer.h # 词法分析头文件 │ ├── parser.h # 语法分析头文件 │ └── eval.h # 求值头文件 └── Makefile # 构建脚本3. 词法分析器实现3.1 词法分析的基本概念词法分析是解释器的第一道工序负责将源代码字符串分解为有意义的标记tokens。每个token包含类型和值信息。我们的迷你解释器需要识别以下类型的token数字字面量如123, 45.67标识符变量名如x, count运算符、-、*、/、关键字if、else、let等分隔符;、()、{}等3.2 Token数据结构设计首先定义token的数据结构// include/lexer.h #ifndef LEXER_H #define LEXER_H typedef enum { TOKEN_EOF, // 文件结束 TOKEN_NUMBER, // 数字 TOKEN_IDENTIFIER, // 标识符 TOKEN_PLUS, // TOKEN_MINUS, // - TOKEN_MULTIPLY, // * TOKEN_DIVIDE, // / TOKEN_ASSIGN, // TOKEN_SEMICOLON, // ; TOKEN_LPAREN, // ( TOKEN_RPAREN, // ) TOKEN_LBRACE, // { TOKEN_RBRACE, // } TOKEN_IF, // if关键字 TOKEN_ELSE, // else关键字 TOKEN_LET, // let关键字 TOKEN_ERROR // 错误token } TokenType; typedef struct { TokenType type; char* value; int line; int column; } Token; typedef struct { char* source; int position; int line; int column; } Lexer; // 函数声明 Lexer* create_lexer(char* source); void destroy_lexer(Lexer* lexer); Token* get_next_token(Lexer* lexer); const char* token_type_to_string(TokenType type); #endif3.3 词法分析器核心实现// src/lexer.c #include stdio.h #include stdlib.h #include string.h #include ctype.h #include lexer.h Lexer* create_lexer(char* source) { Lexer* lexer malloc(sizeof(Lexer)); lexer-source source; lexer-position 0; lexer-line 1; lexer-column 1; return lexer; } void destroy_lexer(Lexer* lexer) { free(lexer); } static char peek(Lexer* lexer) { return lexer-source[lexer-position]; } static char advance(Lexer* lexer) { char c peek(lexer); if (c ! \0) { lexer-position; lexer-column; if (c \n) { lexer-line; lexer-column 1; } } return c; } static void skip_whitespace(Lexer* lexer) { while (isspace(peek(lexer))) { advance(lexer); } } static Token* create_token(TokenType type, char* value, int line, int column) { Token* token malloc(sizeof(Token)); token-type type; token-value value ? strdup(value) : NULL; token-line line; token-column column; return token; } static int is_identifier_char(char c) { return isalnum(c) || c _; } Token* get_next_token(Lexer* lexer) { skip_whitespace(lexer); if (peek(lexer) \0) { return create_token(TOKEN_EOF, NULL, lexer-line, lexer-column); } char current peek(lexer); int line lexer-line; int column lexer-column; // 处理数字 if (isdigit(current)) { char* start lexer-source lexer-position; while (isdigit(peek(lexer))) { advance(lexer); } if (peek(lexer) .) { advance(lexer); while (isdigit(peek(lexer))) { advance(lexer); } } int length (lexer-source lexer-position) - start; char* value malloc(length 1); strncpy(value, start, length); value[length] \0; return create_token(TOKEN_NUMBER, value, line, column); } // 处理标识符和关键字 if (isalpha(current) || current _) { char* start lexer-source lexer-position; while (is_identifier_char(peek(lexer))) { advance(lexer); } int length (lexer-source lexer-position) - start; char* value malloc(length 1); strncpy(value, start, length); value[length] \0; // 检查是否为关键字 if (strcmp(value, if) 0) { free(value); return create_token(TOKEN_IF, NULL, line, column); } else if (strcmp(value, else) 0) { free(value); return create_token(TOKEN_ELSE, NULL, line, column); } else if (strcmp(value, let) 0) { free(value); return create_token(TOKEN_LET, NULL, line, column); } return create_token(TOKEN_IDENTIFIER, value, line, column); } // 处理单个字符的token switch (current) { case : advance(lexer); return create_token(TOKEN_PLUS, NULL, line, column); case -: advance(lexer); return create_token(TOKEN_MINUS, NULL, line, column); case *: advance(lexer); return create_token(TOKEN_MULTIPLY, NULL, line, column); case /: advance(lexer); return create_token(TOKEN_DIVIDE, NULL, line, column); case : advance(lexer); return create_token(TOKEN_ASSIGN, NULL, line, column); case ;: advance(lexer); return create_token(TOKEN_SEMICOLON, NULL, line, column); case (: advance(lexer); return create_token(TOKEN_LPAREN, NULL, line, column); case ): advance(lexer); return create_token(TOKEN_RPAREN, NULL, line, column); case {: advance(lexer); return create_token(TOKEN_LBRACE, NULL, line, column); case }: advance(lexer); return create_token(TOKEN_RBRACE, NULL, line, column); } // 未知字符返回错误token char error_value[2] {current, \0}; advance(lexer); return create_token(TOKEN_ERROR, error_value, line, column); } const char* token_type_to_string(TokenType type) { switch (type) { case TOKEN_EOF: return EOF; case TOKEN_NUMBER: return NUMBER; case TOKEN_IDENTIFIER: return IDENTIFIER; case TOKEN_PLUS: return PLUS; case TOKEN_MINUS: return MINUS; case TOKEN_MULTIPLY: return MULTIPLY; case TOKEN_DIVIDE: return DIVIDE; case TOKEN_ASSIGN: return ASSIGN; case TOKEN_SEMICOLON: return SEMICOLON; case TOKEN_LPAREN: return LPAREN; case TOKEN_RPAREN: return RPAREN; case TOKEN_LBRACE: return LBRACE; case TOKEN_RBRACE: return RBRACE; case TOKEN_IF: return IF; case TOKEN_ELSE: return ELSE; case TOKEN_LET: return LET; case TOKEN_ERROR: return ERROR; default: return UNKNOWN; } }4. 语法分析器设计4.1 抽象语法树AST结构语法分析器将token序列转换为抽象语法树AST这是解释器的核心数据结构。// include/parser.h #ifndef PARSER_H #define PARSER_H #include lexer.h typedef enum { NODE_NUMBER, NODE_IDENTIFIER, NODE_BINARY_OP, NODE_ASSIGNMENT, NODE_VARIABLE_DECL, NODE_IF_STATEMENT, NODE_BLOCK } NodeType; typedef struct ASTNode { NodeType type; union { double number_value; char* identifier_name; struct { struct ASTNode* left; TokenType operator; struct ASTNode* right; } binary_op; struct { char* variable_name; struct ASTNode* value; } assignment; struct { char* variable_name; struct ASTNode* initial_value; } variable_decl; struct { struct ASTNode* condition; struct ASTNode* then_branch; struct ASTNode* else_branch; } if_statement; struct { struct ASTNode** statements; int statement_count; } block; } data; } ASTNode; typedef struct { Lexer* lexer; Token* current_token; } Parser; // 函数声明 Parser* create_parser(Lexer* lexer); void destroy_parser(Parser* parser); ASTNode* parse_program(Parser* parser); void destroy_ast(ASTNode* node); void print_ast(ASTNode* node, int indent); #endif4.2 语法分析器实现// src/parser.c #include stdio.h #include stdlib.h #include string.h #include parser.h Parser* create_parser(Lexer* lexer) { Parser* parser malloc(sizeof(Parser)); parser-lexer lexer; parser-current_token get_next_token(lexer); return parser; } void destroy_parser(Parser* parser) { if (parser-current_token) { free(parser-current_token-value); free(parser-current_token); } free(parser); } static void eat(Parser* parser, TokenType expected_type) { if (parser-current_token-type expected_type) { free(parser-current_token-value); free(parser-current_token); parser-current_token get_next_token(parser-lexer); } else { fprintf(stderr, 语法错误第%d行第%d列期望 %s得到 %s\n, parser-current_token-line, parser-current_token-column, token_type_to_string(expected_type), token_type_to_string(parser-current_token-type)); exit(1); } } static ASTNode* parse_expression(Parser* parser); static ASTNode* parse_term(Parser* parser); static ASTNode* parse_factor(Parser* parser); ASTNode* create_number_node(double value) { ASTNode* node malloc(sizeof(ASTNode)); node-type NODE_NUMBER; node-data.number_value value; return node; } ASTNode* create_identifier_node(char* name) { ASTNode* node malloc(sizeof(ASTNode)); node-type NODE_IDENTIFIER; node-data.identifier_name strdup(name); return node; } ASTNode* create_binary_op_node(ASTNode* left, TokenType operator, ASTNode* right) { ASTNode* node malloc(sizeof(ASTNode)); node-type NODE_BINARY_OP; node-data.binary_op.left left; node-data.binary_op.operator operator; node-data.binary_op.right right; return node; } static ASTNode* parse_factor(Parser* parser) { Token* token parser-current_token; if (token-type TOKEN_NUMBER) { double value atof(token-value); eat(parser, TOKEN_NUMBER); return create_number_node(value); } if (token-type TOKEN_IDENTIFIER) { char* name strdup(token-value); eat(parser, TOKEN_IDENTIFIER); return create_identifier_node(name); } if (token-type TOKEN_LPAREN) { eat(parser, TOKEN_LPAREN); ASTNode* expr parse_expression(parser); eat(parser, TOKEN_RPAREN); return expr; } fprintf(stderr, 语法错误第%d行第%d列意外的token %s\n, token-line, token-column, token_type_to_string(token-type)); exit(1); } static ASTNode* parse_term(Parser* parser) { ASTNode* node parse_factor(parser); while (parser-current_token-type TOKEN_MULTIPLY || parser-current_token-type TOKEN_DIVIDE) { TokenType operator parser-current_token-type; eat(parser, operator); ASTNode* right parse_factor(parser); node create_binary_op_node(node, operator, right); } return node; } static ASTNode* parse_expression(Parser* parser) { ASTNode* node parse_term(parser); while (parser-current_token-type TOKEN_PLUS || parser-current_token-type TOKEN_MINUS) { TokenType operator parser-current_token-type; eat(parser, operator); ASTNode* right parse_term(parser); node create_binary_op_node(node, operator, right); } return node; } ASTNode* parse_program(Parser* parser) { // 简化版本只解析一个表达式 return parse_expression(parser); } void destroy_ast(ASTNode* node) { if (!node) return; switch (node-type) { case NODE_IDENTIFIER: free(node-data.identifier_name); break; case NODE_BINARY_OP: destroy_ast(node-data.binary_op.left); destroy_ast(node-data.binary_op.right); break; default: break; } free(node); } void print_ast(ASTNode* node, int indent) { if (!node) return; for (int i 0; i indent; i) printf( ); switch (node-type) { case NODE_NUMBER: printf(Number: %g\n, node-data.number_value); break; case NODE_IDENTIFIER: printf(Identifier: %s\n, node-data.identifier_name); break; case NODE_BINARY_OP: printf(BinaryOp: %s\n, token_type_to_string(node-data.binary_op.operator)); print_ast(node-data.binary_op.left, indent 1); print_ast(node-data.binary_op.right, indent 1); break; default: printf(Unknown node type\n); } }5. 表达式求值引擎5.1 符号表管理为了实现变量功能我们需要一个符号表来存储变量名和值的映射关系。// include/eval.h #ifndef EVAL_H #define EVAL_H #include parser.h typedef struct Symbol { char* name; double value; struct Symbol* next; } Symbol; typedef struct { Symbol* head; } SymbolTable; typedef struct { double value; int error; char* error_msg; } EvalResult; // 函数声明 SymbolTable* create_symbol_table(); void destroy_symbol_table(SymbolTable* table); void symbol_table_set(SymbolTable* table, char* name, double value); double symbol_table_get(SymbolTable* table, char* name); int symbol_table_exists(SymbolTable* table, char* name); EvalResult evaluate(ASTNode* node, SymbolTable* table); #endif5.2 求值器实现// src/eval.c #include stdio.h #include stdlib.h #include string.h #include eval.h SymbolTable* create_symbol_table() { SymbolTable* table malloc(sizeof(SymbolTable)); table-head NULL; return table; } void destroy_symbol_table(SymbolTable* table) { Symbol* current table-head; while (current) { Symbol* next current-next; free(current-name); free(current); current next; } free(table); } void symbol_table_set(SymbolTable* table, char* name, double value) { // 检查变量是否已存在 Symbol* current table-head; while (current) { if (strcmp(current-name, name) 0) { current-value value; return; } current current-next; } // 创建新符号 Symbol* new_symbol malloc(sizeof(Symbol)); new_symbol-name strdup(name); new_symbol-value value; new_symbol-next table-head; table-head new_symbol; } double symbol_table_get(SymbolTable* table, char* name) { Symbol* current table-head; while (current) { if (strcmp(current-name, name) 0) { return current-value; } current current-next; } // 变量未定义返回0并打印警告 fprintf(stderr, 警告变量 %s 未定义使用默认值0\n, name); return 0.0; } int symbol_table_exists(SymbolTable* table, char* name) { Symbol* current table-head; while (current) { if (strcmp(current-name, name) 0) { return 1; } current current-next; } return 0; } EvalResult evaluate(ASTNode* node, SymbolTable* table) { EvalResult result {0, 0, NULL}; if (!node) { result.error 1; result.error_msg 空节点; return result; } switch (node-type) { case NODE_NUMBER: result.value node-data.number_value; break; case NODE_IDENTIFIER: result.value symbol_table_get(table, node-data.identifier_name); break; case NODE_BINARY_OP: { EvalResult left_result evaluate(node-data.binary_op.left, table); if (left_result.error) return left_result; EvalResult right_result evaluate(node-data.binary_op.right, table); if (right_result.error) return right_result; switch (node-data.binary_op.operator) { case TOKEN_PLUS: result.value left_result.value right_result.value; break; case TOKEN_MINUS: result.value left_result.value - right_result.value; break; case TOKEN_MULTIPLY: result.value left_result.value * right_result.value; break; case TOKEN_DIVIDE: if (right_result.value 0) { result.error 1; result.error_msg 除零错误; return result; } result.value left_result.value / right_result.value; break; default: result.error 1; result.error_msg 不支持的运算符; } break; } default: result.error 1; result.error_msg 不支持的节点类型; } return result; }6. 主程序与REPL实现6.1 完整的解释器主程序// src/main.c #include stdio.h #include stdlib.h #include string.h #include lexer.h #include parser.h #include eval.h void run_code(const char* source) { Lexer* lexer create_lexer(strdup(source)); Parser* parser create_parser(lexer); SymbolTable* table create_symbol_table(); printf(源代码: %s\n, source); printf(词法分析结果:\n); // 演示词法分析 Token* token; do { token get_next_token(lexer); printf( %s, token_type_to_string(token-type)); if (token-value) { printf((%s), token-value); } printf( [行%d列%d]\n, token-line, token-column); } while (token-type ! TOKEN_EOF); // 重置lexer进行语法分析 destroy_lexer(lexer); lexer create_lexer(strdup(source)); destroy_parser(parser); parser create_parser(lexer); printf(\n语法分析结果 (AST):\n); ASTNode* ast parse_program(parser); print_ast(ast, 0); printf(\n求值结果:\n); EvalResult result evaluate(ast, table); if (result.error) { printf(错误: %s\n, result.error_msg); } else { printf(结果: %g\n, result.value); } // 清理资源 destroy_ast(ast); destroy_parser(parser); destroy_lexer(lexer); destroy_symbol_table(table); } void interactive_mode() { printf(迷你解释器交互模式 (输入quit退出)\n); SymbolTable* table create_symbol_table(); char input[256]; while (1) { printf( ); if (!fgets(input, sizeof(input), stdin)) break; // 去除换行符 input[strcspn(input, \n)] 0; if (strcmp(input, quit) 0) break; if (strlen(input) 0) continue; // 执行代码 Lexer* lexer create_lexer(strdup(input)); Parser* parser create_parser(lexer); ASTNode* ast parse_program(parser); EvalResult result evaluate(ast, table); if (result.error) { printf(错误: %s\n, result.error_msg); } else { printf(%g\n, result.value); } destroy_ast(ast); destroy_parser(parser); destroy_lexer(lexer); } destroy_symbol_table(table); } int main(int argc, char* argv[]) { if (argc 1) { // 交互模式 interactive_mode(); } else if (argc 2) { // 执行单行代码 run_code(argv[1]); } else { printf(用法:\n); printf( %s 进入交互模式\n, argv[0]); printf( %s \表达式\ 执行单行代码\n, argv[0]); printf(\n示例:\n); printf( %s \2 3 * 4\\n, argv[0]); printf( %s \(1 2) * (3 - 4)\\n, argv[0]); } return 0; }6.2 构建脚本创建Makefile来简化编译过程# Makefile CC gcc CFLAGS -Wall -Wextra -stdc99 -g SRCDIR src INCDIR include SOURCES $(SRCDIR)/main.c $(SRCDIR)/lexer.c $(SRCDIR)/parser.c $(SRCDIR)/eval.c OBJECTS $(SOURCES:.c.o) TARGET mini_interpreter .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(CFLAGS) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -I$(INCDIR) -c $ -o $ clean: rm -f $(OBJECTS) $(TARGET) test: $(TARGET) ./$(TARGET) 2 3 * 4 ./$(TARGET) (10 - 5) * 2 .PHONY: interactive interactive: $(TARGET) ./$(TARGET)7. 测试与验证7.1 基础功能测试编译并测试解释器# 编译项目 make # 测试基本算术运算 ./mini_interpreter 2 3 ./mini_interpreter 10 - 5 * 2 ./mini_interpreter (3 4) * 5 # 进入交互模式测试复杂表达式 ./mini_interpreter7.2 测试用例设计创建测试脚本来验证解释器的正确性#!/bin/bash # test.sh echo 迷你解释器测试 test_cases( 1 1 # 简单加法 5 - 3 # 简单减法 2 * 3 # 简单乘法 6 / 2 # 简单除法 2 3 * 4 # 运算符优先级 (2 3) * 4 # 括号改变优先级 10 - 5 - 2 # 连续减法 ) expected_results( 2 2 6 3 14 20 3 ) for i in ${!test_cases[]}; do echo 测试: ${test_cases[i]} result$(./mini_interpreter ${test_cases[i]} 21 | grep 结果: | cut -d -f2) if [ $result ${expected_results[i]} ]; then echo ✓ 通过: $result else echo ✗ 失败: 期望 ${expected_results[i]}得到 $result fi echo done8. 功能扩展与优化8.1 添加变量支持扩展语法分析器以支持变量声明和赋值// 在parser.c中添加变量声明解析 static ASTNode* parse_statement(Parser* parser) { if (parser-current_token-type TOKEN_LET) { return parse_variable_declaration(parser); } return parse_expression(parser); } static ASTNode* parse_variable_declaration(Parser* parser) { eat(parser, TOKEN_LET); // 消耗let if (parser-current_token-type ! TOKEN_IDENTIFIER) { fprintf(stderr, 语法错误期望标识符\n); exit(1); } char* var_name strdup(parser-current_token-value); eat(parser, TOKEN_IDENTIFIER); eat(parser, TOKEN_ASSIGN); ASTNode* initializer parse_expression(parser); ASTNode* node malloc(sizeof(ASTNode)); node-type NODE_VARIABLE_DECL; node-data.variable_decl.variable_name var_name; node-data.variable_decl.initial_value initializer; return node; }8.2 添加条件语句支持实现基本的if-else条件判断// 扩展AST节点类型 static ASTNode* parse_if_statement(Parser* parser) { eat(parser, TOKEN_IF); // 消耗if eat(parser, TOKEN_LPAREN); ASTNode* condition parse_expression(parser); eat(parser, TOKEN_RPAREN); ASTNode* then_branch parse_statement(parser); ASTNode* else_branch NULL; if (parser-current_token-type TOKEN_ELSE) { eat(parser, TOKEN_ELSE); else_branch parse_statement(parser); } ASTNode* node malloc(sizeof(ASTNode)); node-type NODE_IF_STATEMENT; node-data.if_statement.condition condition; node-data.if_statement.then_branch then_branch; node-data.if_statement.else_branch else_branch; return node; }8.3 错误处理改进增强错误处理机制提供更友好的错误信息typedef struct { int has_error; char* message; int line; int column; } ParseError; static ParseError* create_error(const char* message, int line, int column) { ParseError* error malloc(sizeof(ParseError)); error-has_error 1; error-message strdup(message); error-line line; error-column column; return error; } // 修改解析函数返回错误而不是直接退出 static ASTNode* parse_expression_with_error(Parser* parser, ParseError** error) { // 实现带错误处理的解析逻辑 }9. 性能优化建议9.1 内存管理优化实现对象池来减少内存分配开销#define TOKEN_POOL_SIZE 100 #define NODE_POOL_SIZE 100 typedef struct { Token tokens[TOKEN_POOL_SIZE]; ASTNode nodes[NODE_POOL_SIZE]; int token_count; int node_count; } MemoryPool; Token* pool_alloc_token(MemoryPool* pool) { if (pool-token_count TOKEN_POOL_SIZE) { return pool-tokens[pool-token_count]; } return malloc(sizeof(Token)); }9.2 词法分析优化使用查找表加速关键字识别typedef struct { const char* keyword; TokenType token_type; } KeywordMap; static KeywordMap keyword_map[] { {if, TOKEN_IF}, {else, TOKEN_ELSE}, {let, TOKEN_LET}, {NULL, TOKEN_ERROR} }; static TokenType get_keyword_type(const char* text) { for (int i 0; keyword_map[i].keyword ! NULL; i) { if (strcmp(text, keyword_map[i].keyword) 0) { return keyword_map[i].token_type; } } return TOKEN_IDENTIFIER; }10. 实际应用场景10.1 配置文件解析迷你解释器可以扩展用于解析简单的配置文件# 示例配置文件 timeout 30 retry_count 3 server_host localhost server_port 808010.2 计算器应用构建图形化计算器界面使用解释器作为计算引擎。10.3 教学工具作为编程语言原理的教学示例帮助学生理解解释器工作原理。11. 常见问题与解决方案11.1 内存泄漏问题问题现象程序运行时间较长后内存占用持续增长。解决方案确保每个malloc都有对应的free使用valgrind等工具检测内存泄漏实现统一的内存管理接口# 使用valgrind检测内存泄漏 valgrind --leak-checkfull ./mini_interpreter 2 211.2 运算符优先级错误问题现象2 3 * 4计算结果错误应为14但得到20。解决方案检查语法分析器的优先级处理确保乘除法优先于加减法验证括号的处理逻辑11.3 错误恢复机制问题现象遇到一个语法错误后整个程序退出。解决方案实现错误恢复机制跳过错误继续解析