1 项目简介
这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典算法精心打造,能够高效且准确地处理用户输入的任意有限自动机,并将其转换为与之等价的正规文法。这一转换器不仅为用户提供了便捷的工具,还为理解和研究自动机与正规文法之间的关系提供了有力的支持,是进行相关学习和研究的得力助手。
2 功能特点
🖥️ “专业GUI界面”: 多选项卡设计,类似专业分析器生成器
🔄 “智能转换”: 自动将FA转换为等价的正规文法
✅ “完整验证”: 输入验证、FA完整性检查
📊 “可视化展示”: FA结构图、转换表、语言特征分析
💾 “文件操作“: 支持保存/加载FA定义、导出结果
🎯 ”示例库“: 内置多个经典FA示例
⚡ ”一键打包“: 支持打包为独立exe文件
🌐 ”跨平台“: 支持Windows、Linux、macOS
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext, filedialog
import json
from collections import defaultdict
import os
import sys
class FAToGrammarConverter:
def __init__(self, root):
self.root = root
self.root.title("有限自动机到正规文法转换器 v1.0")
self.root.geometry("1200x800")
self.root.minsize(1000, 700)
# 设置图标(如果存在)
try:
self.root.iconbitmap('icon.ico')
except:
pass
# 存储FA信息
self.states = set()
self.alphabet = set()
self.transitions = defaultdict(list)
self.start_state = ""
self.final_states = set()
self.create_gui()
self.create_menu()
def create_menu(self):
"""创建菜单栏"""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="文件", menu=file_menu)
file_menu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
file_menu.add_separator()
file_menu.add_command(label="导出结果", command=self.export_result)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.root.quit)
# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="编辑", menu=edit_menu)
edit_menu.add_command(label="清空输入", command=self.clear_input)
edit_menu.add_command(label="加载示例", command=self.load_example)
# 工具菜单
tools_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="工具", menu=tools_menu)
tools_menu.add_command(label="验证FA", command=self.validate_fa)
tools_menu.add_command(label="转换为正规文法", command=self.convert_fa_to_grammar)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="帮助", menu=help_menu)
help_menu.add_command(label="使用说明", command=self.show_help)
help_menu.add_command(label="关于", command=self.show_about)
# 绑定快捷键
self.root.bind_all("<Control-n>", lambda e: self.new_file())
self.root.bind_all("<Control-o>", lambda e: self.open_file())
self.root.bind_all("<Control-s>", lambda e: self.save_file())
def create_gui(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(0, weight=1)
main_frame.rowconfigure(1, weight=1)
# 创建Notebook(选项卡)
notebook = ttk.Notebook(main_frame)
notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
# 输入选项卡
input_frame = ttk.Frame(notebook, padding="10")
notebook.add(input_frame, text="输入有限自动机")
# 结果选项卡
result_frame = ttk.Frame(notebook, padding="10")
notebook.add(result_frame, text="转换结果")
# 可视化选项卡
visual_frame = ttk.Frame(notebook, padding="10")
notebook.add(visual_frame, text="可视化")
self.create_input_tab(input_frame)
self.create_result_tab(result_frame)
self.create_visual_tab(visual_frame)
# 状态栏
self.status_bar = ttk.Label(main_frame, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
self.status_bar.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(5, 0))
def create_input_tab(self, parent):
"""创建输入选项卡"""
# 标题
title_label = ttk.Label(parent, text="有限自动机输入界面",
font=("Microsoft YaHei", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
# 左侧输入区域
left_frame = ttk.LabelFrame(parent, text="FA定义", padding="10")
left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
left_frame.columnconfigure(1, weight=1)
# 状态集合
ttk.Label(left_frame, text="状态集合 Q:", font=("Microsoft YaHei", 10, "bold")).grid(row=0, column=0, sticky=tk.W, pady=5)
self.states_entry = ttk.Entry(left_frame, width=40, font=("Consolas", 10))
self.states_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=5)
ttk.Label(left_frame, text="用逗号分隔,如: q0,q1,q2",
font=("Microsoft YaHei", 9), foreground="gray").grid(row=0, column=2, sticky=tk.W, padx=(10, 0))
# 字母表
ttk.Label(left_frame, text="字母表 Σ:", font=("Microsoft YaHei", 10, "bold")).grid(row=1, column=0, sticky=tk.W, pady=5)
self.alphabet_entry = ttk.Entry(left_frame, width=40, font=("Consolas", 10))
self.alphabet_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=5)
ttk.Label(left_frame, text="用逗号分隔,如: a,b,c",
font=("Microsoft YaHei", 9), foreground="gray").grid(row=1, column=2, sticky=tk.W, padx=(10, 0))
# 转换函数
ttk.Label(left_frame, text="转换函数 δ:", font=("Microsoft YaHei", 10, "bold")).grid(row=2, column=0, sticky=tk.W, pady=5)
self.transitions_text = scrolledtext.ScrolledText(left_frame, height=8, width=40, font=("Consolas", 10))
self.transitions_text.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=5)
ttk.Label(left_frame, text="每行一个转换\n格式: 起始状态,输入符号,目标状态",
font=("Microsoft YaHei", 9), foreground="gray").grid(row=2, column=2, sticky=tk.W, padx=(10, 0))
# 初始状态
ttk.Label(left_frame, text="初始状态 q₀:", font=("Microsoft YaHei", 10, "bold")).grid(row=3, column=0, sticky=tk.W, pady=5)
self.start_state_entry = ttk.Entry(left_frame, width=40, font=("Consolas", 10))
self.start_state_entry.grid(row=3, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=5)
# 终结状态
ttk.Label(left_frame, text="终结状态 F:", font=("Microsoft YaHei", 10, "bold")).grid(row=4, column=0, sticky=tk.W, pady=5)
self.final_states_entry = ttk.Entry(left_frame, width=40, font=("Consolas", 10))
self.final_states_entry.grid(row=4, column=1, sticky=(tk.W, tk.E), padx=(10, 0), pady=5)
ttk.Label(left_frame, text="用逗号分隔",
font=("Microsoft YaHei", 9), foreground="gray").grid(row=4, column=2, sticky=tk.W, padx=(10, 0))
# 右侧按钮区域
right_frame = ttk.LabelFrame(parent, text="操作", padding="10")
right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(10, 0))
# 按钮
ttk.Button(right_frame, text="加载示例", command=self.load_example, width=15).grid(row=0, column=0, pady=5, sticky=tk.W+tk.E)
ttk.Button(right_frame, text="验证FA", command=self.validate_fa, width=15).grid(row=1, column=0, pady=5, sticky=tk.W+tk.E)
ttk.Button(right_frame, text="转换为正规文法", command=self.convert_fa_to_grammar,
style="Accent.TButton", width=15).grid(row=2, column=0, pady=10, sticky=tk.W+tk.E)
ttk.Button(right_frame, text="清空输入", command=self.clear_input, width=15).grid(row=3, column=0, pady=5, sticky=tk.W+tk.E)
# 示例列表
ttk.Label(right_frame, text="示例库:", font=("Microsoft YaHei", 10, "bold")).grid(row=4, column=0, sticky=tk.W, pady=(20, 5))
self.example_listbox = tk.Listbox(right_frame, height=6, font=("Microsoft YaHei", 9))
self.example_listbox.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=5)
self.example_listbox.insert(0, "识别以'a'开头的字符串")
self.example_listbox.insert(1, "识别偶数个'a'")
self.example_listbox.insert(2, "识别包含'ab'的字符串")
self.example_listbox.bind("<Double-Button-1>", self.load_selected_example)
# 配置权重
parent.columnconfigure(0, weight=2)
parent.columnconfigure(1, weight=1)
parent.rowconfigure(1, weight=1)
def create_result_tab(self, parent):
"""创建结果选项卡"""
# 结果显示区域
result_frame = ttk.LabelFrame(parent, text="转换结果 - 正规文法", padding="10")
result_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
result_frame.columnconfigure(0, weight=1)
result_frame.rowconfigure(0, weight=1)
self.result_text = scrolledtext.ScrolledText(result_frame, height=25, width=100,
font=("Consolas", 11))
self.result_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 按钮区域
button_frame = ttk.Frame(parent)
button_frame.grid(row=1, column=0, pady=(10, 0))
ttk.Button(button_frame, text="复制结果", command=self.copy_result).grid(row=0, column=0, padx=5)
ttk.Button(button_frame, text="保存结果", command=self.save_result).grid(row=0, column=1, padx=5)
ttk.Button(button_frame, text="清空结果", command=self.clear_result).grid(row=0, column=2, padx=5)
parent.columnconfigure(0, weight=1)
parent.rowconfigure(0, weight=1)
def create_visual_tab(self, parent):
"""创建可视化选项卡"""
# 可视化区域
visual_frame = ttk.LabelFrame(parent, text="FA结构可视化", padding="10")
visual_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
visual_frame.columnconfigure(0, weight=1)
visual_frame.rowconfigure(0, weight=1)
self.visual_text = scrolledtext.ScrolledText(visual_frame, height=25, width=100,
font=("Consolas", 10))
self.visual_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
parent.columnconfigure(0, weight=1)
parent.rowconfigure(0, weight=1)
def load_selected_example(self, event):
"""加载选中的示例"""
selection = self.example_listbox.curselection()
if selection:
index = selection[0]
if index == 0:
self.load_example()
elif index == 1:
self.load_even_a_example()
elif index == 2:
self.load_contains_ab_example()
def load_example(self):
"""加载示例FA - 识别以'a'开头的字符串"""
self.clear_input()
self.states_entry.insert(0, "q0,q1,q2")
self.alphabet_entry.insert(0, "a,b")
self.transitions_text.insert(1.0, "q0,a,q1\nq1,a,q1\nq1,b,q1\nq0,b,q2\nq2,a,q2\nq2,b,q2")
self.start_state_entry.insert(0, "q0")
self.final_states_entry.insert(0, "q1")
self.update_status("已加载示例:识别以'a'开头的字符串")
def load_even_a_example(self):
"""加载示例FA - 识别偶数个'a'"""
self.clear_input()
self.states_entry.insert(0, "q0,q1")
self.alphabet_entry.insert(0, "a,b")
self.transitions_text.insert(1.0, "q0,a,q1\nq1,a,q0\nq0,b,q0\nq1,b,q1")
self.start_state_entry.insert(0, "q0")
self.final_states_entry.insert(0, "q0")
self.update_status("已加载示例:识别偶数个'a'")
def load_contains_ab_example(self):
"""加载示例FA - 识别包含'ab'的字符串"""
self.clear_input()
self.states_entry.insert(0, "q0,q1,q2")
self.alphabet_entry.insert(0, "a,b")
self.transitions_text.insert(1.0, "q0,a,q1\nq0,b,q0\nq1,a,q1\nq1,b,q2\nq2,a,q2\nq2,b,q2")
self.start_state_entry.insert(0, "q0")
self.final_states_entry.insert(0, "q2")
self.update_status("已加载示例:识别包含'ab'的字符串")
def clear_input(self):
"""清空所有输入"""
self.states_entry.delete(0, tk.END)
self.alphabet_entry.delete(0, tk.END)
self.transitions_text.delete(1.0, tk.END)
self.start_state_entry.delete(0, tk.END)
self.final_states_entry.delete(0, tk.END)
self.update_status("已清空输入")
def update_status(self, message):
"""更新状态栏"""
self.status_bar.config(text=message)
def parse_input(self):
"""解析用户输入的FA"""
try:
# 解析状态集合
states_str = self.states_entry.get().strip()
if not states_str:
raise ValueError("状态集合不能为空")
self.states = set(s.strip() for s in states_str.split(','))
# 解析字母表
alphabet_str = self.alphabet_entry.get().strip()
if not alphabet_str:
raise ValueError("字母表不能为空")
self.alphabet = set(s.strip() for s in alphabet_str.split(','))
# 解析转换函数
transitions_str = self.transitions_text.get(1.0, tk.END).strip()
if not transitions_str:
raise ValueError("转换函数不能为空")
self.transitions = defaultdict(list)
for line in transitions_str.split('\n'):
line = line.strip()
if line:
parts = [p.strip() for p in line.split(',')]
if len(parts) != 3:
raise ValueError(f"转换函数格式错误: {line}")
from_state, symbol, to_state = parts
if from_state not in self.states:
raise ValueError(f"状态 {from_state} 不在状态集合中")
if to_state not in self.states:
raise ValueError(f"状态 {to_state} 不在状态集合中")
if symbol not in self.alphabet:
raise ValueError(f"符号 {symbol} 不在字母表中")
self.transitions[(from_state, symbol)].append(to_state)
# 解析初始状态
self.start_state = self.start_state_entry.get().strip()
if not self.start_state:
raise ValueError("初始状态不能为空")
if self.start_state not in self.states:
raise ValueError("初始状态不在状态集合中")
# 解析终结状态
final_states_str = self.final_states_entry.get().strip()
if not final_states_str:
raise ValueError("终结状态不能为空")
self.final_states = set(s.strip() for s in final_states_str.split(','))
for state in self.final_states:
if state not in self.states:
raise ValueError(f"终结状态 {state} 不在状态集合中")
return True
except ValueError as e:
messagebox.showerror("输入错误", str(e))
self.update_status(f"错误: {str(e)}")
return False
def validate_fa(self):
"""验证FA的完整性和正确性"""
if not self.parse_input():
return False
try:
# 检查是否所有状态都可达
reachable_states = set()
stack = [self.start_state]
reachable_states.add(self.start_state)
while stack:
current = stack.pop()
for symbol in self.alphabet:
if (current, symbol) in self.transitions:
for next_state in self.transitions[(current, symbol)]:
if next_state not in reachable_states:
reachable_states.add(next_state)
stack.append(next_state)
unreachable = self.states - reachable_states
# 显示验证结果
result = ["FA验证结果:", "=" * 30]
result.append(f"状态总数: {len(self.states)}")
result.append(f"字母表大小: {len(self.alphabet)}")
result.append(f"转换函数数量: {sum(len(v) for v in self.transitions.values())}")
result.append(f"可达状态: {len(reachable_states)}")
if unreachable:
result.append(f"不可达状态: {', '.join(unreachable)}")
result.append("警告: 存在不可达状态!")
else:
result.append("✓ 所有状态都可达")
# 检查FA类型
is_dfa = all(len(transitions) <= 1 for transitions in self.transitions.values())
result.append(f"FA类型: {'DFA (确定有限自动机)' if is_dfa else 'NFA (非确定有限自动机)'}")
messagebox.showinfo("验证结果", "\n".join(result))
self.update_status("FA验证完成")
return True
except Exception as e:
messagebox.showerror("验证错误", f"验证过程中发生错误: {str(e)}")
self.update_status(f"验证失败: {str(e)}")
return False
def convert_fa_to_grammar(self):
"""将FA转换为正规文法"""
if not self.parse_input():
return
try:
# 构造正规文法
grammar = self.fa_to_regular_grammar()
self.display_grammar(grammar)
self.display_visualization()
self.update_status("转换完成")
except Exception as e:
messagebox.showerror("转换错误", f"转换过程中发生错误: {str(e)}")
self.update_status(f"转换失败: {str(e)}")
def fa_to_regular_grammar(self):
"""FA到正规文法的转换算法"""
productions = []
# 对于每个转换 δ(q, a) = p,创建产生式 q → ap
for (from_state, symbol), to_states in self.transitions.items():
for to_state in to_states:
production = f"{from_state} → {symbol}{to_state}"
productions.append(production)
# 对于每个终结状态 f ∈ F,创建产生式 f → ε
for final_state in self.final_states:
production = f"{final_state} → ε"
productions.append(production)
grammar = {
'non_terminals': self.states,
'terminals': self.alphabet,
'start_symbol': self.start_state,
'productions': productions
}
return grammar
def display_grammar(self, grammar):
"""显示转换后的正规文法"""
result = []
result.append("╔" + "═" * 60 + "╗")
result.append("║" + "转换结果:正规文法 G = (N, T, P, S)".center(60) + "║")
result.append("╚" + "═" * 60 + "╝")
result.append("")
# 非终结符集合
result.append("【非终结符集合】")
result.append(f"N = {{{', '.join(sorted(grammar['non_terminals']))}}}")
result.append("")
# 终结符集合
result.append("【终结符集合】")
result.append(f"T = {{{', '.join(sorted(grammar['terminals']))}}}")
result.append("")
# 开始符号
result.append("【开始符号】")
result.append(f"S = {grammar['start_symbol']}")
result.append("")
# 产生式集合
result.append("【产生式集合】")
result.append("P = {")
for i, production in enumerate(grammar['productions']):
prefix = " " if i < len(grammar['productions']) - 1 else " "
suffix = "," if i < len(grammar['productions']) - 1 else ""
result.append(f"{prefix}{production}{suffix}")
result.append("}")
result.append("")
# 转换算法说明
result.append("╔" + "═" * 60 + "╗")
result.append("║" + "转换算法说明".center(60) + "║")
result.append("╚" + "═" * 60 + "╝")
result.append("")
result.append("算法步骤:")
result.append("1. 对于FA中的每个转换 δ(q, a) = p,")
result.append(" 创建产生式 q → ap")
result.append("")
result.append("2. 对于FA中的每个终结状态 f ∈ F,")
result.append(" 创建产生式 f → ε")
result.append("")
result.append("3. FA的初始状态成为文法的开始符号")
result.append("")
result.append("✓ 此正规文法生成的语言与原FA识别的语言相同")
result.append("✓ 转换保持了语言的等价性")
# 显示结果
self.result_text.delete(1.0, tk.END)
self.result_text.insert(1.0, '\n'.join(result))
def display_visualization(self):
"""显示FA的可视化表示"""
visual = []
visual.append("╔" + "═" * 60 + "╗")
visual.append("║" + "有限自动机结构可视化".center(60) + "║")
visual.append("╚" + "═" * 60 + "╝")
visual.append("")
# 状态图表示
visual.append("【状态转换图】")
visual.append("")
# 绘制状态
for state in sorted(self.states):
state_type = ""
if state == self.start_state:
state_type += "[初始]"
if state in self.final_states:
state_type += "[终结]"
visual.append(f"状态 {state} {state_type}")
# 显示从该状态出发的转换
for symbol in sorted(self.alphabet):
if (state, symbol) in self.transitions:
for target in self.transitions[(state, symbol)]:
visual.append(f" --{symbol}--> {target}")
visual.append("")
# 转换表
visual.append("【转换表】")
visual.append("δ" + " " * 8 + "|" + " ".join(f"{s:>8}" for s in sorted(self.alphabet)))
visual.append("-" * (10 + 9 * len(self.alphabet)))
for state in sorted(self.states):
row = f"{state:<8} |"
for symbol in sorted(self.alphabet):
if (state, symbol) in self.transitions:
targets = ",".join(self.transitions[(state, symbol)])
row += f"{targets:>8} "
else:
row += f"{'∅':>8} "
visual.append(row)
visual.append("")
visual.append("【语言描述】")
visual.append("此FA识别的正规语言L(FA)的特征:")
# 简单的语言特征分析
if len(self.final_states) == 1 and self.start_state in self.final_states:
visual.append("- 包含空字符串ε")
# 检查是否存在循环
has_loops = any(state in self.transitions.get((state, symbol), [])
for state in self.states for symbol in self.alphabet)
if has_loops:
visual.append("- 可以生成无限长度的字符串")
visual.append(f"- 字母表: {{{', '.join(sorted(self.alphabet))}}}")
visual.append(f"- 状态数: {len(self.states)}")
self.visual_text.delete(1.0, tk.END)
self.visual_text.insert(1.0, '\n'.join(visual))
def copy_result(self):
"""复制结果到剪贴板"""
self.root.clipboard_clear()
self.root.clipboard_append(self.result_text.get(1.0, tk.END))
self.update_status("结果已复制到剪贴板")
def save_result(self):
"""保存结果到文件"""
filename = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if filename:
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(self.result_text.get(1.0, tk.END))
self.update_status(f"结果已保存到: {filename}")
except Exception as e:
messagebox.showerror("保存错误", f"无法保存文件: {str(e)}")
def clear_result(self):
"""清空结果"""
self.result_text.delete(1.0, tk.END)
self.visual_text.delete(1.0, tk.END)
self.update_status("结果已清空")
def new_file(self):
"""新建文件"""
self.clear_input()
self.clear_result()
self.update_status("新建文件")
def open_file(self):
"""打开文件"""
filename = filedialog.askopenfilename(
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
)
if filename:
try:
with open(filename, 'r', encoding='utf-8') as f:
data = json.load(f)
self.clear_input()
self.states_entry.insert(0, ','.join(data.get('states', [])))
self.alphabet_entry.insert(0, ','.join(data.get('alphabet', [])))
self.start_state_entry.insert(0, data.get('start_state', ''))
self.final_states_entry.insert(0, ','.join(data.get('final_states', [])))
transitions_text = '\n'.join([f"{t[0]},{t[1]},{t[2]}" for t in data.get('transitions', [])])
self.transitions_text.insert(1.0, transitions_text)
self.update_status(f"已打开文件: {filename}")
except Exception as e:
messagebox.showerror("打开错误", f"无法打开文件: {str(e)}")
def save_file(self):
"""保存文件"""
if not self.parse_input():
return
filename = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
)
if filename:
try:
transitions = []
for (from_state, symbol), to_states in self.transitions.items():
for to_state in to_states:
transitions.append([from_state, symbol, to_state])
data = {
'states': list(self.states),
'alphabet': list(self.alphabet),
'transitions': transitions,
'start_state': self.start_state,
'final_states': list(self.final_states)
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
self.update_status(f"已保存到: {filename}")
except Exception as e:
messagebox.showerror("保存错误", f"无法保存文件: {str(e)}")
def export_result(self):
"""导出转换结果"""
if not self.result_text.get(1.0, tk.END).strip():
messagebox.showwarning("导出警告", "没有可导出的结果")
return
filename = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
)
if filename:
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(self.result_text.get(1.0, tk.END))
f.write("\n" + "="*60 + "\n")
f.write("可视化信息:\n")
f.write(self.visual_text.get(1.0, tk.END))
self.update_status(f"结果已导出到: {filename}")
except Exception as e:
messagebox.showerror("导出错误", f"无法导出文件: {str(e)}")
def show_help(self):
"""显示帮助信息"""
help_text = """
有限自动机到正规文法转换器 - 使用说明
1. 输入有限自动机:
• 状态集合:输入所有状态,用逗号分隔
• 字母表:输入所有输入符号,用逗号分隔
• 转换函数:每行一个转换,格式为"起始状态,输入符号,目标状态"
• 初始状态:输入初始状态
• 终结状态:输入所有终结状态,用逗号分隔
2. 操作步骤:
• 点击"加载示例"可以快速加载预设的FA
• 点击"验证FA"检查输入的FA是否正确
• 点击"转换为正规文法"执行转换
• 在"转换结果"选项卡查看结果
• 在"可视化"选项卡查看FA结构
3. 文件操作:
• 支持保存/打开FA定义文件(JSON格式)
• 支持导出转换结果(文本格式)
4. 快捷键:
• Ctrl+N:新建
• Ctrl+O:打开
• Ctrl+S:保存
5. 注意事项:
• 状态名和符号不能包含逗号
• 支持确定和非确定有限自动机
• 转换后的正规文法与原FA等价
"""
messagebox.showinfo("使用说明", help_text)
def show_about(self):
"""显示关于信息"""
about_text = """
有限自动机到正规文法转换器 v1.0
"""
messagebox.showinfo("关于", about_text)
def main():
root = tk.Tk()
# 设置样式
style = ttk.Style()
style.theme_use('clam')
# 配置颜色主题
style.configure('Accent.TButton', background='#0078D4', foreground='white')
app = FAToGrammarConverter(root)
# 设置窗口居中
root.update_idletasks()
x = (root.winfo_screenwidth() // 2) - (root.winfo_width() // 2)
y = (root.winfo_screenheight() // 2) - (root.winfo_height() // 2)
root.geometry(f"+{x}+{y}")
root.mainloop()
if __name__ == "__main__":
main()
安装依赖
pip install -r requirements.txt
# 有限自动机到正规文法转换器依赖
# Python 3.6+ 兼容
# GUI框架 (通常随Python安装)
# tkinter - 内置模块
# 打包工具
pyinstaller>=5.0.0
# 其他依赖
setuptools>=40.0.0
```bash
# 运行构建脚本
build.bat
# 或手动构建
pip install pyinstaller
pyinstaller --onefile --windowed --name="FA转换器" fa_to_grammar_converter.py
# 运行exe文件
dist\FA转换器.exe
# 设置执行权限
chmod +x build.sh
# 运行构建脚本
./build.sh
# 运行可执行文件
./dist/FA转换器