手把手教你用C++ Builder 6给PL0编译器“打补丁”:新增ELSE、FOR等12个关键词 用C Builder 6为PL0编译器扩展12个关键词的实战指南在编译原理课程实验中PL0编译器作为经典的教学案例往往需要学生进行功能扩展。本文将手把手教你如何使用C Builder 6为PL0编译器添加ELSE、FOR等12个新关键词从代码定位到调试技巧提供完整的解决方案。1. 理解PL0编译器的核心架构PL0编译器采用单趟扫描的编译方式以语法分析为核心词法分析和代码生成作为独立过程被调用。整个系统由18个相互嵌套或并列的过程组成主要数据结构包括符号表(TABLE数组)记录标识符的层次、属性和相对位置代码区(CODE数组)存储生成的类PCODE目标指令运行时栈(S数组)用于目标程序执行时的数据存储关键函数模块包括GetSym() // 词法分析读取一个单词 Statement() // 语句处理 Block() // 分程序处理核心 Condition() // 条件处理 Expression()// 表达式处理在开始修改前建议先通读源代码特别关注GetSym()词法分析函数和Statement()语句处理函数的实现逻辑。理解原有保留字(如IF、THEN、WHILE)的处理流程这对后续添加新关键词至关重要。2. 新增关键词的完整实现步骤2.1 修改符号类型枚举定义首先需要在SYMBOL枚举类型中添加新的符号定义。原PL0编译器有33个符号定义我们需要扩展12个新符号typedef enum { NUL, IDENT, NUMBER, PLUS, MINUS, TIMES, SLASH, ODDSYM, EQL, NEQ, LSS, LEQ, GTR, GEQ, LPAREN, RPAREN, COMMA, SEMICOLON, PERIOD, BECOMES, BEGINSYM, ENDSYM, IFSYM, THENSYM, WHILESYM, WRITESYM, READSYM, DOSYM, CALLSYM, CONSTSYM, VARSYM, PROCSYM, PROGSYM, /* 新增的12个符号 */ ELSESYM, // ELSE FORSYM, // FOR TOSYM, // TO DOWNTOSYM, // DOWNTO RETURNSYM, // RETURN TIMESBECOMES, // * SLASHBECOMES, // / PLUSONE, // MINUSONE, // -- ANDSYM, // ORSYM, // || NOTSYM // ! } SYMBOL;同时需要更新符号输出数组SYMOUT[]保持与枚举定义一致char *SYMOUT[] { NUL, IDENT, NUMBER, PLUS, MINUS, TIMES, SLASH, ODDSYM, EQL, NEQ, LSS, LEQ, GTR, GEQ, LPAREN, RPAREN, COMMA, SEMICOLON, PERIOD, BECOMES, BEGINSYM, ENDSYM, IFSYM, THENSYM, WHILESYM, WRITESYM, READSYM, DOSYM, CALLSYM, CONSTSYM, VARSYM, PROCSYM, PROGSYM, /* 新增的12个符号输出 */ ELSESYM, FORSYM, TOSYM, DOWNTOSYM, RETURNSYM, TIMESBECOMES, SLASHBECOMES, PLUSONE, MINUSONE, ANDSYM, ORSYM, NOTSYM };2.2 更新保留字表和符号映射PL0编译器使用KWORD数组存储保留字字符串WSYM数组存储对应的符号值。原系统有14个保留字我们需要新增5个保留字// 保留字字符串表 strcpy(KWORD[1], BEGIN); strcpy(KWORD[2], CALL); strcpy(KWORD[3], CONST); strcpy(KWORD[4], DO); /* 新增保留字 */ strcpy(KWORD[5], DOWNTO); // 位置5 strcpy(KWORD[6], ELSE); // 位置6 strcpy(KWORD[7], END); strcpy(KWORD[8], FOR); // 位置8 strcpy(KWORD[9], IF); strcpy(KWORD[10], ODD); strcpy(KWORD[11], PROCEDURE); strcpy(KWORD[12], PROGRAM); strcpy(KWORD[13], READ); strcpy(KWORD[14], RETURN); // 位置14 strcpy(KWORD[15], THEN); strcpy(KWORD[16], TO); // 位置16 strcpy(KWORD[17], VAR); strcpy(KWORD[18], WHILE); strcpy(KWORD[19], WRITE); // 保留字对应的符号值 WSYM[1] BEGINSYM; WSYM[2] CALLSYM; WSYM[3] CONSTSYM; WSYM[4] DOSYM; /* 新增保留字符号 */ WSYM[5] DOWNTOSYM; WSYM[6] ELSESYM; WSYM[7] ENDSYM; WSYM[8] FORSYM; WSYM[9] IFSYM; WSYM[10] ODDSYM; WSYM[11] PROCSYM; WSYM[12] PROGSYM; WSYM[13] READSYM; WSYM[14] RETURNSYM; WSYM[15] THENSYM; WSYM[16] TOSYM; WSYM[17] VARSYM; WSYM[18] WHILESYM; WSYM[19] WRITESYM;同时需要更新单字符运算符的映射表SSYMSSYM[] PLUS; SSYM[-] MINUS; SSYM[*] TIMES; SSYM[/] SLASH; SSYM[(] LPAREN; SSYM[)] RPAREN; SSYM[] EQL; SSYM[,] COMMA; SSYM[.] PERIOD; SSYM[;] SEMICOLON; /* 新增单字符运算符 */ SSYM[] ANDSYM; SSYM[!] NOTSYM;2.3 修改词法分析器GetSym()GetSym()函数负责识别源代码中的各种符号。我们需要扩展它以支持新的复合运算符和保留字void GetSym() { // ... (原有代码) else if (CH *) { GetCh(); if (CH ) { // 识别 * SYM TIMESBECOMES; GetCh(); } else { SYM TIMES; } } else if (CH /) { GetCh(); if (CH ) { // 识别 / SYM SLASHBECOMES; GetCh(); } else { SYM SLASH; } } else if (CH ) { GetCh(); if (CH ) { // 识别 SYM PLUSONE; GetCh(); } else { SYM PLUS; } } else if (CH -) { GetCh(); if (CH -) { // 识别 -- SYM MINUSONE; GetCh(); } else { SYM MINUS; } } else if (CH |) { GetCh(); if (CH |) { // 识别 || SYM ORSYM; GetCh(); } else { Error(19); // 非法符号 } } // ... (原有代码) }2.4 修改语句处理函数Statement()在Statement()函数中我们需要为新增的保留字添加处理逻辑。虽然FOR、RETURN等语句需要完整的语法支持但作为基础扩展我们可以先添加简单的识别void STATEMENT(SYMSET FSYS, int LEV, int TX) { switch (SYM) { // ... (原有case语句) /* 新增保留字处理 */ case FORSYM: GetSym(); // TODO: 实现FOR循环完整逻辑 break; case ELSESYM: GetSym(); // 实际ELSE处理在IF语句中实现 break; case RETURNSYM: GetSym(); // TODO: 实现RETURN语句 break; /* 新增运算符处理 */ case TIMESBECOMES: GetSym(); Form1-printfs( *); break; case SLASHBECOMES: GetSym(); Form1-printfs( /); break; case PLUSONE: GetSym(); Form1-printfs( ); break; case MINUSONE: GetSym(); Form1-printfs( --); break; case ANDSYM: GetSym(); Form1-printfs( ); break; case ORSYM: GetSym(); Form1-printfs( ||); break; case NOTSYM: GetSym(); Form1-printfs( !); break; // ... (原有代码) } }3. 实现ELSE子句的完整方案ELSE语句的实现相对复杂需要修改条件语句的处理逻辑。以下是详细步骤3.1 文法扩展原PL0条件语句文法〈条件语句〉 :: IF〈条件〉THEN〈语句〉扩展为〈条件语句〉 :: IF〈条件〉THEN〈语句〉[ELSE〈语句〉]3.2 修改Statement()中的IF处理逻辑关键修改点是保存两个跳转地址条件为假时跳转到ELSE部分ELSE结束后跳转到IF语句之后。case IFSYM: GetSym(); /* 处理条件 */ CONDITION(SymSetUnion(SymSetNew(THENSYM,DOSYM),FSYS),LEV,TX); if (SYM THENSYM) GetSym(); else Error(16); // 缺少THEN CX1 CX; // 保存条件跳转指令位置 GEN(JPC, 0, 0); // 生成条件跳转地址暂填0 /* 处理THEN语句 */ STATEMENT(FSYS, LEV, TX); CX2 CX; // 保存THEN语句结束位置 GEN(JMP, 0, 0); // 生成无条件跳转跳过ELSE部分 /* 回填条件跳转地址 */ CODE[CX1].A CX; // 如果条件为假跳转到这里(ELSE开始处) /* 处理可选的ELSE部分 */ if (SYM ELSESYM) { GetSym(); STATEMENT(FSYS, LEV, TX); } /* 回填跳过ELSE的跳转地址 */ CODE[CX2].A CX; // THEN执行完后跳到这里(IF语句结束) break;3.3 语义规则说明修改后的IF-ELSE语句执行流程计算条件表达式结果在栈顶条件为真执行THEN块语句跳过ELSE块(通过JMP指令)条件为假跳过THEN块(通过JPC指令)执行ELSE块语句继续执行后续代码4. 修改不等号表示原PL0使用#表示不等号现改为更常见的表示// 注释掉原来的不等号定义 // SSYM[#] NEQ; // 在GetSym()中修改 if (CH ) { GetCh(); if (CH ) { SYM LEQ; // GetCh(); } else if (CH ) { SYM NEQ; // GetCh(); } else { SYM LSS; // } }5. 调试技巧与常见问题在修改PL0编译器过程中可能会遇到以下典型问题5.1 词法分析错误症状无法识别新增的关键词或运算符解决方法检查KWORD和WSYM数组是否正确定义确保GetSym()中的复合运算符识别逻辑正确使用调试输出验证符号识别结果5.2 语法分析错误症状处理新增语法结构时报错解决方法检查Statement()中的case分支是否完整验证文法规则的实现是否准确确保符号集(FSYS)包含所有可能的后续符号5.3 代码生成问题症状生成的目标代码执行结果不正确解决方法检查跳转地址的回填是否正确验证栈操作指令的顺序和参数使用解释器的单步执行功能跟踪代码执行5.4 调试建议添加调试输出在关键函数入口处打印当前状态printf(Enter STATEMENT, SYM%s\n, SYMOUT[SYM]);检查符号表在出现未定义标识符错误时打印符号表内容for (int i 0; i TX; i) { printf(%s: kind%d, level%d, addr%d\n, TABLE[i].NAME, TABLE[i].KIND, TABLE[i].vp.LEVEL, TABLE[i].vp.ADR); }验证目标代码在代码生成后检查CODE数组内容for (int i 0; i CX; i) { printf(%d: %d %d %d\n, i, CODE[i].F, CODE[i].L, CODE[i].A); }6. 测试方案设计为确保修改后的编译器正确性需要设计全面的测试用例6.1 基础功能测试测试1新增保留字识别PROGRAM TEST1; VAR X; BEGIN IF X 0 THEN ELSE WRITE(X); FOR X : 1 TO 10 DO WRITE(X); RETURN END.预期结果能正确识别所有新增保留字不报语法错误6.2 运算符测试测试2复合运算符PROGRAM TEST2; VAR A, B; BEGIN A : 10; B : 5; A * B; // A A * B A / 2; // A A / 2 A; // A A 1 B--; // B B - 1 WRITE(A, B) END.预期结果输出 26 46.3 ELSE语句测试测试3ELSE分支验证PROGRAM TEST3; VAR X; BEGIN READ(X); IF X 0 THEN WRITE(1) ELSE WRITE(0); WRITE(9) END.测试用例输入5 → 输出1 9输入0 → 输出0 9输入-3 → 输出0 96.4 不等号修改测试测试4不等号运算PROGRAM TEST4; VAR A, B; BEGIN A : 1; B : 2; IF A B THEN WRITE(1) ELSE WRITE(0) END.预期结果输出17. 进一步扩展建议完成基础关键词扩展后可以考虑以下进阶改进实现FOR循环语义支持FOR i : 1 TO 10 DO和FOR i : 10 DOWNTO 1 DO语法生成相应的目标代码实现循环控制完善RETURN语句支持从过程中返回值处理过程调用栈的返回逻辑增强错误处理添加更精确的错误位置报告支持错误恢复机制优化代码生成实现简单的常量传播优化消除冗余指令添加注释支持识别和处理单行(//)和多行(/* */)注释这些扩展将使学生更深入地理解编译器设计的各个方面从词法分析到代码优化全面提升编译原理实践能力。