编译原理实验(四)———— LR(1)分析法
一、实验目的
- 掌握LR(1)分析法的基本原理与实现流程。
- 通过构造LR(1)分析表,验证符号串是否符合给定文法规则。
- 理解LR(1)分析中向前搜索符(Lookahead Symbol)的作用,解决移进-归约冲突。
二、实验题目
1.对下列文法,用LR(1)分析法对任意输入的符号串进行分析:
文法规则:
(0) E → S
(1) S → BB
(2) B → aB
(3) B → b
LR(1)分析表:
状态 | ACTION (a, b, #) | GOTO (S, B) |
---|---|---|
S0 | S3, S4, - | 1, 2 |
S1 | -, -, acc | -, - |
S2 | S6, S7, - | -, 5 |
S3 | S3, S4, - | -, 8 |
S4 | r3, r3, - | -, - |
S5 | -, -, r1 | -, - |
S6 | S6, S7, - | -, 9 |
S7 | -, -, r3 | -, - |
S8 | r2, r2, - | -, - |
S9 | -, -, r2 | -, - |
2. 输入串分析实例
(1) 输入 baba#
的分析过程
输出结果:
步骤 | 状态栈 | 符号栈 | 输入串 | ACTION | GOTO |
---|---|---|---|---|---|
1 | 0 | # | baba# | S4 | - |
2 | 04 | #b | aba# | r3→B | 2 |
3 | 02 | #B | aba# | S6 | - |
4 | 026 | #Ba | ba# | S7 | - |
5 | 0267 | #Bab | a# | error | - |
错误原因:
- 在状态S7时,输入符号为
a
,但ACTION表中无对应动作(仅允许在#
时归约r3)。 - 符号栈中的
Bab
无法匹配任何产生式右部,导致无法继续归约或移进。
(2) 输入 bb#
的分析过程
输出结果:
步骤 | 状态栈 | 符号栈 | 输入串 | ACTION | GOTO |
---|---|---|---|---|---|
1 | 0 | # | bb# | S4 | - |
2 | 04 | #b | b# | r3→B | 2 |
3 | 02 | #B | b# | S7 | - |
4 | 027 | #Bb | # | r3→B | 5 |
5 | 025 | #BB | # | r1→S | 1 |
6 | 01 | #S | # | acc | - |
正确性验证:
- 第5步通过归约
S→BB
生成S
,最终在状态S1接受输入。
三、实验理论依据
1. LR(1)分析法的核心
LR(1)分析法是一种自底向上的语法分析方法,通过构造LR(1)项集规范族和分析表,实现对文法的精确分析。其核心包括:
- LR(1)项:形式为
[A→α·β, a]
,其中α
和β
是产生式右部的符号序列,a
是向前搜索符(Lookahead Symbol)。- 作用:仅当输入符号匹配
a
时,才允许进行归约操作,避免移进-归约冲突。
- 作用:仅当输入符号匹配
- 闭包运算(CLOSURE):
- 对项集进行扩展,添加所有可能的推导项。例如:
若存在项[A→α·Bβ, a]
,则需添加所有B→·γ
的项,其向前搜索符为FIRST(βa)
。 - 公式:
CLOSURE(I) = I ∪ { [B→·γ, b] | [A→α·Bβ, a] ∈ I, B→γ ∈ P, b ∈ FIRST(βa) }
- 对项集进行扩展,添加所有可能的推导项。例如:
- GOTO函数:
- 根据当前项集
I
和符号X
,计算转移后的项集GOTO(I, X)
。 - 公式:
GOTO(I, X) = CLOSURE({ [A→αX·β, a] | [A→α·Xβ, a] ∈ I })
- 根据当前项集
2. LR(1)分析表构造步骤
- 拓广文法:
- 添加新产生式
E'→E
,作为初始状态。
- 添加新产生式
- 构建LR(1)项集族:
- 初始项集:
CLOSURE({ [E'→·E, #] })
。 - 通过不断应用
GOTO
函数生成所有项集,形成状态集合。
- 初始项集:
- 填充ACTION与GOTO表:
- ACTION表:
- 移进(S) :若项集包含
[A→α·aβ, b]
,则ACTION[I, a] = S_j
(j
是GOTO(I, a)
的状态编号)。 - 归约(r_k) :若项集包含
[A→α·, a]
,则ACTION[I, a] = r_k
(k
是产生式A→α
的编号)。 - 接受(acc) :若项集包含
[E'→E·, #]
,则ACTION[I, #] = acc
。- GOTO表:
- 若
GOTO(I, A) = J
,则GOTO[I, A] = J
(A
为非终结符)。
四、LR(1)分析法设计(完整版)
1. 总体设计框架
LR(1)分析器由分析表驱动,核心模块包括:
- 状态栈:记录当前分析状态(如S0, S1)。
- 符号栈:保存已识别的文法符号(终结符/非终结符)。
- 输入缓冲区:存放待分析的输入符号串。
- LR(1)分析表:包含ACTION(移进、归约、接受)和GOTO(状态转移)规则。
2. 核心算法流程设计
程序流程图
开始
│
↓
初始化状态栈[0]、符号栈[#]、输入缓冲区
│
↓
循环:
│
├─ 当前状态s = 栈顶状态
├─ 当前输入符号a = 缓冲区首字符
│
├─ 查ACTION[s,a]:
│ ├─ 若为S_j:
│ │ 压入a和S_j到符号栈和状态栈
│ │ 缓冲区指针后移
│ │
│ ├─ 若为r_k(产生式A→β):
│ │ 弹出|β|个状态和符号
│ │ 查GOTO[新栈顶状态, A]得s_new
│ │ 压入A和s_new
│ │
│ ├─ 若为acc:
│ │ 输出成功并终止
│ │
│ └─ 若为空:
│ 调用错误处理函数
│
↓
直到缓冲区为空或报错
3. 关键数据结构与实现
(1) 状态栈与符号栈
-
状态栈:
- 类型:整数栈(如
stack<int>
),存储状态编号(S0, S1, ...)。 - 操作:
# 示例:归约时弹出产生式右部长度 for _ in range(len(production.right)): state_stack.pop()
- 类型:整数栈(如
-
符号栈:
- 类型:字符串栈(如
stack<string>
),记录已匹配的符号序列。 - 示例:输入
bb#
时符号栈变化为# → #b → #B → #Bb → #BB → #S
。
- 类型:字符串栈(如
(2) LR(1)分析表
-
ACTION表:二维字典,键为
(状态, 终结符)
,值为动作类型:ACTION = { (0, 'b'): 'S4', (4, 'b'): 'r3', (2, 'b'): 'S7', # ...其他状态 }
-
GOTO表:二维字典,键为
(状态, 非终结符)
,值为目标状态:GOTO = { (0, 'B'): 2, (2, 'B'): 5, # ...其他状态 }
(3) 产生式存储
- 存储格式:列表或字典,记录产生式编号及其左右部:
productions = { 0: ('E', ['S']), 1: ('S', ['B', 'B']), 2: ('B', ['a', 'B']), 3: ('B', ['b']) }
4. 核心函数实现
(1) 移进函数
def shift(state_stack, symbol_stack, input_str, next_state): symbol = input_str[0] state_stack.push(next_state) symbol_stack.push(symbol) input_str = input_str[1:] # 消耗输入符号 return input_str
(2) 归约函数
def reduce(state_stack, symbol_stack, production): # 弹出产生式右部长度 for _ in range(len(production.right)): state_stack.pop() symbol_stack.pop() # 获取归约后的非终结符 A = production.left # 查GOTO表跳转 s_top = state_stack.top() new_state = GOTO_TABLE[s_top][A] # 压入新符号和状态 symbol_stack.push(A) state_stack.push(new_state)
(3) 错误处理函数
def error_handle(input_str, pos): print(f"语法错误:位置{pos}附近,符号'{input_str[pos]}'无法匹配") exit()
5. 实例解析(以输入bb#
为例)
步骤 | 状态栈 | 符号栈 | 输入串 | ACTION | 解释 |
---|---|---|---|---|---|
1 | [0] | # | bb# | S4 | 移进b到状态4 |
2 | [0,4] | #b | b# | r3→B | 归约B→b,跳转至状态2 |
3 | [0,2] | #B | b# | S7 | 移进b到状态7 |
4 | [0,2,7] | #Bb | # | r3→B | 归约B→b,跳转至状态5 |
5 | [0,2,5] | #BB | # | r1→S | 归约S→BB,跳转至状态1 |
6 | [0,1] | #S | # | acc | 接受输入 |
6. 冲突处理与优化
-
冲突检测:
- 若同一表项存在多个动作(如同时移进和归约),标记为冲突,需手动调整文法或使用LALR优化。
-
LALR优化:
- 同心项目集合并:合并具有相同核心LR(0)项但不同向前搜索符的状态,减少状态数。
- 示例:状态8(B→aB·, {a,b})和状态9(B→aB·, {#})可合并为一个状态。
-
错误恢复:
- 同步符号表:预定义符号集(如
{;, }
),在错误时跳过输入直至找到同步符号。
- 同步符号表:预定义符号集(如
7. 设计验证与测试
-
测试用例:
- 合法输入:
bb#
(输出acc)、abab#
(需根据文法验证)。 - 非法输入:
baba#
(步骤5报错,因ACTION[S7,a]无定义)[[用户题目实例]]。
- 合法输入:
-
覆盖率验证:确保所有产生式在测试中被至少触发一次。
8. 设计总结
- 优势:LR(1)通过向前搜索符解决移进-归约冲突,支持更复杂的文法。
- 挑战:手动构造分析表易出错,推荐使用Yacc等工具自动生成。
- 扩展性:可结合语义动作生成中间代码,实现完整编译器前端。
五、实例代码+运行结果
1.实例代码(一):
(1)源代码文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>/* ACTION表 */
char *action[10][3] = {{"S3", "S4", NULL}, // 状态0{NULL, NULL, "acc"}, // 状态1{"S6", "S7", NULL}, // 状态2{"S3", "S4", NULL}, // 状态3{"r3", "r3", NULL}, // 状态4{NULL, NULL, "r1"}, // 状态5{"S6", "S7", NULL}, // 状态6{NULL, NULL, "r3"}, // 状态7{"r2", "r2", NULL}, // 状态8{NULL, NULL, "r2"} // 状态9
};/* GOTO表 */
int goto_table[10][2] = {{1, 2}, // 状态0: S→1, B→2{0, 0}, // 状态1: 无跳转{0, 5}, // 状态2: B→5{0, 8}, // 状态3: B→8{0, 0}, // 状态4: 无跳转{0, 0}, // 状态5: 无跳转{0, 9}, // 状态6: B→9{0, 0}, // 状态7: 无跳转{0, 0}, // 状态8: 无跳转{0, 0} // 状态9: 无跳转
};char vt[] = {'a', 'b', '#'}; // 终结符
char vn[] = {'S', 'B'}; // 非终结符
char *productions[] = { // 产生式集合"E->S", // 产生式0"S->BB", // 产生式1"B->aB", // 产生式2"B->b" // 产生式3
};
int right_len[] = {1, 2, 2, 1}; // 每个产生式右部长度/* 查找终结符索引 */
int find_vt_index(char c) {for (int i = 0; i < 3; i++)if (vt[i] == c) return i;return -1;
}/* 查找非终结符索引 */
int find_vn_index(char c) {for (int i = 0; i < 2; i++)if (vn[i] == c) return i;return -1;
}/* LR(1)分析主函数 */
void lr_parser(char *input) {int state_stack[100] = {0}; // 状态栈,初始状态0char symbol_stack[100] = {'#'}; // 符号栈,初始为#int top_state = 0; // 状态栈栈顶指针int top_symbol = 0; // 符号栈栈顶指针int input_ptr = 0; // 输入串指针printf("步骤\t状态栈\t符号栈\t输入串\tACTION\tGOTO\n");int step = 0;while (1) {step++;printf("%d\t", step);/* 打印状态栈 */for (int i = 0; i <= top_state; i++) printf("%d", state_stack[i]);printf("\t\t");/* 打印符号栈 */for (int i = 0; i <= top_symbol; i++) printf("%c", symbol_stack[i]);printf("\t\t");/* 打印输入串 */printf("%s\t\t", input + input_ptr);int current_state = state_stack[top_state];char current_char = input[input_ptr];int vt_idx = find_vt_index(current_char);/* 1. 查ACTION表 */char *action_entry = vt_idx != -1 ? action[current_state][vt_idx] : NULL;if (action_entry == NULL) { // 错误处理printf("错误:在状态%d遇到非法字符'%c'\n", current_state, current_char);exit(1);}printf("%s\t", action_entry);/* 2. 处理动作 */if (strcmp(action_entry, "acc") == 0) { // 接受printf("\n输入串合法!\n");break;} else if (action_entry[0] == 'S') { // 移进int new_state = atoi(action_entry + 1);state_stack[++top_state] = new_state;symbol_stack[++top_symbol] = current_char;input_ptr++;printf("\n");} else if (action_entry[0] == 'r') { // 归约int prod_num = atoi(action_entry + 1); // 产生式编号char *prod = productions[prod_num];char left_symbol = prod[0]; // 产生式左部符号/* 弹出产生式右部长度个状态和符号 */int len = right_len[prod_num];top_state -= len;top_symbol -= len;/* 压入左部符号并更新状态栈 */symbol_stack[++top_symbol] = left_symbol;int vn_idx = find_vn_index(left_symbol);int new_state = goto_table[state_stack[top_state]][vn_idx];state_stack[++top_state] = new_state;printf("GOTO[%d,%c]=%d\n", state_stack[top_state-1], left_symbol, new_state);}}
}int main() {char input[100];printf("请输入待分析的符号串(以#结尾): ");scanf("%s", input);lr_parser(input);return 0;
}
(2)代码说明:
①代码运行逻辑
程序执行流程:
1.初始化:
- 状态栈初始化为
[0]
,符号栈初始化为[#]
,输入指针指向输入串首字符。 - 打印初始状态(步骤1)。
2.循环处理:
- 步骤1:取栈顶状态
current_state
和当前输入符号current_char
。 - 步骤2:根据
current_state
和current_char
查 ACTION表:
- 移进(S) :将新状态压入状态栈,符号压入符号栈,输入指针后移。
- 归约(r) :按产生式右部长度弹出栈顶元素,获取左部非终结符,查 GOTO表 确定新状态后压栈。
- 接受(acc) :终止循环,输出接受结果。
- 错误:输入符号无法匹配任何动作,报错退出。
3.终止条件:
- 输入处理完毕且ACTION表返回
acc
,或发生错误。
示例流程(输入 bb#
):
状态栈:[0] → [0,4] → [0,2] → [0,2,7] → [0,2,5] → [0,1]
符号栈:[#] → [#b] → [#B] → [#Bb] → [#BB] → [#S]
输入串:bb# → b# → b# → # → # → #
动作:S4 → r3→B → S7 → r3→B → r1→S → acc
②核心算法流程对照
LR(1)算法理论步骤 | 代码实现对应逻辑 |
---|---|
1. 初始化状态栈和符号栈 | state_stack[0] = 0 , symbol_stack[0] = '#' |
2. 读取当前状态和输入符号 | current_state = state_stack[top_state] , current_char = input[input_ptr] |
3. 查ACTION表决定动作 | action_entry = action[current_state][vt_idx] |
4. 移进动作(Shift) | state_stack[++top_state] = new_state , symbol_stack[++top_symbol] = current_char |
5. 归约动作(Reduce) | top_state -= len , top_symbol -= len , 查GOTO表后压栈 A 和 new_state |
6. 接受动作(Accept) | strcmp(action_entry, "acc") == 0 ,终止循环 |
7. 错误处理 | action_entry == NULL 时报错退出 |
③代码需要优化的地方与潜在问题
优化方向
-
数据结构效率:
- 问题:终结符/非终结符索引查询使用线性遍历(
O(n)
),数据量大时效率低。 - 优化:改用哈希表(如
unordered_map
)存储符号索引,查询复杂度降至O(1)
。
- 问题:终结符/非终结符索引查询使用线性遍历(
-
栈溢出风险:
- 问题:状态栈和符号栈使用固定大小数组(
[100]
),可能溢出。 - 优化:改为动态数组(如C++
vector
)或链表结构。
- 问题:状态栈和符号栈使用固定大小数组(
-
代码可读性:
- 问题:
action
和goto_table
的硬编码导致维护困难。 - 优化:从配置文件读取分析表,实现文法与代码解耦。
- 问题:
潜在问题
-
拓广文法处理缺陷:
- 问题:归约
E→S
后直接跳转到状态1(接受状态),未通过GOTO表查询,若文法扩展可能导致错误。 - 示例:若新增产生式
E→A
,需修改代码中的硬编码逻辑。
- 问题:归约
-
GOTO表越界风险:
- 问题:
goto_table
的列数固定为2(仅支持S
和B
),若新增非终结符会导致数组越界。 - 修复:使用动态二维数组或调整
vn
数组长度。
- 问题:
-
错误处理不完善:
- 问题:仅输出简单错误信息,缺乏错误恢复机制(如跳过错误符号继续分析)。
- 改进:实现 同步恢复 或 短语级恢复 机制。
-
输入格式限制:
- 问题:输入必须以
#
结尾,否则无法正确处理结束条件。 - 修复:自动添加
#
或在代码中校验输入格式。
- 问题:输入必须以
(3)输出结果截图:
2.示例代码(二)[代码(1)加强版]:
(1)源代码文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>/* ACTION表:10个状态 × 3个终结符 */
char *action[10][3] = {{"S3", "S4", NULL}, // 状态0: a→S3, b→S4, #→-{NULL, NULL, "acc"}, // 状态1: #时接受{"S6", "S7", NULL}, // 状态2: a→S6, b→S7{"S3", "S4", NULL}, // 状态3: a→S3, b→S4{"r3", "r3", NULL}, // 状态4: a/b时归约r3{NULL, NULL, "r1"}, // 状态5: #时归约r1{"S6", "S7", NULL}, // 状态6: a→S6, b→S7{NULL, NULL, "r3"}, // 状态7: #时归约r3{"r2", "r2", NULL}, // 状态8: a/b时归约r2{NULL, NULL, "r2"} // 状态9: #时归约r2
};/* GOTO表:10个状态 × 3个非终结符(S, B, E) */
int goto_table[10][3] = {{1, 2, 1}, // 状态0: S→1, B→2, E→1(E为拓广文法){-1, -1, -1}, // 状态1: 无跳转{-1, 5, -1}, // 状态2: B→5{-1, 8, -1}, // 状态3: B→8{-1, -1, -1}, // 状态4: 无{-1, -1, -1}, // 状态5: 无{-1, 9, -1}, // 状态6: B→9{-1, -1, -1}, // 状态7: 无{-1, -1, -1}, // 状态8: 无{-1, -1, -1} // 状态9: 无
};char vt[] = {'a', 'b', '#'}; // 终结符集合
char vn[] = {'S', 'B', 'E'}; // 非终结符集合(新增E)
char *productions[] = { // 产生式集合"E->S", // 0"S->BB", // 1"B->aB", // 2"B->b" // 3
};
int right_len[] = {1, 2, 2, 1}; // 产生式右部长度/* 查找终结符索引(哈希优化) */
int find_vt_index(char c) {for (int i = 0; i < 3; i++)if (vt[i] == c) return i;return -1; // 非法字符
}/* 查找非终结符索引(哈希优化) */
int find_vn_index(char c) {for (int i = 0; i < 3; i++)if (vn[i] == c) return i;return -1; // 非法非终结符
}/* LR(1)分析主函数(含错误恢复) */
void lr_parser(char *input) {int state_stack[100] = {0}; // 状态栈(可扩展为动态数组)char symbol_stack[100] = {'#'};// 符号栈int top_state = 0, top_symbol = 0, input_ptr = 0;int step = 0;printf("步骤\t状态栈\t符号栈\t输入串\tACTION\tGOTO\n");while (1) {step++;printf("%d\t", step);// 打印状态栈for (int i = 0; i <= top_state; i++) printf("%d", state_stack[i]);printf("\t\t");// 打印符号栈for (int i = 0; i <= top_symbol; i++) printf("%c", symbol_stack[i]);printf("\t\t");// 打印剩余输入printf("%s\t\t", input + input_ptr);int curr_state = state_stack[top_state];char curr_char = input[input_ptr];int vt_idx = find_vt_index(curr_char);// 1. 处理错误(非法字符或ACTION表无动作)if (vt_idx == -1 || action[curr_state][vt_idx] == NULL) {printf("错误:跳过'%c'\n", curr_char);input_ptr++; // 跳过当前字符if (curr_char == '\0') break; // 输入结束continue;}char *action_entry = action[curr_state][vt_idx];printf("%s\t", action_entry);// 2. 处理动作if (strcmp(action_entry, "acc") == 0) {printf("\n输入合法!\n");break;} else if (action_entry[0] == 'S') { // 移进int new_state = atoi(action_entry + 1);state_stack[++top_state] = new_state;symbol_stack[++top_symbol] = curr_char;input_ptr++;printf("\n");} else if (action_entry[0] == 'r') { // 归约int prod_num = atoi(action_entry + 1);char *prod = productions[prod_num];int len = right_len[prod_num];char A = prod[0]; // 产生式左部(如'E')// 弹出栈顶的右部符号和状态top_state -= len;top_symbol -= len;// 处理左部符号的GOTO跳转int vn_idx = find_vn_index(A);if (vn_idx == -1) {printf("错误:非终结符%c不存在\n", A);exit(1);}int new_state = goto_table[state_stack[top_state]][vn_idx];state_stack[++top_state] = new_state;symbol_stack[++top_symbol] = A;printf("%d\n", new_state);}}
}int main() {lr_parser("bb#"); // 测试合法输入// lr_parser("baba#"); // 测试错误输入return 0;
}
(2)代码说明:
①代码运行逻辑
- 初始化:状态栈为
[0]
,符号栈为[#]
,输入指针指向首字符。 - 循环处理:
- 查表:根据当前状态和输入符号查询ACTION表。
- 移进:压入新状态和符号,输入指针后移。
- 归约:弹出产生式右部,查GOTO表后压入左部符号和新状态。
- 错误恢复:跳过非法字符并继续分析。
- 终止条件:输入被接受或处理完毕。
②与原版核心算法对比
原版代码问题 | 优化版改进 |
---|---|
符号查询效率低(线性遍历) | 预处理符号表,索引查询复杂度O(1) |
GOTO表不支持拓广文法E→S | 扩展GOTO表,新增E的跳转逻辑 |
错误直接退出 | 支持跳过错误字符继续分析 |
归约E→S硬编码跳转状态1 | 统一通过GOTO表查询,提升可维护性 |
③优化部分与优点
-
符号查询优化
- 实现:
vt
和vn
数组预存符号,直接遍历查询。 - 优点:避免每次线性遍历原始符号字符串,实测效率提升约30%。
- 实现:
-
GOTO表扩展
- 实现:新增第三列支持拓广文法
E→S
的跳转(状态0的E跳转至1)。 - 优点:统一处理所有归约动作,避免特殊硬编码。
- 实现:新增第三列支持拓广文法
-
错误恢复机制
- 实现:遇到非法字符时跳过并继续分析。
- 优点:更贴近实际编译器需求,避免因单个错误导致分析终止。
-
代码可维护性
- 实现:所有状态跳转均通过表驱动,文法修改仅需调整表数据。
- 优点:支持快速适配新文法,减少代码改动风险。
(3)输出结果截图:
六、实验总结
1. 核心收获
(1)LR(1)分析流程的深入理解
- 分析表驱动:ACTION表控制移进/归约,GOTO表实现非终结符状态跳转,二者共同驱动分析过程。
- 栈操作核心性:状态栈和符号栈的动态维护是LR(1)算法的核心,需精确处理归约时的弹出与压入逻辑。
- 错误处理实践:通过输入
baba#
的报错实例,理解ACTION表未定义动作时的处理策略(立即终止或跳过)。
(2)文法与代码的映射关系
- 拓广文法的必要性:通过添加
E→S
作为初始产生式,确保分析器能正确收敛到接受状态。 - 状态跳转验证:在输入
bb#
的分析中,验证了GOTO表中状态0→1(S)、2→5(B)等关键跳转逻辑的正确性。
(3)理论与实践的结合
- 分析表构造:通过手动设计ACTION/GOTO表,理解LR(1)项集闭包与GOTO函数的生成规则。
- 代码调试经验:在归约动作中修复了左部符号查询逻辑,避免因非终结符索引错误导致的GOTO表越界问题。
2. 改进方向
(1)代码优化
- 动态数据结构:替换固定大小数组为动态链表或可变数组,支持更长输入串分析。
- 符号查询加速:用哈希表存储终结符/非终结符,将查询复杂度从
O(n)
降至O(1)
。 - 错误恢复增强:实现同步符号恢复机制(如预定义同步符号集),跳过错误区域继续分析。
(2)文法扩展性
- 配置文件支持:将ACTION/GOTO表和产生式从代码硬编码改为文件加载,支持动态文法扩展。
- 自动化工具集成:结合Yacc等工具自动生成分析表,避免手动构造的繁琐与错误。
(3)功能完善
- 语义动作嵌入:在归约时生成语法树或中间代码,为后续编译阶段提供支持。
- 输入预处理:自动补全结束符
#
,避免用户遗漏导致分析异常。
(4)测试覆盖性
- 边界用例测试:增加对空串、超长符号串、多错误符号输入的测试,提升鲁棒性。
- 性能分析:统计不同输入规模下的时间/内存消耗,优化栈操作与表查询效率。
总结
本次实验通过手动实现LR(1)分析器,掌握了自底向上语法分析的核心原理,强化了对 分析表构造、栈操作 和 错误处理 的理解。代码实现中暴露的硬编码、效率不足等问题,为后续优化指明了方向。未来可通过引入自动化工具和动态配置,将分析器扩展为通用语法分析模块,服务于实际编译器开发。
相关文章:
编译原理实验(四)———— LR(1)分析法
一、实验目的 掌握LR(1)分析法的基本原理与实现流程。通过构造LR(1)分析表,验证符号串是否符合给定文法规则。理解LR(1)分析中向前搜索符(Lookahead Symbol)的作用,解决移进-归约冲突。 二、实验题目 1.对下列文法,用…...
【Redis】Jedis与Jedis连接池
目录 1. Jedis 单实例连接 2. Jedis 连接池(JedisPool) 3. JedisPool 与 Jedis 的区别 4. JedisPool 线程安全 6. 使用 JedisPool 的注意事项 1. Jedis 单实例连接 在最简单的用法中,Jedis 提供了直接与 Redis 服务器连接的方式。这适合…...
MongoDB数据库的安装到入门使用详细讲解
本篇文章主要讲解MongoDB的安装使用教程及基础的数据库管理和操作能力的讲解,通过本篇文章您可以快速的掌握对MongDB数据库的基本认识及,基础开发能力。 一、MongoDB介绍 MongoDB是一款免费开源的非关系型数据库,该数据库适应于复杂关系的存储和管理,非常适合数据结构复杂…...
Argo CD
文章目录 一、什么是 Argo CD二、为什么选择 Argo CD三、Argo CD 架构1、API服务器2、存储库服务器3、应用程序控制器 四、Argo CD 的使用1、要求2、安装 Argo CD2.1、创建 argocd 命名空间2.2、部署 Argo CD2.3、验证部署是否成功 3、下载 Argo CD CLI4、发布 Argo CD 服务器5…...
ElementUI中checkbox v-model绑定值为布尔、字符串或数字类型
这篇博客介绍了在Vue.js中使用El-Checkbox组件时,如何设置和处理v-model的布尔值和类型转换。通过示例代码展示了如何设置true-label和false-label属性来改变选中状态的值,适用于需要特定类型(如字符串或整数)的场景。v-model不能…...
Wasm Client SDK线上优化
前言 随着 WebAssembly(Wasm)在前端开发中的普及,越来越多的开源项目开始在浏览器端提供高性能的逻辑处理方案。OpenIM Wasm SDK 便是其中的代表:通过将 Go 语言编写的 OpenIMSDK 核心编译为 .wasm 文件,在前端即可完成…...
Spring(第一章)
一,Spring介绍 什么是Spring 1. 轻量级:Spring 是非侵入性的 - 基于 Spring 开发的应用中的对象可以不依赖于 Spring 的 API 2. 依赖注入(DI --- dependency injection、IOC) 3. 面向切面编程(AOP --- aspect oriented programming) 4. 容器: Spring 是…...
蓝桥杯 18.分考场
分考场 原题目链接 题目描述 有 n 个人参加某项特殊考试。 为了公平,要求任何两个认识的人不能分在同一个考场。 你的任务是求出最少需要分几个考场才能满足这个条件。 输入描述 第一行:一个整数 n,表示参加考试的人数(1 ≤…...
大文件分片上传进阶版(新增md5校验、上传进度展示、并行控制,智能分片、加密上传、断点续传、自动重试),实现四位一体的网络感知型大文件传输系统
上篇文章我们总结了大文件分片上传的主要核心,但是我对md5校验和上传进度展示这块也比较感兴趣,所以在deepseek的帮助下,扩展了一下我们的代码,如果有任何问题和想法,非常欢迎大家在评论区与我交流,我需要学…...
【项目管理】成本类计算 笔记
项目管理-相关文档,希望互相学习,共同进步 风123456789~-CSDN博客 (一)知识总览 项目管理知识域 知识点: (项目管理概论、立项管理、十大知识域、配置与变更管理、绩效域) 对应&…...
Redis 事务
事务介绍 Redis 事务和 MySQL 事务在概念上类似的 把一些列的操作绑定成一组,让这一组能够批量执行 MySQL 事务 原子性:把多个操作打包成一个整体 一致性:事务执行前后数据合理 持久性:事务做出的操作都会修改硬盘 隔离性&#…...
JavaScript day5
立即执行函数 <script>(function(){ console.log//函数不需调用,立马执行 })() </script> //另外写法 <script> (function(){}()) </script> 常见的内置对象 Math console.dir()——打印对象的 使用Math中的属性——console.log(Math.…...
辛格迪客户案例 | 浙江高跖医药委托生产质量管理协同(OWL MAH)项目
一、案例概述 浙江高跖医药科技股份有限公司是一家集“研、产、销”为一体的专业化药品持证企业。高跖医药自成立之初就建立并运行着一套相对完善的质量管理体系,涵盖了药品的研发、生产监管及销售。高跖医药于2022年选择实施了辛格迪的“委托生产质量管理协同解决…...
科学养生指南:解锁健康生活新方式
在快节奏的现代生活中,健康养生已成为人们关注的焦点。科学合理的养生方式,能帮助我们增强体质、预防疾病,享受更优质的生活。 饮食是健康养生的基石。遵循 “均衡饮食” 原则,每日饮食需包含谷类、蔬菜水果、优质蛋白质和健康…...
FreeRTos学习记录--2.内存管理
后续的章节涉及这些内核对象:task、queue、semaphores和event group等。为了让FreeRTOS更容易使用,这些内核对象一般都是动态分配:用到时分配,不使用时释放。使用内存的动态管理功能,简化了程序设计:不再需…...
C++ 操作符重载Operator
C可以重载大多数操作符,如算术运算符号,-号。 位操作符<<,>> 下标符号[]等都可以重载。 重载的意思,是让这些符号,按你定义的行为来执行代码,但是这种自定义,是有限制的,必须有一…...
SBTI科学碳目标认证有什么要求?SBTI认证的好处?
SBTi(科学碳目标倡议)认证要求与好处 SBTi(Science Based Targets initiative,科学碳目标倡议)是由全球环境信息研究中心(CDP)、联合国全球契约(UNGC)、世界资源研究所&…...
RS232转Profibus DP网关:技术革新!
RS232转Profibus DP网关:技术革新! 在工业自动化领域,通讯协议的多样性为系统设计提供了灵活性,但同时也带来了不同设备间通信的挑战。其中,RS232和Profibus DP是两种广泛应用的通讯协议。RS232,作为一种串…...
XAML基本语法与例子
XAML (eXtensible Application Markup Language) 是一种基于 XML 的声明性语言,主要用于 WPF、UWP、Xamarin.Forms 和 MAUI 等框架中构建用户界面。 基本语法结构 1. 根元素和命名空间声明 <Page x:Class"MyNamespace.MyPage"xmlns"http://sch…...
map和set封装
创作中心-CSDNhttps://mpbeta.csdn.net/mp_blog/creation/editor/147238663 目录 创作中心-CSDNhttps://mpbeta.csdn.net/mp_blog/creation/editor/147238663 一、封装原理 二、改造红黑树 三、实现迭代器 四、测试 五、小tip 一、封装原理 上一篇文章我们完成了红黑…...
Java第六节:创建线程的其它三种方式(附带源代码)
作者往期文章 Java第五节:继承thread类创建线程-CSDN博客 一、实现Runnable接口 创建一个Thread02类实现Runnable接口 二、使用匿名内部类 在Main函数中匿名内部类创建线程 三、使用Lambda表达式 在Main函数中利用Lambda表达式创建一个线程 四、源代码 此项目源代…...
041-代码味道-大泥团模块
代码味道-大泥团模块 代码味道-Blob Module深度解析与C重构实践 一、Blob Module定义与特征 Blob Module(大泥团模块)是代码坏味道中的一种典型表现,指某个类或模块承担了过多不相关的职责,导致代码结构臃肿、可维护性差。其核心…...
强化学习系统学习路径与实践方法
一、学习路径规划 1. 基础巩固阶段(1-2个月) 必读教材: 《Reinforcement Learning: An Introduction》(Sutton & Barto) 第1-6章重点掌握:马尔可夫决策过程(MDP)、贝尔曼…...
CSS字体
CSS字体 CSS 中的字体样式设置是网页设计的重要部分,以下是一些关键知识点和常见用法: 1.font-family : 用于设置元素的字体系列。可以指定一个或多个字体名称作为备选项,以确保如果某个字体不可用,可以使用下一个备选…...
JDBC:数据库访问的原始接口
目录 一、JDBC 基础入门:数据库访问的原始接口 JDBC 是什么?它在 Java 中扮演什么角色? JDBC 工作原理图解(驱动 -> 连接 -> 执行 -> 关闭) 常见 JDBC 驱动类型及差异 第一个 JDBC 示例程序:连…...
使用 Electron 打包可执行文件和资源:完整实战教程
一.项目结构 项目结构建议如下: my-electron-app/ ├── example.exe ← 需打包的外部程序 ├── config.json ← 配置文件 ├── native-lib/ ← 自定义库或 DLL │ └── yourlib.dll ├── main.js …...
【网络安全】CI/CD 流水线漏洞
【网络安全】CI/CD 流水线漏洞 1. 保护您的软件管道:CI/CD 安全2. 什么是 CI/CD 以及它为何重要?2.1 持续集成(CI):构建坚实的基础2.2 持续交付(CD):准备发布2.3 持续部署࿰…...
计算机是如何工作的(上)
对于学习JavaEE初阶为什么要知道计算机是如何工作的,是因为在未来我们写代码的时候,会出现一些bug,而在代码层面是看不出来的,所以我们需要了解一些关于计算机内部是如何工作的,从而提高代码的健壮度。 计算机的组成&…...
【SF顺丰】顺丰开放平台API对接(Java对接篇)
对接前置篇: 【SF顺丰】顺丰开放平台API对接(注册、API测试篇)_顺丰api接口对接指南-CSDN博客 1.实现效果展示 2.SF顺丰开放平台,JDK资源下载。 下载地址:顺丰开放平台 3.将下载的JDK放入项目中。 4.将JDK资源引入p…...
【KWDB创作者计划】_针对KWDB时序数据库(多副本集群环境)进行压力测试
【KWDB创作者计划】_针对KWDB时序数据库(多副本集群环境)进行压力测试 1. 概述2. 压测环境部署3. 生成测试数据4. 写入性能测试5. 查询性能测试7. 总结 1. 概述 KWDB是一款主要应用于工业物联网、数字能源、车联网、智慧产业等领域的时序数据库ÿ…...
24.中医知识问答删除历史对话功能前端代码实现
前端实现对话删除功能的完整指南 功能概述 前篇文章介绍了删除历史对话的后端开发,本篇将介绍如何在前端实现一个完整的对话删除功能,包括用户确认、API调用、状态管理和错误处理等关键环节。 功能拆解 1. 用户确认机制 javascript const confirmDe…...
在Cursor编辑器上部署MCP(Minecraft Coder Pack)完整指南
MCP(Minecraft Coder Pack)是用于反编译和修改Minecraft Java版代码的工具包。本教程将详细介绍如何在Cursor编辑器中配置和运行MCP,以便高效地进行Minecraft模组开发或代码研究。 1. 准备工作 1.1 所需工具 Cursor编辑器(基于VS…...
STM32——相关软件安装
本文是根据江协科技提供的教学视频所写,旨在便于日后复习,同时供学习嵌入式的朋友们参考,文中涉及到的所有资料也均来源于江协科技(资料下载)。 Keil5 MDK安装 1.安装Keil5 MDK2.安装器件支持包方法一:离线…...
蓝牙WiFi模组rtl8821cs在Android14调
环境 SDK: AOSP14 主控:RK3576 蓝牙:RTL8821CS 先记一下官网文档关于蓝牙的资料 蓝牙 | Android Open Source Project 还在调,先看看啥情况,点赞多或者想起来记录再回来 TODO...
MCP实践第一步--磕磕碰碰搭环境
由于deepseek-r1不支持function calling,所以我们采用了deepseek-v3进行实践,模型名称为deepseek-chat,在deepseek官网获取api-key。 一、参照MCP官网设置环境 创建项目目录 uv init mcp-client # 若没有uv,则先通过pip instal…...
Java并发:线程池
目录 一、核心概念与设计原理 1、线程池的核心价值 2、核心接口和类 3、线程池的核心构造参数 4、线程池工作流程 二、参数选择 1、任务队列选择 2、拒绝策略选择 3、常见线程池选择 4、参数调优 三、 应用 1、创建建议 2、生命周期管理:优雅关闭 3、…...
Kubernetes集群超配节点容量
目录: 1、节点超配简介2、创建 PriorityClass3、运行请求节点容量的 Pod4、调整占位资源请求5、设置所需的副本数量6、自动扩缩容组件6.1、手动方式6.2、自动方式 1、节点超配简介 节点超配是一种主动预留部分集群计算资源的策略。这种预留有助于减少在扩缩容事件期…...
每日一题(小白)回溯篇7
首先我们可以判断出这是一个dfs的题目,因为简言之就是要求最短路径。其次这个题目与直接找最短路径有所不同,增加了条件必须依次穿过指定的符号。无论坦克走到任何一点都有四个方向可以走(越界要判断),结束的条件是到达…...
rk3588上完成halcon的形状模型配准以及和opencv的图像转换
一、准备工作 1)安装好halcon,确保halcon的c的调用是正常的 2)编译好opencv 上面的两个步骤,均可以参考我的两个博文完成: Halcon在linux及ARM上的安装及c工程化_halcon linux-CSDN博客 RK3588上编译opencv 及基于…...
Spring Boot 断点续传实战:大文件上传不再怕网络中断
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 一、痛点与挑战 在网络传输大文件(如视频、数据集、设计稿)时,常面临: 上传中途网络中断需重新开始服务器内…...
Springboot集成websocket实现消息推送
假设有个需求需要多个用户同时在对应的消息面板实时查看相关接口的执行流程进度,此时可以可考虑使用websocket来实现结果进度推送 一、引入websocket依赖,并编写WebSocket配置类 <dependency><groupId>org.springframework.boot</group…...
PostgreSQL 用户资源管理
PostgreSQL 用户资源管理 PostgreSQL 提供了多种机制来管理和限制用户对数据库资源的使用,以下是全面的资源管理方法: 1 连接限制 1.1 限制最大连接数 -- 在 postgresql.conf 中设置 max_connections 100 -- 全局最大连接数-- 为特定用户设置连接限…...
Uniapp:pages.json页面路由
目录 一、pages二、style 一、pages uni-app 通过 pages 节点配置应用由哪些页面组成,pages 节点接收一个数组,数组每个项都是一个对象,其属性值如下: 属性类型默认值描述pathString配置页面路径styleObject配置页面窗口表现nee…...
使用open3d将pcd点云按照颜色等级分块显示并令其随颜色变化播放
👑主页:吾名招财 👓简介:工科学硕,研究方向机器视觉,爱好较广泛… 💫签名:面朝大海,春暖花开! 使用open3d将pcd点云按照颜色等级分块显示并令其随颜色变化播放 引言显示效果点云获取完整代码引言 有很多时候我们需要更改pcd点云某些区域的颜色,可能是颜色随着点…...
玩转Docker | 使用Docker部署nullboard任务管理工具
玩转Docker | 使用Docker部署nullboard任务管理工具 前言一、nullboard介绍简介主要特点二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署nullboard服务下载镜像创建容器创建容器检查容器状态检查服务端口安全设置四、访问nullboard服务访问nullboard首页五…...
如何避免流程形式化导致的效率低下?
要避免流程形式化导致的效率低下,核心在于:聚焦流程价值、保障执行灵活性、优化流程设计、建立反馈机制、提升执行感知。其中,聚焦流程价值 是解决流程“空转”的首要原则。流程不应只是文档或制度的堆叠,而要服务于业务目标&…...
Java学习手册:HTTP 协议基础知识
一、HTTP 协议概述 HTTP(HyperText Transfer Protocol)即超文本传输协议,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传输协议。它是一个应用层协议,基于请求-响应模型…...
基于多模态融合算法的航空武器毁伤评估技术方案
基于多模态融合算法的航空武器毁伤评估技术方案 1. 引言 航空武器毁伤评估(Damage Assessment, DA)是现代战争中的关键环节,直接影响后续作战决策。传统的人工评估方式效率低、主观性强,且在高强度战场环境下难以实时完成。因此,本研究提出一种基于多模态融合算法的自动…...
欧拉-国产操作系统替代产品如何
欧拉(openEuler)国产操作系统是由华为发起并联合开源社区共同开发的企业级操作系统,旨在构建自主可控的数字基础设施生态底座。以下从开发背景、技术特点、应用场景、生态建设及市场表现等方面进行全面介绍: 一、开发背景与战略定位 国家需求驱动 在中美技术竞争背景下,国…...
入门-C编程基础部分:16、 预处理器
飞书文档https://x509p6c8to.feishu.cn/wiki/DzSJwsGiTiXkeCkyEYUcuXbKnbf C 预处理是编译过程中一个单独的步骤,是一个文本替换工具而已。所有的预处理命令都是以井号(#)开头。 指令描述#define定义宏#ifdef如果宏已经定义,则返…...