【编译原理】用Python实现LR(0)语法分析

实验内容

对于给定的如下文法,编写调试一个上下文无关文法的LR(0)分析程序。

文法G’为:
S → E S\to E SE
E → a A E\to aA EaA
E → b B E \to bB EbB
A → c A A\to cA AcA
A → d A\to d Ad
B → c B B\to cB BcB
B → d B\to d Bd


实验环境

  • IDE:PyCharm 2023.2.1 (Community Edition)
  • Python:3.8.5
  • 库:tkinter

测试结果截图以及说明

文法:

在这里插入图片描述

读取功能后功能选择:

在这里插入图片描述

状态集:

在这里插入图片描述

状态表:

在这里插入图片描述

分析字符串,输入后得到结果:

在这里插入图片描述

退出程序:

在这里插入图片描述


代码部分

代码结构如下

project_floder
│  LICENSE.md
│  main.py
│  README.md
│  read_grammar.py
│
├─.idea
│  │  .gitignore
│  │  deployment.xml
│  │  LR(0).iml
│  │  misc.xml
│  │  modules.xml
│  │  vcs.xml
│  │  workspace.xml
│  │
│  └─inspectionProfiles
│          profiles_settings.xml
│
├─src
│      grammar_1.txt
│      grammar_2.txt
│
└─__pycache__

代码仓库(Github)

在这里插入图片描述
https://github.com/hiddenSharp429/LR-0-_analysis

read_grammar.py

"""
@coding : utf-8
@File   : read_grammar.py
@Author : hiddensharp
@Date   : 2023/12/10
@Desc   : 返回一个能够给lr0分析器分析的字典并自动变成增广文法
@Version:
@Last_editor
"""import reclass ReadGrammar(dict):def __init__(self, grammar_file_path):super().__init__()self.file_path = grammar_file_pathself.translate()self.add_augmented_production()def translate(self):# 从文件中读取规则with open(self.file_path, 'r', encoding='utf-8') as file:rules = file.readlines()# 遍历每一条规则for rule in rules:# 使用正则表达式匹配规则的左部和右部match = re.match(r'\s*(\w+)\s*->\s*(.+)\s*', rule)if match:left = match.group(1)right = match.group(2)# 如果左部非终结符还不存在于字典中,则添加if left not in self:self[left] = []# 将右部按照单个字符拆分并添加到左部的规则中productions = [symbol for symbol in right]self[left].append(tuple(productions))def add_augmented_production(self):# 获取原始文法的起始符号original_start_symbol = list(self.keys())[0]# 生成新的起始符号new_start_symbol = 'S'# 创建新的字典,将新的起始产生式添加到新的字典中new_dict = {new_start_symbol: [(original_start_symbol,)]}# 将原始字典的内容添加到新的字典后面new_dict.update(self)# 将新的字典设置为当前字典self.clear()self.update(new_dict)# 返回新的起始符号return new_start_symbolif __name__ == '__main__':# 定义需要读取的文法文件的路径grammar_file_path = 'src/grammar_2.txt'  # 使用非拓广的文法# 转换为该解析器能读取的字典格式grammar_reader = ReadGrammar(grammar_file_path)print("Original Grammar:")print(grammar_reader)

main.py

"""
@coding : utf-8
@File   : main.py
@Author : zixian Zhu(hiddensharp)
@Date   : 2023/12/10
@Desc   :
@Version:
@Last_editor
"""
import tkinter as tk
from tkinter import filedialog, messagebox
from read_grammar import ReadGrammarclass LR0Parser:def __init__(self, grammar):# 初始化LR(0)分析器,接收文法作为参数self.grammar = grammar# 计算闭包和转移函数self.compute_closure_goto()def closure(self, items):'''计算闭包函数:param items: 当前项目集的集合:return:'''# 初始化闭包集合,包含输入的项目集closure_items = set(items)# 标志用于迭代,表示是否有项目被添加到闭包中changed = True# 循环直到没有新的项目添加到闭包中为止while changed:# 将标志设为 False,表示没有新的项目添加到闭包中changed = False# 对当前闭包集合中的每个项目进行遍历for item in closure_items.copy():# 获取项目中点的位置,如果没有点则使用项目的长度作为点的位置dot_index = item[1].index('.') if '.' in item[1] else len(item[1])# 如果点的位置不在项目的最后一个位置之前if dot_index < len(item[1]) - 1:# 获取点后面的符号next_symbol = item[1][dot_index + 1]# 如果该符号在文法中if next_symbol in self.grammar:# 对该符号的每个产生式进行遍历for production in self.grammar[next_symbol]:# 创建一个新项目,将点移动到产生式的开头new_item = (next_symbol, ('.',) + production)# 如果新项目不在闭包中,则添加到闭包中,并将标志设为 Trueif new_item not in closure_items:closure_items.add(new_item)changed = True# 处理以点结尾的项目的闭包for item in closure_items.copy():dot_index = item[1].index('.') if '.' in item[1] else len(item[1])# 如果点在项目的最后一个位置if dot_index == len(item[1]) - 1:# 检查状态集中是否已经存在相同的状态if item not in closure_items:# 添加新项目到闭包中,并将标志设为 Trueclosure_items.add(item)changed = True# 返回闭包的冻结集合,确保集合是不可变的return frozenset(sorted(closure_items))def goto(self, items, symbol):'''计算转移函数:param items: 当前项目集的集合:param symbol: 转移的符号:return:'''# 初始化转移后的项目集合goto_items = set()# 对当前项目集合中的每个项目进行遍历for item in items:# 获取项目中点的位置,如果没有点则使用项目的长度作为点的位置dot_index = item[1].index('.') if '.' in item[1] else len(item[1])# 如果点的位置不在项目的最后一个位置之前,并且点后的符号与输入的符号相匹配if dot_index < len(item[1]) - 1 and item[1][dot_index + 1] == symbol:# 创建一个新项目,将点移动到符号的位置new_item = (item[0], item[1][:dot_index] + (symbol, '.') + item[1][dot_index + 2:])# 将新项目添加到转移后的项目集合中goto_items.add(new_item)# 调用闭包函数,计算转移后的闭包return self.closure(goto_items)def compute_closure_goto(self):'''计算LR(0)项集规范族中所有状态的闭包和转移函数:return:'''# 初始化状态集合和转移字典self.states = []self.transitions = {}# 获取文法中所有出现的符号,包括终结符和非终结符,并按照大小写顺序排列all_symbols = sorted(set(symbol for production_list in self.grammar.values() for production in production_list for symbol inproduction))# 初始化初始项目和初始状态initial_item = ('S', ('.', 'E'))initial_state = self.closure({initial_item})# 将初始状态添加到状态集合中self.states.append(initial_state)# 使用队列实现广度优先搜索,初始状态入队queue = [initial_state]# 循环直到队列为空while queue:# 出队当前状态current_state = queue.pop(0)# 对文法中的每个符号进行遍历,包括终结符和非终结符for symbol in all_symbols:# 计算转移后的项目集goto_items = self.goto(current_state, symbol)# 如果存在转移后的项目集if goto_items:# 计算转移后项目集的闭包closure_goto_items = self.closure(goto_items)# 如果闭包非空且不在已有状态集合中if closure_goto_items and closure_goto_items not in self.states:# 将闭包添加到状态集合中self.states.append(closure_goto_items)# 将闭包入队,以便继续搜索queue.append(closure_goto_items)# 记录当前状态到符号的转移关系self.transitions[(current_state, symbol)] = closure_goto_itemsdef is_reduce_state(self, state):'''判断状态是否为规约状态:param state: 状态:return: 是否为规约状态'''# 在这里实现判断状态是否为规约状态的逻辑for item in state:# 获取项目中点的位置,如果没有点则使用项目的长度作为点的位置dot_index = item[1].index('.') if '.' in item[1] else len(item[1])# 如果点的位置在项目的最后一个位置之前,说明这是规约状态if dot_index < len(item[1]) - 1:return False# 如果没有任何项目的点在最后,说明这是规约状态return Truedef get_reduce_production_index(self, state):'''获取规约状态对应的产生式的索引:param state: 规约状态:return: 产生式的索引'''# 在文法中查找该产生式的位置for item in state:# 获取项目中点的位置,如果没有点则使用项目的长度作为点的位置dot_index = item[1].index('.') if '.' in item[1] else len(item[1])# 如果点的位置在产生式右部的最后一个位置,说明这是规约状态if dot_index == len(item[1]) - 1:# 获取规约项目的产生式左右部production_left = item[0]production_right = item[1][:dot_index]production_index = 0 # 产生式子在文法中的索引# 在文法中查找该产生式左右两部分均匹配的位置for left_symbol, productions in self.grammar.items():for index, production in enumerate(productions):if left_symbol == production_left and production == production_right:return production_index + indexproduction_index += len(productions)def print_states(self):'''打印所有的状态集:return:'''print("LR(0) Parsing States:")# 遍历所有状态for i, state in enumerate(self.states):# 输出当前状态的编号和项目集print(f"State {i}: {state}")def print_table(self):'''打印LR(0)分析表:return:'''# 获取文法中所有出现的符号,包括终结符和非终结符all_symbols = sorted(set(symbol for production_list in self.grammar.values() for production in production_list for symbol inproduction))# 分类大小写字符uppercase_symbols = sorted(set(symbol for symbol in all_symbols if symbol.isupper()))lowercase_symbols = sorted(set(symbol for symbol in all_symbols if symbol.islower()))lowercase_symbols.append('#') # 给ACTION表添加一个#列all_sorted_symbols = lowercase_symbols + uppercase_symbols# 初始化表格self.states_table = [[''] * (len(self.states) + 10) for _ in range(len(all_symbols) + 10)]# 设置表头self.states_table[0][0] = 'states'for row in range(len(self.states)):self.states_table[row + 1][0] = rowfor col, symbol in enumerate(all_sorted_symbols, start=1):self.states_table[0][col] = symbol# 填写表格for row in range(1, len(self.states) + 1):for col in range(1, len(all_sorted_symbols) + 1):# 获取当前符号current_states = self.states_table[row][0]current_symbol = self.states_table[0][col]# 如果是规约状态,则填写规约式的序号if self.is_reduce_state(self.states[current_states]):production_index = self.get_reduce_production_index(self.states[current_states])if production_index == 0:  # 增广产生式的索引为0# 只有#列才标注accif current_symbol == '#':cell_value = 'acc'else:cell_value = ''else: # 除了增广产生式以外的情况if current_symbol in lowercase_symbols: # 只填写终结符栏cell_value = 'r' + str(production_index)else:cell_value = ''else:# 计算gotogoto_state = self.goto(self.states[current_states], current_symbol)# 如果存在goto状态,填写状态编号,并根据符号类型添加前缀if goto_state:# 查找goto_state在self.states中的索引goto_index = self.states.index(goto_state)cell_value = goto_indexif current_symbol.islower():cell_value = 'S' + str(cell_value)else:cell_value = ''# 将单元格值填写到表格中self.states_table[row][col] = cell_value# 打印表格for row in range(len(self.states) + 1):for col in range(len(all_sorted_symbols) + 1):print(f"{self.states_table[row][col]:<10}", end='|')print()def parse_string(self, input_string):'''使用LR(0)分析表判断输入字符串是否属于文法:param input_string: 输入字符串:return: 如果是文法中的句子,返回True;否则,返回False'''# 将输入字符串添加结束符'#'input_string += '#'# 初始化分析栈,初始状态,和输入字符串索引states_stack = [0]symbol_stack = ['#']input_index = 0step = 1  # 步骤计数goto_state = ''  # 初始化goto_statewhile True:# 获取当前状态和输入符号current_state = states_stack[-1]current_symbol = input_string[input_index]# 查找LR(0)分析表中的动作action = self.get_action(current_state, current_symbol)# 打印步骤信息print(f"步骤{step:<4} | 状态栈: {str(states_stack):<20} | 符号栈: {str(symbol_stack):<30} | "f"输入串: {input_string[input_index:]:<15} | "f"ACTION: {action:<10} | ", end="")# 如果动作为空,说明分析表中无对应的动作,字符串不属于文法if not action:return False# 根据动作类型进行处理if action.startswith('S'):# 移入动作next_state = int(action[1:])states_stack.append(next_state)symbol_stack += input_string[input_index]input_index += 1print(f"GOTO: ")elif action.startswith('r'):# 规约动作production_index = int(action[1:])production = self.get_production_by_index(production_index)# 弹出产生式右部的符号for _ in range(len(production[1])):states_stack.pop()symbol_stack.pop()# 获取规约后的状态goto_state = self.get_action(states_stack[-1], production[0])states_stack.append(goto_state)symbol_stack.append(production[0])print(f"GOTO: {goto_state}")elif action == 'acc':print(f"GOTO: ")# 接受动作,字符串属于文法return Truestep += 1  # 步骤计数加一def get_action(self, state, symbol):'''根据状态和符号获取LR(0)分析表中的动作:param state: 状态:param symbol: 符号:return: 动作'''# 如果状态和符号在分析表中有对应的动作,则返回动作,否则返回空字符串symbol_index = self.states_table[0].index(symbol)state_index = state + 1action = self.states_table[state_index][symbol_index]return actiondef get_production_by_index(self, production_index):'''根据产生式的索引获取产生式:param production_index: 产生式的索引:return: 产生式'''# 在文法中查找该产生式的位置for left_symbol, productions in self.grammar.items():for index, production in enumerate(productions):if production_index == 0:return left_symbol, productionproduction_index -= 1if __name__ == '__main__':# 创建Tkinter根窗口(不显示)root = tk.Tk()root.withdraw()  # 隐藏Tkinter根窗口# 显示自定义提示窗口info_message = """导入的文件格式示例:E -> aAE -> bBA -> cAA -> dB -> cBB -> d"""messagebox.showinfo("提示", info_message)# 弹出文件选择对话框grammar_file_path = filedialog.askopenfilename(title="选择文件",filetypes=[("Text files", "*.txt"), ("All files", "*.*")])# 定义需要读取的文法文件的路径# grammar_file_path = 'src/grammar_2.txt'# 语法文件不为空if(grammar_file_path):grammar = ReadGrammar(grammar_file_path)  # 转换为该解析器能读取的字典格式lr0_parser = LR0Parser(grammar)  # 创建LR(0)分析器while True:print("\n选择你想要的功能:")print("1. 查看状态集")print("2. 查看状态表")print("3. 分析字符串")print("4. Exit")choice = input("Enter your choice (1-4): ")if choice == '1':lr0_parser.print_states()  # 打印该文法的状态集合elif choice == '2':print("-------------------------------")lr0_parser.print_table()  # 打印LR(0)的状态表print("-------------------------------")elif choice == '3':# 测试字符串 bccdinput_string = input("Enter the string to parse: ")result = lr0_parser.parse_string(input_string)if result:print(f'该输入串:"{input_string}" 属于该文法')else:print(f'该输入串:"{input_string}" 不属于该文法')elif choice == '4':print("成功退出程序")breakelse:print("非法数字!")

结束语

如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/702433.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网购商城系统源码 积分兑换商城系统源码 独立后台附教程

应用介绍 本文来自&#xff1a;网购商城系统源码 积分兑换商城系统源码 独立后台附教程 - 源码1688 简介&#xff1a; 网购商城系统源码 积分兑换商城系统源码 独立后台附教程 测试环境&#xff1a;NginxPHP7.0MySQL5.6thinkphp伪静态 图片&#xff1a;

软件实际应用实例,茶楼收银软件管理系统操作流程,茶室计时计费会员管理系统软件试用版教程

软件实际应用实例&#xff0c;茶楼收银软件管理系统操作流程&#xff0c;茶室计时计费会员管理系统软件试用版教程 一、前言 以下软件以 佳易王茶社计时计费管理系统软件V17.9为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、计时计费&…

JavaWeb——007MYSQL(DQL多表设计)

# 数据库开发-MySQL 一级目录二级目录三级目录 1. 数据库操作-DQL1.1 介绍1.2 语法1.3 基本查询1.4 条件查询1.5 聚合函数1.6 分组查询1.7 排序查询1.8 分页查询1.9 案例1.9.1 案例一1.9.2 案例二 2. 多表设计2.1 一对多2.1.1 表设计2.1.2 外键约束 2.2 一对一2.3 多对多2.4 案…

遥感影像目标检测:从CNN(Faster-RCNN)到Transformer(DETR)

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…

vue或webpack加载highcharts与highcharts-3d

highcharts与highcharts-3d下载 https://jshare.com.cn/demos/hhhhiG 点击对应的文件可打开&#xff0c;复制代码到&#xff08;创建一个同名文件&#xff09;里面&#xff1b;放到项目对应目录下 引入 两种引入 highcharts.js 方法皆可用&#xff1b;注意 highcharts-3d 引入…

超全整理,自动化测试-YAML 配置文件深入解析(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、YAML详情 YAM…

Dear ImGui的UE5.3集成实践

Dear ImGui一直较为火热&#xff0c;这是一个调试使用并且可以响应快速迭代的Gui库&#xff0c;甚至可以做到在任何代码块中调用API即显示。如果你想更多的了解一下可访问其官方网站&#xff1a;https://www.dearimgui.org/ 那么本文就来在UE5中尝试踩坑使用它。 UE4.26版本 …

[引擎开发] 深入C++模板编程

[本文大纲] 引言 模板实例化 隐式实例化 显式实例化 模板具体化 显式具体化 部分具体化 函数重载和具体化 类型推断 隐式类型转换 支持的类型转换 引用和const 通用引用、引用折叠和完美转发 …

RDMA内核态函数ib_post_recv()源码分析

接上文&#xff0c;上文分析了内核rdma向发送队列添加发送请求的函数ib_post_send&#xff0c;本文分析一下向接收队列添加接收请求的函数ib_post_recv。其实函数调用流程与上文类似&#xff0c;不再重复说明&#xff0c;可参考链接。 函数调用过程 最终会调用到这个函数 下面…

浅谈数据分析工具在智慧城市中的作用

随着城市化、技术进步和人口不断增长&#xff0c;智慧城市已成为当今世界主要技术发展之一。 智慧城市设备依靠描述模型对城市环境产生的大量数据进行数据分析。 在这种城市景观中&#xff0c;智慧城市是技术和可持续的城市地区&#xff0c;利用信息和通信技术(ICT)来改善城市…

C语言每日一题(61)盛最多水的容器

题目链接 力扣 11 盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水…

【服务发现--ingress】

1、ingress介绍 Ingress 提供从集群外部到集群内服务的 HTTP 和 HTTPS 路由。 流量路由由 Ingress 资源所定义的规则来控制。 Ingress 是对集群中服务的外部访问进行管理的 API 对象&#xff0c;典型的访问方式是 HTTP。 Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟…

K线实战分析系列之十一:行情力量不足——平头形态

K线实战分析系列之十一&#xff1a;行情力量不足——平头形态 一、平头形态二、不同形态与平头形态的叠加三、总结平头形态 一、平头形态 前一根K线具有较长的实体&#xff0c;后一根K线的实体比较小&#xff0c;无论是多头还是空头的力量到第二根K线都被瓦解了多头上攻&#…

【数据结构与算法】(21)高级数据结构与算法设计之 Dynamic-Programming 动态规划算法 代码示例与详细讲解

目录 4.3 Dynamic-Programming1) Fibonacci降维 2) 最短路径 - Bellman-Ford3) 不同路径-Leetcode 62降维 4) 0-1 背包问题降维 5) 完全背包问题降维 6) 零钱兑换问题-Leetcode322降维零钱兑换 II-Leetcode 518 7) 钢条切割问题降维类似题目 Leetcode-343 整数拆分 8) 最长公共…

python 基础知识点(蓝桥杯python科目个人复习计划51)

今日复习计划&#xff1a;做复习题 例题1&#xff1a;大石头的搬运工 问题描述&#xff1a; 在一款名为“大石头的搬运工”的游戏中&#xff0c;玩家需要 操作一排n堆石头&#xff0c;进行n - 1轮游戏。 每一轮&#xff0c;玩家可以选择一堆石头&#xff0c;并将其移动到任…

【生活】浅浅记录

各位小伙伴们好鸭&#xff0c;今天不是技术文章&#xff0c;浅浅记录一下最近几个月的收获&#x1f60a; 新的一年&#xff0c;一起努力&#xff0c;加油加油&#xff01;

tinymce问题处理

Vite构建工具下Tinymce踩坑指南 解决方案是在路劲前面增加/&#xff0c;这个跟上面链接有些区别&#xff0c;区别原因应该是如果路由采用的是createWebHashHistory则应该去掉/&#xff0c;如果是createWebHistory则应该加上/ 页面引用,一种异步加载&#xff0c;一种同步加载&…

LeetCode 热题 100 | 二叉树(二)

目录 1 543. 二叉树的直径 2 102. 二叉树的层序遍历 3 108. 将有序数组转换为二叉搜索树 菜鸟做题&#xff0c;语言是 C 1 543. 二叉树的直径 这道题和 124. 二叉树中的最大路径和 太像了 题眼&#xff1a;二叉树的 直径 是指树中任意两个节点之间 最长路径的长度 。…

JS基础(三)-操作和流程控制

一 操作网页元素的步骤 1. 查找网页元素 给标签设置id属性&#xff0c;一个网页中的id值不允许重复 <button id"btn">按钮</button> 2. 给按钮绑定事件&#xff0c;监听用户操作 btn.onclick function(){ 一旦监听到用户的…

人工智能 — 特征选择、特征提取、PCA

目录 一、特征选择1、定义2、原因3、做法4、生成过程5、停止条件 二、特征提取三、PCA 算法1、零均值化&#xff08;中心化&#xff09;2、方差3、协方差4、协方差矩阵5、对协方差矩阵求特征值、特征矩阵6、对特征值进行排序7、评价模型8、代码实现9、sklearn 库10、鸢尾花实例…