当前位置: 首页 > news >正文

编译原理实验3——自下而上的SLR1语法分析实现(包含画DFA转换图、建表、查表)

文章目录

    • 实验目的
    • 实现流程
      • 定义DFA状态
      • 实现代码
      • 运行结果
        • 测试1
        • 测试2
        • 测试3
    • 总结

实验目的

实现自下而上的SLR1语法分析,画出DFA图

实现流程

定义DFA状态

class DFA:def __init__(self, id_, item_, next_ids_):self.id_ = id_  # 编号self.item_ = item_  # productionsself.next_ids_ = next_ids_  # { v1:id1 , v2:id2 ...}def __eq__(self, other):return set(self.item_) == set(other.item_)

类属性:

  • id_:一个数字,表示状态的编号
  • item_:一个集合,表示状态所含有的项目集
  • next_ids_:一个字典,表示其可跳转的状态关系,key值表示经过什么符号跳转,value值表示跳转到的状态编号

类方法:

  • 判断两个状态是否相等,依据这两个状态所包含的项目集判断,若项目集中包含的元素完全相同,则这两个状态相等。
    例如:
    在这里插入图片描述
    如图,状态I0可以通过S、B分别跳转到I1、I2,则其DFA状态类为:
    • id_:0
    • item_: { S’->.S, S->.BB, B->.aB, B->.b }
    • next_ids_: { ‘S’: 1 , ‘B’:2 }

实现代码

代码逻辑
1.计算所有项目
在这里插入图片描述
如图,首先找到字符串中 -> 的位置索引,python中字符匹配得到的是 - 的索引,记为ind,因此再ind=ind+2即可得到候选式首个字符a的位置,以这个位置为起点,遍历到末尾,加入小数点即可得到一个新的项目,如:当得到ind=1时,
item1 = str[:ind+2+0] + ’.’ + str[ind+2+0:] = “B->” + “.” + “ab” = “B->.ab”
item2 = str[:ind+2+1] + ’.’ + str[ind+2+1:] = “B->a” + “.” + “b” = “B->a.b”
item3 = str[:ind+2+2] + ’.’ + str[ind+2+2:] = “B->ab” + “.” + “” = “B->ab.”
(得益于python简单的切片索引,可以通过str[start:end]取得str中索引位置为start到end-1的子字符串。)

2.计算闭包
自定义一个closure函数

  • 输入:待求闭包的项目集item
  • 输出:项目集item的闭包
  • 求解过程:
    • 遍历项目集,对于其中的每个项目,判断“.”的位置在哪:
    • 如果“.”在最后,则表明是归约项目,不再对其进行闭包。
    • 如果“.”不在最后,看“.”后面的字符是否为非终结符,找到该非终结符所对应的产生式的项目,并将这些项目加入闭包中。
    • 再对求过一次闭包后的新的项目集再求其闭包,方法同上,直至求出的新的项目集不再有新的项目产生。(前后两次闭包后,项目集没有加入新的项目,长度不变)

3.计算GO闭包
自定义一个go函数

  • 输入:待求闭包的项目集item,跳转字符v
  • 输出:项目集item的闭包
  • 求解过程:
    • 遍历项目集,找出“.”右边为字符v的产生式,并将“.”移动到字符v后面,得到新的产生式集item2
    • 将item2代入上述closure函数中求解闭包。

4.构建DFA状态转换图
生成初始状态0:将增广文法所在产生式传入closure函数求解闭包,记为S0_item,根据DFA类定义初始状态:S0 = DFA(0, S0_item, {}),即状态编号为0,项目集为S0_item,跳转关系为空字典。然后将初始状态加入到存储所有状态的列表:all_DFA。即 all_DFA = [S0]
遍历all_DFA中的所有状态,求解每个状态中项目集的Go闭包,记录新的状态。遍历完一轮all_DFA后,继续遍历all_DFA,直到前后两次all_DFA长度不变(没有新的状态产生)

5.判断是否为SLR1文法:

  • 先判断是否符合LR0文法
    遍历all_DFA中的所有状态S,遍历状态S的项目集的所有产生式,若“.”在末尾,则为归约项目,若“.”后为终结符,则为移进项目,统计归约项目和移进项目个数,若存在2个及以上的归约项目或存在1个移进项目和1个归约项目,则相应存在“归约-归约冲突”和“移进-归约冲突”,不符合LR0文法。
  • 判断是否符合SLR1文法
    如图,依据归约项目和移进项目的follow集进行判断
    在这里插入图片描述

6.构造SLR1表:
定义actions={} 、 gotos={},分别表示action表和gotos表,key值为一个二元组,如(1,’#’),表示状态1和字符’#’所对应的单元格。
遍历all_DFA中的所有状态S:

  • 如果S的next_ids_为空字典,即它没有箭头指向其他状态,则S的项目集中必只包含一个项目,为归约项目或接受项目。
    - 若该项目去除“.”后与增广文法所在产生式相等,该项目为接受项目,记录actions[ (id_,’#’) ]=“acc”,id_为当前状态S的编号。
    - 否则,则为归约项目。将该产生式依据“->”拆成左部和右部,遍历左部的follow集,记录actions[ (id_,ch) ] = rn,ch为左部follow集的元素,rn为产生式编号。
  • 如果S的next_ids字典不为空,即表明有指向下一个项目的箭头,从而当前状态S可能存在接受、归约、移进等项目。
    - 遍历S的项目集:判断是否为接受项目和归约项目同上
    - 遍历S的next_ids字典:若key值为终结符,记录actions[ (id_, key) ] = S+value,value为下一个状态的编号;若key值为非终结符,记录gotos[ (id_, key) ] = value。

7.根据输入串查表分析
建立变量,状态栈state、符号栈symbol、字符指针sp。

  • 给state加入0,symbol加入#,sp=0
  • 取出state栈顶状态St和symbol栈顶符号Sy,如果actions的key中不存在(St,Sy)二元组,则分析失败,否则进行下一步。
  • 用(St,Sy)查actions得到动作a
    • 如果a是Sn,为移进操作,将状态Sn加入到状态栈state,将sp指向的字符加入到符号栈中symbol,并且sp+=1
    • 如果a是rn, 为归约操作,找到产生式rn,将符号栈symbol中对应产生式右部的字符弹出,也将状态栈state对应产生式右部字符个数的状态弹出。取此时state和symbol栈顶元素St、Sy,查gotos表,如果gotos的key中不存在(St,Sy)二元组,则分析失败,否则将查到的对应的状态加入状态栈state中。
    • 如果a是“acc”,分析成功,退出循环
  • 循环条件为sp!=分析串长度

需要用到graphviz库,用于画DFA转换图,自行百度搜索下载,不难
(下载graphviz.exe软件–>改环境变量–>python安装graphviz库)

import copy
from collections import defaultdict
import graphviz
import pandas as pdclass DFA:def __init__(self, id_, item_, next_ids_):self.id_ = id_  # 编号self.item_ = item_  # productionsself.next_ids_ = next_ids_  # { v1:id1 , v2:id2 ...}def __eq__(self, other):return set(self.item_) == set(other.item_)class FirstAndFollow:def __init__(self, formulas_str_list):self.formulas_str_list = formulas_str_listself.formulas_dict = defaultdict(set)self.first = defaultdict(set)self.follow = defaultdict(set)self.S = ""self.Vn = set()self.Vt = set()self.info = {}def process(self, formulas_str_list):formulas_dict = defaultdict(set)  # 存储产生式 ---dict<set> 形式# 转为特定类型for production in formulas_str_list:left, right = production.split('->')if "|" in right:r_list = right.split("|")for r in r_list:formulas_dict[left].add(r)else:formulas_dict[left].add(right)  # 若left不存在,会自动创建 left: 空setS = list(formulas_dict.keys())[0]  # 文法开始符Vn = set()Vt = set()for left, right in formulas_dict.items():  # 获取终结符和非终结符Vn.add(left)for r_candidate in right:for symbol in r_candidate:if not symbol.isupper() and symbol != '@':Vt.add(symbol)# 消除左递归# formulas_dict = self.eliminate_left_recursion(formulas_dict)return formulas_dict, S, Vn, Vtdef cal_v_first(self, v):  # 计算符号v的first集# 如果是终结符或ε,直接加入到First集合if not v.isupper():self.first[v].add(v)else:for r_candidate in self.formulas_dict[v]:i = 0while i < len(r_candidate):next_symbol = r_candidate[i]# 如果是非终结符,递归计算其First集合if next_symbol.isupper():if next_symbol == v:breakself.cal_v_first(next_symbol)self.first[v] = self.first[v].union(self.first[next_symbol] - {'@'})  # 合并first(next_symbol)/{@}if '@' not in self.first[next_symbol]:break# 如果是终结符,加入到First集合else:self.first[v].add(next_symbol)breaki += 1# 如果所有符号的First集合都包含ε,将ε加入到First集合if i == len(r_candidate):self.first[v].add('@')def cal_all_first(self):for vn in self.formulas_dict.keys():self.cal_v_first(vn)for vt in self.Vt:self.cal_v_first(vt)self.cal_v_first('@')# def: 计算Follow集合1——考虑 添加first(Vn后一个非终结符)/{ε}, 而 不考虑 添加follow(left)def cal_follow1(self, vn):self.follow[vn] = set()if vn == self.S:  # 若为开始符,加入#self.follow[vn].add('#')for left, right in self.formulas_dict.items():  # 遍历所有文法,取出左部单Vn、右部候选式集合for r_candidate in right:  # 遍历当前 右部候选式集合i = 0while i <= len(r_candidate) - 1:  # 遍历当前 右部候选式if r_candidate[i] == vn:  # ch == Vnif i + 1 == len(r_candidate):  # 如果是最后一个字符  >>>>>  S->....Vself.follow[vn].add('#')breakelse:  # 后面还有字符  >>>>> S->...V..while i != len(r_candidate):i += 1# if r_candidate[i] == vn:  # 又遇到Vn,回退 >>>>> S->...V..V..#     breakif r_candidate[i].isupper():  # 非终结符  >>>>> S->...VA..self.follow[vn] = self.follow[vn].union(self.first[r_candidate[i]] - {'@'})if '@' in self.first[r_candidate[i]]:  # 能推空  >>>>> S->...VA..  A可推空if i + 1 == len(r_candidate):  # 是最后一个字符  >>>>> S->...VA  A可推空 可等价为 S->...Vself.follow[vn].add('#')breakelse:  # 不能推空 >>>>> S->...VA..  A不可推空breakelse:  # 终结符  >>>>> S->...Va..self.follow[vn].add(r_candidate[i])breakelse:i += 1# def: 计算Follow集合2——考虑 添加follow(left)def cal_follow2(self, vn):for left, right in self.formulas_dict.items():  # 遍历所有文法,取出左部单Vn、右部候选式集合for r_candidate in right:  # 遍历当前 右部候选式集合i = 0while i <= len(r_candidate) - 1:  # 遍历当前 右部候选式if r_candidate[i] == vn:  # 找到Vnif i == len(r_candidate) - 1:  # 如果当前是最后一个字符,添加 follow(left) >>>>>  S->..Vself.follow[vn] = self.follow[vn].union(self.follow[left])breakelse:  # 看看后面的字符能否推空 >>>>>  S->..V..while i != len(r_candidate):i += 1if '@' in self.first[r_candidate[i]]:  # 能推空  >>>>> S->..VB..  B可推空if i == len(r_candidate) - 1:  # 且是最后一个字符  >>>>> S->..VB  B可推空self.follow[vn] = self.follow[vn].union(self.follow[left])breakelse:  # 不是最后一个字符,继续看  >>>>> S->..VBA..  B可推空continueelse:  # 不能推空  >>>>>  S->..VB..  B不可为空breaki += 1# def: 计算所有Follow集合的总长度,用于判断是否还需要继续完善def cal_follow_total_Len(self):total_Len = 0for vn, vn_follow in self.follow.items():total_Len += len(vn_follow)return total_Lendef cal_all_follow(self):# 先用 cal_follow1 算for vn in self.formulas_dict.keys():self.cal_follow1(vn)# 在循环用 cal_follow2 算, 直到所有follow集总长度不再变化,说明计算完毕while True:old_len = self.cal_follow_total_Len()for vn in self.formulas_dict.keys():self.cal_follow2(vn)new_len = self.cal_follow_total_Len()if old_len == new_len:breakdef solve(self):# print("\n=============FirstFollow=============")self.formulas_dict, self.S, self.Vn, self.Vt = self.process(self.formulas_str_list)self.cal_all_first()self.cal_all_follow()# print(f"first: {self.first}")# print(f"follow: {self.follow}")# print("=============FirstFollow=============\n")return self.first, self.followclass SLR1:def __init__(self, formulas_str_list):self.formulas_str_list = formulas_str_list  # listself.S = ""self.Vn = []self.Vt = []self.dot_items = []  # 所有可能的.项目集self.all_DFA = []self.actions = []self.gotos = []self.first = defaultdict(set)self.follow = defaultdict(set)def step1_pre_process(self, grammar_list):formulas_str_list = []S = grammar_list[0][0]  # 开始符Vt = []  # 终结符Vn = []  # 非终结符for pro in grammar_list:pro_left, pro_right = pro.split("->")if "|" in pro_right:r_list = pro_right.split("|")for r in r_list:formulas_str_list.append(pro_left + "->" + r)else:formulas_str_list.append(pro)# 增广文法formulas_str_list.insert(0, S + "'->" + S)# print(formulas_str_list)for pro in formulas_str_list:pro_left, pro_right = pro.split("->")if pro_left not in Vn:Vn.append(pro_left)for r_candidate in pro_right:for symbol in r_candidate:if not symbol.isupper() and symbol != '@':if symbol not in Vt:Vt.append(symbol)print("S:",S)print("Vn:", Vn)print("Vt:", Vt)ff = FirstAndFollow(formulas_str_list)first, follow = ff.solve()return S, Vn, Vt, formulas_str_list, first, followdef step2_all_dot_pros(self, grammar_str):dot_items = []for pro in grammar_str:ind = pro.find("->")  # 返回-的下标for i in range(len(pro) - ind - 1):tmp = pro[:ind + 2 + i] + "." + pro[ind + 2 + i:]dot_items.append(tmp)return dot_itemsdef closure(self, item):  # 求item所有的产生式的闭包c_item = itemold_c_item = []while len(old_c_item) != len(c_item):old_c_item = copy.deepcopy(c_item)for pro in old_c_item:if pro not in c_item:c_item.append(pro)dot_left, dot_right = pro.split(".")if dot_right == "":  # 当前产生式为最后一项为. .不能继续移动,跳过continueif dot_right[0] in self.Vn:  # .后面为非终结符, 加入它的Vn->.xxxxfor dot_p in self.dot_items:ind = dot_p.find("->")  # 返回-的下标if dot_p[0] == dot_right[0] \and dot_p[ind + 2] == "." \and dot_p[1] != "'" \and dot_p not in c_item:  # 首字符匹配 且不为增广符c_item.append(dot_p)return c_itemdef go(self, item, v):  # 生成item向v移动后的item_productionto_v_item = []for pro in item:  # 生成item能够用v跳转的新的产生式dot_left, dot_right = pro.split(".")if dot_right != "":if dot_right[0] == v:  # .右边是跳转符vto_v_item.append(dot_left + dot_right[0] + "." + dot_right[1:])new_item = Noneif len(to_v_item) != 0:  # 求新产生式的闭包new_item = self.closure(to_v_item)return new_itemdef exist_idx(self, all_DFA, new_dfa):if new_dfa.item_ is None:return -1for i in range(len(all_DFA)):if new_dfa == all_DFA[i]:return ireturn -1def step3_construct_LR0_DFA(self, dot_items):# 生成初始Item0all_DFA = []item0_pros = []item0_pros.extend(self.closure([dot_items[0]]))all_DFA.append(DFA(0, item0_pros, {}))visited_dfa = []  # close表old_visted_dfa = []  # 用于判断close表长度是否再变化V = list(self.Vn) + list(self.Vt)  # 合并非终结符和终结符while True:old_visted_dfa = copy.deepcopy(visited_dfa)  # 副本for dfa in all_DFA:if dfa in visited_dfa:  # 已经访问过,则continuecontinuevisited_dfa.append(dfa)  # 加入close表item = dfa.item_for v in V:new_item = self.go(item, v)if new_item is not None:new_dfa = DFA(-1, new_item, {})idx = self.exist_idx(all_DFA, new_dfa)if idx == -1:  # 不存在new_dfa.id_ = len(all_DFA)dfa.next_ids_[v] = new_dfa.id_all_DFA.append(new_dfa)else:  # 存在dfa.next_ids_[v] = idxif len(old_visted_dfa) == len(visited_dfa):  # close表长度不变,退出循环breakreturn all_DFAdef print_DFA(self, all_DFA):for dfa in all_DFA:print("====")print(f"id={dfa.id_}")print(f"item={dfa.item_}")print(f"next={dfa.next_ids_} \n")def step4_draw_DFA(self, all_DFA):# 创建Digraph对象dot = graphviz.Digraph()for dfa in all_DFA:label = f"I{dfa.id_}\n"node_color = "lightblue"if dfa.id_ == 0:node_color = "lightpink"for pro in dfa.item_:label += pro + "\n"dot.node(str(dfa.id_), label=label,style='filled', fillcolor=node_color,shape='rectangle', fontname='Verdana')if len(dfa.next_ids_) != 0:for v, to_id in dfa.next_ids_.items():dot.edge(str(dfa.id_), str(to_id), label=v, fontcolor='red')# 显示图形dot.view()return dotdef step5_check_LR0(self, all_DFA):  # 判断是否为LR0文法flag = Truefor dfa in all_DFA:item = dfa.item_shift_num = 0  # 移进数目protocol_num = 0  # 归约数目shift_vt = set()protocol_vn = set()shift_pro = set()protocol_pro = set()for pro in item:dot_left, dot_right = pro.split(".")if dot_right == "":  # .在最后,为归约项目# if dot_left[:2] == self.S + "'":  # 接受项目,不考虑为归约项目#     continueprotocol_num += 1pro_left, pro_right = pro.split("->")protocol_vn.add(pro_left)protocol_pro.add(pro)elif dot_right[0] in self.Vt:  # .后面为终结符,为移进项目shift_num += 1shift_vt.add(dot_right[0])shift_pro.add(pro)if protocol_num == 1 and shift_num >= 1:shift_conf_msg = ""for s_pro in shift_pro:shift_conf_msg += s_pro + " "print(f"I{dfa.id_}中:{shift_conf_msg}{next(iter(protocol_pro))}存在移进-归约冲突")for vt in shift_vt:for vn in protocol_vn:if self.first[vt].intersection(self.follow[vn]):  # 有交集flag = Falseprint(f"它们的first与follow交集不为空,不满足SLR")return flagflag = Trueprint(f"但它们的first与follow交集为空,可忽略")elif protocol_num >= 2:pro_conf_msg = ""for p_pro in protocol_pro:pro_conf_msg += p_pro + " "print(f"I{dfa.id_}中: {pro_conf_msg} 存在归约-归约冲突,不满足SLR")flag = Falsereturn flagdef step6_construct_LR0_table(self, all_DFA, formulas_str_list):actions = {}gotos = {}for dfa in all_DFA:id_ = dfa.id_next_ids = dfa.next_ids_if len(next_ids) == 0:  # 无下一个状态,必定为归约项目或接受项目,且只有一个pro = dfa.item_[0].replace(".", "")  # 去除.if pro == formulas_str_list[0]:  # 如果这一个为接受项目:S'->Sactions[(id_, "#")] = "acc"else:  # 其他的指定产生式# ===========SLR1===========# for vt in self.Vt:#     actions[(id_, vt)] = "r" + str(formulas_str_list.index(pro))# actions[(id_, "#")] = "r" + str(formulas_str_list.index(pro))# ===========SLR===========pro_left, pro_right = pro.split("->")for ch in self.follow[pro_left]:actions[(id_, ch)] = "r" + str(formulas_str_list.index(pro))actions[(id_, "#")] = "r" + str(formulas_str_list.index(pro))else:  # 有指向下一个项目,同时当前项目可能存在接受项目、归约项目(点在末尾)、移进项目for item in dfa.item_:pro_left, pro_right = item.split(".")if pro_right == "":  # .在最后   为归约项目pro = item.replace(".", "")if pro == formulas_str_list[0]:  # 为接受项目actions[(id_, "#")] = "acc"else:  # 其他的归约项目left, right = pro.split("->")for ch in self.follow[left]:actions[(id_, ch)] = "r" + str(formulas_str_list.index(pro))actions[(id_, "#")] = "r" + str(formulas_str_list.index(pro))for v, to_dfa_id in next_ids.items():if v in self.Vt:actions[(id_, v)] = "S" + str(to_dfa_id)elif v in self.Vn:gotos[(id_, v)] = to_dfa_id# 转成df对象merged_dict = {key: value for d in (actions, gotos) for key, value in d.items()}sorted_keys = sorted(merged_dict.keys(), key=lambda x:(x[1].isupper(), x[1] == "#", x[1]))sort_dict = {key: merged_dict[key] for key in sorted_keys}columns = []for k in sort_dict.keys():if k[1] not in columns:columns.append(k[1])rows = set(key[0] for key in sort_dict.keys())df = pd.DataFrame(index=rows, columns=columns)for key, value in sort_dict.items():df.loc[key[0], key[1]] = valueprint(df)return actions, gotosdef step7_LR0_analyse(self, actions, gotos, formulas_str_list, input_str):s = list(input_str)s.append("#")sp = 0  # 字符串指针state_stack = []symbol_stack = []state_stack.append(0)symbol_stack.append("#")step = 0msg = ""info_step, info_state_stack, info_symbol_stack, info_str, info_msg, info_res = [], [], [], [], [], ""# 分析while sp != len(s):step += 1ch = s[sp]top_state = state_stack[-1]top_symbol = symbol_stack[-1]info_step.append(step)info_state_stack.append("".join([str(x) for x in state_stack]))info_symbol_stack.append("".join(symbol_stack))info_str.append("".join(s[sp:]))if (top_state, ch) not in actions.keys():info_res = f"error:分析失败,找不到Action({(top_state, ch)})"info_msg.append("error")breakfind_action = actions[(top_state, ch)]if find_action[0] == "S":  # 移进操作state_stack.append(int(find_action[1:]))symbol_stack.append(ch)sp += 1msg = f"Action[{top_state},{ch}]={find_action}: 状态{find_action[1:]}入栈"elif find_action[0] == 'r':  # 归约操作pro = formulas_str_list[int(find_action[1:])]  # 获取第r行的产生式pro_left, pro_right = pro.split("->")pro_right_num = len(pro_right)for i in range(pro_right_num):state_stack.pop()symbol_stack.pop()symbol_stack.append(pro_left)goto_key = (state_stack[-1], symbol_stack[-1])if goto_key in gotos.keys():state_stack.append(gotos[goto_key])msg = f"{find_action}: 用{pro}归约,Goto[{top_state},{symbol_stack[-1]}]={gotos[goto_key]}入栈"else:info_res = f"error:分析失败,找不到GOTO({state_stack[-1]},{symbol_stack[-1]})"elif find_action == "acc":msg = "acc: 分析成功!"info_msg.append(msg)info_res = "Success!"breakinfo_msg.append(msg)# printprint("{:<10} {:<10} {:<10} {:<10} {:<20}".format("步骤","状态栈","输入串","符号栈","分析"))for i in range(len(info_step)):print("{:<10} {:<10} {:<10} {:<10} {:<20}".format(info_step[i],info_state_stack[i],info_symbol_stack[i],info_str[i],info_msg[i]))info = {"info_step": info_step,"info_state_stack": info_state_stack,"info_symbol_stack": info_symbol_stack,"info_str": info_str,"info_msg": info_msg,"info_res": info_res}return infodef init(self):print("===========基本信息==========")self.S, self.Vn, self.Vt, self.formulas_str_list, self.first, self.follow = self.step1_pre_process(self.formulas_str_list)self.dot_items = self.step2_all_dot_pros(self.formulas_str_list)  # 计算所有项目(带点)self.all_DFA = self.step3_construct_LR0_DFA(self.dot_items)  # 计算项目集的DFA转换关系# self.print_DFA(self.all_DFA)self.step4_draw_DFA(self.all_DFA)  # 画项目集的DFA转换图print("===========移进-归约重冲突=========")self.step5_check_LR0(self.all_DFA)  # 检测是否符合LR0文法print("===========SLR1表=========")self.actions, self.gotos = self.step6_construct_LR0_table(self.all_DFA, self.formulas_str_list)  # 画表def solve(self, input_str):print("===========分析过程=========")self.info = self.step7_LR0_analyse(self.actions, self.gotos, self.formulas_str_list, input_str)if __name__ == "__main__":grammar1 = [  # + ()"E->E+T","E->T","T->(E)","T->i"]grammar2 = [  # 课本例子"S->BB","B->aB","B->b"]grammar3 = [  # + * ()"E->E+T","E->T","T->T*F","T->F","F->(E)","F->i"]lr0 = SLR1(grammar1)lr0.init()lr0.solve("i+i")

运行结果

测试1

输入:
文法:在这里插入图片描述
分析串:在这里插入图片描述

输出:
在这里插入图片描述
在这里插入图片描述

测试2

输入:
文法:在这里插入图片描述
分析串:在这里插入图片描述

输出:
在这里插入图片描述
在这里插入图片描述

测试3

输入:
文法:在这里插入图片描述
分析串:在这里插入图片描述

输出:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

对于SLR1分析法,检查是否符合SLR1文法时,需要在LR1文法的基础上,增加了各个项目对于follow集是否有交集的判断,因此需要另外实现first集和follow集的求解,另外。而且,SLR文法主要解决了LR0文法中出现的“移进-归约”冲突,但对于“归约-归约”冲突仍然不能解决。

相关文章:

编译原理实验3——自下而上的SLR1语法分析实现(包含画DFA转换图、建表、查表)

文章目录 实验目的实现流程定义DFA状态实现代码运行结果测试1测试2测试3 总结 实验目的 实现自下而上的SLR1语法分析&#xff0c;画出DFA图 实现流程 定义DFA状态 class DFA:def __init__(self, id_, item_, next_ids_):self.id_ id_ # 编号self.item_ item_ # productio…...

基于tomcat的https(ssl)双向认证

一、背景介绍 某个供应商服务需要部署到海外&#xff0c;如果海外多个地区需要部署多个服务&#xff0c;最好能实现统一登录&#xff0c;这样可以减轻用户的使用负担&#xff08;不用记录一堆密码&#xff09;。由于安全问题&#xff08;可能会泄露用户数据&#xff09;&#x…...

【iOS ARKit】3D人体姿态估计实例

与2D人体姿态检测一样&#xff0c;在ARKit 中&#xff0c;我们不必关心底层的人体骨骼关节点检测算法&#xff0c;也不必自己去调用这些算法&#xff0c;在运行使用 ARBodyTrackingConfiguration 配置的 ARSession 之后&#xff0c;基于摄像头图像的3D人体姿态估计任务也会启动…...

ROS2 CMakeLists.txt 和 package.xml

这里记录一下ROS2中功能包package.xml和CMakeLists.txt的格式。以LIO-SAM的ROS2版本为例&#xff1a; 一&#xff1a;CMakeLists.txt cmake_minimum_required(VERSION 3.5) project(lio_sam)if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)set(CMAKE_BUILD_TYPE…...

代码献瑞,算力有礼!低代码开发工具PaddleX特色产线新春福利来啦

回望2023年&#xff0c;飞桨在开发套件能力基础上&#xff0c;充分结合大模型能力&#xff0c;正式在飞桨星河社区上线发布了低代码开发工具PaddleX&#xff0c;实现AI应用开发效果和效率的大幅提升。产品通过提供图形界面开发模式&#xff0c;将复杂的编程任务简化为简单易用的…...

C语言:操作符详解

创作不易&#xff0c;给个三连吧&#xff01;&#xff01; 一、算术操作符 C语言中为了方便计算&#xff0c;提供了算数操作符&#xff0c;分别是:,-,*,/,% 由于这些操作符都是有两个操作数&#xff08;位于操作符两边&#xff09;&#xff0c;所以这种操作符也叫做双目操作…...

Rust 初体验2

变量类型 Rust 语言的变量数据类型&#xff0c;主要包括整型、浮点型、字符、布尔型、元组、数组、字符串、枚举、结构体和可变变量等。 fn main() { // 整型 let integer: i32 100; println!("整型: {}", integer); // 浮点型 let floating_point: f64 3.1…...

vue-cil的watch函数详解

在Vue中&#xff0c;watch是一个非常有用的API&#xff0c;用于侦听一个响应式引用&#xff08;例如由ref创建&#xff09;或响应式对象&#xff08;由reactive创建&#xff09;的属性&#xff0c;并在值变化时执行回调函数。Vue 3的Composition API引入了这种侦听方式&#xf…...

堆排及时间复杂度分析

箴言: 初始阶段&#xff0c;不需要去纠结那一种更优美&#xff0c;非要找出那一种是最好的&#xff0c;其实能解决问题的就是好办法。 一&#xff0c;常见排序时间复杂度 冒泡快排归并堆排桶排时间O(n^2)O(nlogn)O(nlogn)O(nlogn)kn空间O(1)O(1)O(nlogn)O(1)kn 二&#xff…...

数据结构:双向链表

文章目录 1. 双向带头循环链表的结构2. 相关操作2.1 创建节点2.2 尾插2.3 头插2.4 打印2.5 尾删2.6 头删2.7 查找2.8 指定位置前/后插入2.9 删除指定位置的节点2.10 删除指定位置后的节点2.11 销毁链表 3.顺序表与链表区别 1. 双向带头循环链表的结构 与单链表不同的是&#xf…...

51单片机之数码管显示表白数字篇

朝菌不知晦朔 蟪蛄不知春秋 眼界决定境界 CSDN 请求进入专栏 是否进入《51单片机专栏》? 确定 目录 数码管的简介 数码管引脚定义 数码管的原理图 74HC245 代码实现 静态数码管的显示 动态数码管的显示 数码管实现表白画面 数码管的简介 L…...

代码随想录算法训练营DAY16 | 二叉树 (3)

一、LeetCode 104 二叉树的最大深度 题目链接&#xff1a;104.二叉树的最大深度https://leetcode.cn/problems/maximum-depth-of-binary-tree/ 思路&#xff1a;采用后序遍历递归求解。 class Solution {int ans 0;public int maxDepth(TreeNode root) {if(root null){retur…...

springboot(ssm大学生计算机基础网络教学系统 在线课程系统Java系统

springboot(ssm大学生计算机基础网络教学系统 在线课程系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;springboot&#xff08;可改ssm&#xff09; vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mys…...

前端架构: 脚手架的开发流程和常用框架

脚手架的开发流程 脚手架的创建 $ npm init 脚手架的开发 分包 分包是指当我们一个脚手架比较复杂的时候&#xff0c;不可能把所有的js代码全部写在一个脚手架当中势必会把它建很多的不同的模块 package&#xff0c;通常我们会把它称之为一个分包的过程会和实际的这个项目一样…...

3.0 Hadoop 概念

本章着重介绍 Hadoop 中的概念和组成部分&#xff0c;属于理论章节。如果你比较着急可以跳过。但作者不建议跳过&#xff0c;因为它与后面的章节息息相关。 Hadoop 整体设计 Hadoop 框架是用于计算机集群大数据处理的框架&#xff0c;所以它必须是一个可以部署在多台计算机上…...

mysql 对于null字段排序处理

最近遇到一个需求 &#xff0c;需要对一个报表的多个字段进行多字段复杂条件排序 排序字段为NULL时 Mysql对于排序字段为NULL时&#xff0c;有自身默认的排序规则&#xff0c;默认是认为null 值 是无穷小 ELECT id,script_id,last_modified,live_count,next_show FROM virtua…...

NLP_语言模型的雏形 N-Gram 模型

文章目录 N-Gram 模型1.将给定的文本分割成连续的N个词的组合(N-Gram)2.统计每个N-Gram在文本中出现的次数&#xff0c;也就是词频3.为了得到一个词在给定上下文中出现的概率&#xff0c;我们可以利用条件概率公式计算。具体来讲&#xff0c;就是计算给定前N-1个词时&#xff0…...

mac电脑flutter环境配置,解决疑难问题

准备工作 首先搭建flutter的环境需要使用到flutter的sdk&#xff0c;可以直接跳去官网下载&#xff1a;Choose your first type of app - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter&#xff0c;下载时要注意你电脑所使用的芯片是Intel的还是苹果的芯片。 下载好的…...

C++ bool 布尔类型

在C 中 bool类型占用1个字节长度&#xff0c;bool 类型只有两个取值&#xff0c;true 和 false&#xff0c;true 表示“真”&#xff0c;false 表示“假”。 需要注意的C中使用cout 打印的时候是没有true 和 false 的 只有0和1 &#xff0c;这里0表示假&#xff0c;非0表示真 …...

DC-7靶机渗透详细流程

信息收集&#xff1a; 1.存活扫描&#xff1a; 由于靶机和kali都是nat的网卡&#xff0c;都在一个网段&#xff0c;我们用arp-scan会快一点&#xff1a; arp-scan arp-scan -I eth0 -l └─# arp-scan -I eth0 -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:dd:ee:6…...

提速MySQL:数据库性能加速策略全解析

提速MySQL&#xff1a;数据库性能加速策略全解析 引言理解MySQL性能指标监控和评估性能指标索引优化技巧索引优化实战案例 查询优化实战查询优化案例分析 存储引擎优化InnoDB vs MyISAM选择和优化存储引擎存储引擎优化实例 配置调整与系统优化配置调整系统优化优化实例 实战案例…...

Flink实战六_直播礼物统计

接上文&#xff1a;Flink实战五_状态机制 1、需求背景 现在网络直播平台非常火爆&#xff0c;在斗鱼这样的网络直播间&#xff0c;经常可以看到这样的总榜排名&#xff0c;体现了主播的人气值。 人气值计算规则&#xff1a;用户发送1条弹幕互动&#xff0c;赠送1个荧光棒免费…...

Compose | UI组件(十五) | Scaffold - 脚手架

文章目录 前言一、Scaffold脚手架简介二、Scaffold的主要组件三、如何使用Scaffold四、Compose中Scaffold脚手架的具体例子例子1&#xff1a;基本Scaffold布局例子2&#xff1a;带有Drawer的Scaffold布局例子3&#xff1a;带有Snackbar的Scaffold布局 总结 前言 Compose中的Sca…...

Vue-60、Vue技术router-link的replace属性

1、作用&#xff1a;控制路由跳转时操作浏览器历史记录的模式 2、浏览器的历史记录有两种写入方式&#xff1a;分别是push和replace,push是追加历史记录&#xff0c;replace是替换当前记录。路由跳转时候默认为push 3、如何开启replace模式&#xff1a; <router-link rep…...

Hive与Presto中的列转行区别

Hive与Presto列转行的区别 1、背景描述2、Hive/Spark列转行3、Presto列转行 1、背景描述 在处理数据时&#xff0c;我们经常会遇到一个字段存储多个值&#xff0c;这时需要把一行数据转换为多行数据&#xff0c;形成标准的结构化数据 例如&#xff0c;将下面的两列数据并列转换…...

探讨CSDN等级制度:博客等级、原力等级、创作者等级

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…...

2.8作业

sqlite3数据库操作接口详细整理&#xff0c;以及常用的数据库语句 头文件&#xff1a; #include <sqlite3.h> 编译时候要加上-lsqlite3 gcc a.c -lsqlite3 1&#xff09;sqlite3_open 打开一个数据库&#xff0c;如果数据库不存在&#xff0c;则创建一个数据库 2&am…...

机器学习中常用的性能度量—— ROC 和 AUC

什么是泛化能力&#xff1f; 通常我们用泛化能力来评判一个模型的好坏&#xff0c;通俗的说&#xff0c;泛化能力是指一个机器学期算法对新样本&#xff08;即模型没有见过的样本&#xff09;的举一反三的能力&#xff0c;也就是学以致用的能力。 举个例子&#xff0c;高三的…...

微服务入门篇:Nacos注册中心(Nacos安装,快速入门,多级存储,负载均衡,环境隔离,配置管理,热更新,集群搭建,nginx反向代理)

目录 1.Nacos安装1.官网下载2.解压到本地3.启动nacos 2.Nacos快速入门1.在父工程中导入nacos依赖2.给子项目添加客户端依赖3.修改对应服务的配置文件4.启动服务&#xff0c;查看nacos发现情况 3.Nacos服务多级存储模型4.NacosRule负载均衡5. 服务实例的权重设置6.环境隔离&…...

解决CORS错误(Spring Boot)

记录一下错误&#xff0c;以博客的形式 前言 跨域&#xff08;Cross-Origin&#xff09;是指在Web开发中&#xff0c;当一个Web应用试图从一个源&#xff08;域名、协议、端口组合&#xff09;获取资源时&#xff0c;该请求的目标与当前页面的源不同。具体来说&#xff0c;当一…...

NLP入门系列—词嵌入 Word embedding

NLP入门系列—词嵌入 Word embedding 2013年&#xff0c;Word2Vec横空出世&#xff0c;自然语言处理领域各项任务效果均得到极大提升。自从Word2Vec这个神奇的算法出世以后&#xff0c;导致了一波嵌入&#xff08;Embedding&#xff09;热&#xff0c;基于句子、文档表达的wor…...

JUnit5单元测试框架提供的注解

目录 第一章、注释在类上的注解1.1&#xff09;JUnit5注释在类上的注解集成测试&#xff1a;SpringBootTest集成测试&#xff1a;ExtendWith(SpringExtension.class)单元测试&#xff1a;ExtendWith(MockitoExtension.class)切片测试:WebMvcTest和DataJpaTest<font colorred…...

ThinkPHP 中使用Redis

环境.env [app] app_debug "1" app_trace ""[database] database "" hostname "127.0.0.1" hostport "" password "" prefix "ls_" username ""[redis] hostname "127.0.0.1…...

Go语言Gin框架安全加固:全面解析SQL注入、XSS与CSRF的解决方案

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在使用 Gin 框架处理前端请求数据时&#xff0c;必须关注安全性问题&#xff0c;以防范常见的攻击…...

MySQL数据库基础与SELECT语句使用梳理

MySQL数据库基础与SELECT语句使用梳理 注意&#xff1a;本文操作全部在终端进行 数据库基础知识 什么是数据库 数据库&#xff08;database&#xff09;是保存有组织的数据的容器&#xff08;通常是一个文件或一组文件&#xff09;&#xff0c;实质上数据库是一个以某种 有组…...

scikit-learn 1.3.X 版本 bug - F1 分数计算错误

如果您正在使用 scikit-learn 1.3.X 版本&#xff0c;在使用 f1_score() 或 classification_report() 函数时&#xff0c;如果参数设置为 zero_division1.0 或 zero_divisionnp.nan&#xff0c;那么函数的输出结果可能会出错。错误的范围可能高达 100%&#xff0c;具体取决于数…...

Python面试题19-24

解释Python中的装饰器&#xff08;decorators&#xff09;是什么&#xff0c;它们的作用是什么&#xff1f; 装饰器是一种Python函数&#xff0c;用于修改其他函数的功能。它们允许在不修改原始函数代码的情况下&#xff0c;动态地添加功能。解释Python中的文件处理&#xff08…...

《Django+React前后端分离项目开发实战:爱计划》 01 项目整体概述

01 Introduction 《Django+React前后端分离项目开发实战:爱计划》 01 项目整体概述 Welcome to Beginning Django API wih React! This book focuses on they key tasks and concepts to get you started to learn and build a RESTFul web API with Django REST Framework,…...

从零开始 TensorRT(4)命令行工具篇:trtexec 基本功能

前言 学习资料&#xff1a; TensorRT 源码示例 B站视频&#xff1a;TensorRT 教程 | 基于 8.6.1 版本 视频配套代码 cookbook 参考源码&#xff1a;cookbook → 07-Tool → trtexec 官方文档&#xff1a;trtexec 在 TensorRT 的安装目录 xxx/TensorRT-8.6.1.6/bin 下有命令行…...

基于SpringBoot+Vue的校园博客管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…...

基于 SpringBoot 和 Vue.js 的权限管理系统部署教程

大家后&#xff0c;我是 jonssonyan 在上一篇文章我介绍了我的新项目——基于 SpringBoot 和 Vue.js 的权限管理系统&#xff0c;本文主要介绍该系统的部署 部署教程 这里使用 Docker 进行部署&#xff0c;Docker 基于容器技术&#xff0c;它可以占用更少的资源&#xff0c;…...

Redis篇之集群

一、主从复制 1.实现主从作用 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。主节点用来写的操作&#xff0c;从节点用来读操作&#xff0c;并且主节点发生写操作后&#xff0c;会把数据同…...

JUnit 5 注解总结与解析

前言 大家好&#xff0c;我是chowley&#xff0c;通过前篇的JUnit实践&#xff0c;我对这个框架产生了好奇&#xff0c;除了断言判断&#xff0c;它还有哪些用处呢&#xff1f;下面来总结一下它的常见注解及作用。 正文 在Java单元测试中&#xff0c;JUnit是一种常用的测试框…...

CSS综合案例4

CSS综合案例4 1. 综合案例 我们来做一个静态的轮播图。 2. 分析思路 首先需要加载一张背景图进去需要4个小圆点&#xff0c;设置样式&#xff0c;并用定位和平移调整位置添加两个箭头&#xff0c;也是需要用定位和位移进行调整位置 3. 代码演示 html文件 <!DOCTYPE htm…...

WifiConfigStore初始化读取-Android13

WifiConfigStore初始化读取 1、StoreData创建并注册2、WifiConfigStore读取2.1 文件读取流程2.2 时序图2.3 日志 1、StoreData创建并注册 packages/modules/Wifi/service/java/com/android/server/wifi/WifiConfigManager.java mWifiConfigStore.registerStoreData(mNetworkL…...

【Spring源码解读!底层原理进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;底层原理高级进阶》 &#x1f680…...

从零开始手写mmo游戏从框架到爆炸(六)— 消息处理工厂

就好像门牌号一样&#xff0c;我们需要把消息路由到对应的楼栋和楼层&#xff0c;总不能像菜鸟一样让大家都来自己找数据吧。 首先这里我们参考了rabbitmq中的topic与tag模型&#xff0c;topic对应类&#xff0c;tag对应方法。 新增一个模块&#xff0c;专门记录路由eternity-…...

Go基础学习笔记-知识点

学习笔记记录了我在学习官方文档过程中记的要点&#xff0c;可以参考学习。 go build *.go 文件 编译 go run *.go 执行 go mod init 生成依赖管理文件 gofmt -w *.go 格式换名称的大小写用来控制方法的可见域主方法及包命名规范 package main //注意package的命名&#xff0…...

jvm几个常见面试题整理

1. Full GC触发机制有如下5种情况。 (1)调用System.gc()时&#xff0c;系统建议执行Full GC&#xff0c;但是不必然执行。(2)老年代空间不足。(3)方法区空间不足。(4)老年代的最大可用连续空间小于历次晋升到老年代对象的平均大小就会进行Full GC。(5)由Eden区、S0(From)区向S…...

ReentrantLock 和 公平锁

ReentrantLock 和 公平锁 一、基本介绍 ReentrantLock(重入锁) 是一个独占式锁&#xff0c;具有和synchronize的监视器锁基本相同的行为和语意。但和synchronized相比&#xff0c;它更加的灵活、强大、增加了轮询、超时、中断等高级功能以及可以创建公平和非公平锁。Reentran…...