编译原理-LR0分析表(含python代码)

问题描述

  • 输入产生式文法,若符合LR0文法,则分析得到该文法的LR0分析表:Goto表和Action表。

算法设计

  • 1、 判断该文法是否符合LR0文法:

    • a) 规约-规约冲突
      • i. 若该文法中有两条产生式的右部相同,则会产生“规约-规约”冲突,不符合LR0文法。
    • b) 移进-规约冲突
      • i. 若该文法中含有产生式能产生空 @ ,则会产生移进-规约,不符合LR0文法。
      • ii. 若分析该文法时,构建自动机过程中,有某个节点中的所有产生式中,有任意两条产生式,一条需要移进,一条需要规约,则不符合LR0文法。
  • 2、 创建活前缀自动机

    • a) 给出开始产生式作为自动机开始节点,以点 “ . ”为记号,记录推导的点
    • b) 分析该节点的产生式是否为完整:
      • i. 完整即为:” . “后不为非终结符。
      • ii. 不完整即为:“ . ”后为非终结符。还可以根据该非终结符展开得到其他产生式。
    • c) 由该节点分析其邻接节点
      • i. 若还能接受输入,则可以产生新的节点
      • ii. 若不能接受输入,则该节点应该为Reduce节点或Accept节点
          1. 若“ . ”前为结束符 # ,则为Accept节点
          1. 否则为Reduce节点
    • d) 新产生的所有节点回到(b)步骤继续分析,直至不产生新的节点,算法结束。
    • e) 案例:
      • i. 文法为:

      • 文法

      • ii. 开始产生式为S -> E$,所以开始节点为:

      • 图2  开始节点

      • iii. 因为该节点产生式有” . ”后为非终结符,则代表产生式未完整,需要进一步展开,得到:

      • 图3  第一次展开非终结符

      • iv. 易于知道,该节点产生式还未完整,还可以展开,最后得到:

      • 图4  完整的开始节点

      • v. 分析该节点能接受的输入,从而得到其邻接节点,易于知道,其能接受的输入为“ E、T、int、( “四个输入,所以邻接节点数量为4个。

      • 图5  开始节点的邻接节点

      • vi. 然后分析新产生节点的邻接节点即可,最后可得:

      • 图6  自动机结果

  • 3、 由自动机结构构件Goto表和Action表

    • a) 首先给自动机每个节点标号,如下:

      • 图7 标号的自动机
    • b) 根据标号的自动机建立Goto表和Action表,其中个标号代表两个表的相应行,Goto表中列头部的非终结符和终结符代表该节点接受的输入,空代表Error,Action表内容代表每个节点进行的相应操作:

      • 图 8 Goto表和Action表
  • 4、 至此,LR0分析完成。

测试结果

图9  输入产生式

图10 Goto表和Action表结果

图11  每个节点的内容

源代码

import tkinter as tk
from tkinter import *
from tkinter.ttk import Treeview
import tkinter.messagebox
from tkinter import ttk


bg = "#F0F8FF"  # 背景颜色

y = 1  # 行
product_list = {}  # 产生式集合

terminal_char = []


# 窗口居中
def CenterWindow(window, width, height, bg=bg):
    window.configure(bg=bg)  # 背景颜色
    # 设置窗口居中
    screenwidth = window.winfo_screenwidth()
    screenheight = window.winfo_screenheight()
    alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2)
    window.geometry(alignstr)
    # 设置窗口大小不可改变
    window.resizable(width=False, height=False)


# 判断产生式左边是否正确   规则:1、只能为大写字母,且数量为一个
def is_left_correct(str: str):
    if len(str) == 0 or len(str) > 1:
        tk.messagebox.showerror('提示', '左边只能为大写字母且数量是1,请检查!!')
        return False
    elif str >= 'A' and str <= 'Z':
        return True
    tk.messagebox.showerror('提示', '左边只能为大写字母且数量是1,请检查!!')
    return False


# 判断产生式右边是否正确   规则:1、不能含空格2、不能含数字3、@后不能含任何元素
def is_right_correct(str: str):
    if len(str) == 1 and str[0] == '@':
        return True
    for index in range(len(str)):
        if str[index] == '@' and len(str) != 1:
            tk.messagebox.showerror('提示', '@前后不能含任何元素,请检查!!')
            return False
    return True


# 双击列表行   修改产生式
tip = False  # 是否不显示提示


def treevie_double_wclick(event, tree, e1, e2):
    global y
    if len(tree.selection()) == 0:
        return ""
    global tip, product_list

    for item in tree.selection():
        item_text = tree.item(item, "values")
        tree.delete(item)
        product_list[item_text[1]].remove(item_text[3])
        if len(product_list[item_text[1]]) == 0:
            product_list.pop(item_text[1])
        e1.delete(0, "end")
        e2.delete(0, "end")
        e1.insert("end", item_text[1])
        e2.insert("end", item_text[3])

    # 重新排序
    sort_tree(tree)

    if not tip:
        tip = tkinter.messagebox.askyesno('提示', '所选数据已返回,请修改后重新提交\n是否不再提示(是  or  否)?')


# 添加产生式按钮
def btn_add_data(e1, e2, tree):
    global product_list
    left = e1.get()
    right = e2.get()
    if len(left) == 0 or len(right) == 0:
        tk.messagebox.showerror('提示', '不能空输入!!')
        return ""
    if not is_left_correct(left):
        return ""
    if not is_right_correct(right):
        return ""
    # 判断是否已存在该字典key值
    # 存在key值
    if left in product_list.keys():
        # 若重复  提示重复
        if right in product_list[left]:
            tk.messagebox.showerror('提示', '不能输入重复的产生式!!')
            return ""
        # 若不重复  直接添加
        else:
            product_list[left].append(right)
    # 不存在key值
    # 添加列表
    else:
        product_list[left] = [right]
    # 添加数据
    add_data(left, right, tree)
    sort_tree(tree)
    e1.delete(0, "end")
    e2.delete(0, "end")


# 给列表添加数据
def add_data(left, right, tree):  # 添加数据
    global y
    tree.insert("", "end", values=(y, left, "-->", right))
    y += 1


# 清空列表
def clear_tree(tree, clear_pro=False):
    global product_list
    item_list = tree.get_children()
    for item in item_list:
        tree.delete(item)
    if clear_pro:
        product_list.clear()
    # print(product_list)


# 获取第二个参数
def take_second(elemt):
    return elemt[1]


# 获取第一个参数
def take_first(elemt):
    return elemt[0]


# 排序表格
def sort_tree(tree):
    global y
    y = 1
    data_list = []
    item_list = tree.get_children()
    for item in item_list:
        item_text = tree.item(item, "values")
        data_list.append(item_text)
    clear_tree(tree)
    data_list.sort(key=take_second)
    for item in data_list:
        add_data(item[1], item[3], tree)


def create_tree(window, cols):
    # 表格窗体容器
    frame = Frame(window, bg=bg)
    frame.pack()
    ybar = Scrollbar(frame, orient='vertical')  # 竖直滚动条
    # 表格
    style = ttk.Style(window)
    # set ttk theme to "clam" which support the fieldbackground option
    style.theme_use("clam")
    style.configure("Treeview", background=bg,
                    fieldbackground=bg, foreground="green")
    tree = Treeview(frame, show='headings', columns=cols, yscrollcommand=ybar.set, height=12, selectmode="browse")

    ybar['command'] = tree.yview

    tree.grid(row=0, column=0)  # grid方案
    ybar.grid(row=0, column=1, sticky='ns')

    return tree


def create_tree_goto_action(window, cols_goto, cols_action, window_width):
    # goto表格窗体容器
    goto_frame = Frame(window, bg=bg)
    goto_frame.place(x=10, y=21)
    goto_ybar = Scrollbar(goto_frame, orient='vertical')  # 竖直滚动条
    # 表格
    style = ttk.Style(window)
    # set ttk theme to "clam" which support the fieldbackground option
    style.theme_use("clam")
    style.configure("Treeview", background=bg,
                    fieldbackground=bg, foreground="green")
    tree_goto = Treeview(goto_frame, show='headings', columns=cols_goto, yscrollcommand=goto_ybar.set, height=24,
                         selectmode="browse")

    goto_ybar['command'] = tree_goto.yview

    tree_goto.grid(row=0, column=0)  # grid方案
    goto_ybar.grid(row=0, column=1, sticky='ns')

    # action表格窗体容器
    action_frame = Frame(window, bg=bg)
    action_frame.place(x=window_width - 220, y=21)
    action_ybar = Scrollbar(action_frame, orient='vertical')  # 竖直滚动条
    # 表格
    tree_action = Treeview(action_frame, show='headings', columns=cols_action, yscrollcommand=action_ybar.set,
                           height=24, selectmode="browse")

    action_ybar['command'] = tree_action.yview

    tree_action.grid(row=0, column=0)  # grid方案
    action_ybar.grid(row=0, column=1, sticky='ns')

    return [tree_goto, tree_action]


# 初始化窗口
def init_window():
    window = tk.Tk()
    window.title("LR0分析表")
    CenterWindow(window, 400, 410, bg)
    lable = tk.Label(window, text="产生式( 双击修改,重新写入 ),结束符为 #", bg=bg)
    lable.pack()
    lable2 = tk.Label(window, text="请不要写‘ | ’,产生式右边为单独整体,默认为空(@)", bg=bg)
    lable2.pack()

    cols = ["行", "左边", "箭头", "右边"]  # 数据
    tree = create_tree(window, cols)  # 创建表格

    tree.heading('行', text="行")
    tree.heading('左边', text="左边")
    tree.heading('箭头', text="箭头")
    tree.heading('右边', text="右边")
    tree.column('行', width=25, anchor=S)  # 定义列
    tree.column('左边', width=60, anchor=S)  # 定义列
    tree.column('箭头', width=40, anchor=S)  # 定义列
    tree.column('右边', width=150, anchor=S)  # 定义列

    tree.bind('<Double-Button-1>', lambda event: treevie_double_wclick(event, tree, e1, e2))  # 双击事件绑定

    e1 = Entry(window, width=6, fg='green')
    e1.place(x=80, y=320)
    lable_jiantou = Label(window, text="-->", bg=bg)
    lable_jiantou.place(x=140, y=320)
    e2 = Entry(window, width=10, fg='green')
    e2.place(x=175, y=320)
    btn_add = Button(window, text="添加产生式", command=lambda: btn_add_data(e1, e2, tree), bg=bg)

    btn_add.place(x=280, y=318)

    LR0_btn = tk.Button(window, text="求LR0", activebackground="#40E0D0", bg=bg, command=LR0_table)
    # SLR1_btn = tk.Button(window, text="求SLR(1)", activebackground="#40E0D0", bg=bg, command=SLR1_table)
    btn_y = 350
    btn_x = 80
    btn_w = 70
    btn_h = 40
    LR0_btn.place(x=btn_x, y=btn_y, width=btn_w, height=btn_h)
    # SLR1_btn.place(x=btn_x + 100, y=btn_y, width=btn_w + 15, height=btn_h)

    btn_delete = tk.Button(window, text="清空表格", command=lambda: clear_tree(tree, True), bg=bg,
                           activebackground="#40E0D0")
    btn_delete.place(x=btn_x + 200, y=btn_y, width=btn_w, height=btn_h)

    '''测试数据'''
    '''测试数据一'''  # 符合LR(0)文法
    add_data('E', 'TB#', tree)
    add_data('B', '+TB', tree)
    add_data('T', 'FC', tree)
    add_data('C', '*FC', tree)
    add_data('F', '(E)', tree)
    add_data('F', 'i', tree)
    sort_tree(tree)
    product_list['B'] = ['+TB']
    product_list['C'] = ['*FC']
    product_list['E'] = ['TB#']
    product_list['F'] = ['(E)', 'i']
    product_list['T'] = ['FC']

    '''测试数据二'''  #
    # add_data('S', 'AB', tree)
    # add_data('A', 'Da', tree)
    # add_data('B', 'cC', tree)
    # add_data('C', 'aADC', tree)
    # add_data('D', 'b', tree)
    # sort_tree(tree)
    # product_list['S'] = ['AB']
    # product_list['A'] = ['Da']
    # product_list['B'] = ['cC']
    # product_list['C'] = ['aADC']
    # product_list['D'] = ['b']

    '''测试数据三'''  #
    # add_data('S', 'AB', tree)
    # add_data('S', 'bC', tree)
    # add_data('A', 'b', tree)
    # add_data('B', 'aD', tree)
    # add_data('C', 'AD', tree)
    # add_data('C', 'b', tree)
    # add_data('D', 'aS', tree)
    # add_data('D', 'c', tree)
    # sort_tree(tree)
    # product_list['S'] = ['AB', 'bC']
    # product_list['A'] = ['b']
    # product_list['B'] = ['aD']
    # product_list['C'] = ['AD', 'b']
    # product_list['D'] = ['aS', 'c']

    '''测试数据'''
    window.mainloop()


# 关闭窗口
def close_window(window):
    window.destroy()


# 判断字符类型
def type_char(c):
    if c >= 'A' and c <= 'Z':
        return 'N'  # 非终结符
    return 'T'  # 终结符


# 创建终结符集合
def create_terminal():
    global terminal_char, product_list
    terminal_char.clear()
    for key in product_list.keys():
        for item in product_list[key]:
            for index in range(len(item)):
                if type_char(item[index]) == 'T' and item[index] not in terminal_char and item[index] != '@':
                    terminal_char.append(item[index])
    if '#' not in terminal_char:
        terminal_char.append('#')


node_dic = {}  # 装全部节点  每个node_index  对应一个  node


class node:
    def __init__(self, name):
        self.next_node_dic = {}  # 装可以进行的输入,有了这个输入就可以转换到另一个节点,即每个输入对应一个   key为输入   node(node的下标 node_index)为结果
        self.this_node_product_dic = {}  # 当前节点含有的产生式,含点(.)作为记号
        self.name = str(name)
        global node_dic, product_list
        node_dic[name] = self
        self.node_index = name
        for key in product_list.keys():
            self.this_node_product_dic[key] = []

    # 添加下一个节点    每个key对应一个节点 状态
    def add_next_node(self, key, index_node):
        self.next_node_dic[key] = index_node

    # 添加this_node_product_list当前节点含的产生式
    def add_product(self, left, right):
        self.this_node_product_dic[left].append(right)

    # 返回节点的index,用于查找节点
    def get_node_index(self):
        return self.node_index

    # 返回节点的this_node_product_dic
    def get_this_node_product_dic(self):
        return self.this_node_product_dic

    # 返回节点的next_node_dic
    def get_next_node_dic(self):
        return self.next_node_dic

    # 打印节点信息
    def print_node(self):
        print('---------------------------')
        print("序号:", self.node_index)
        print("产生式:")
        for key in self.this_node_product_dic.keys():
            for item in self.this_node_product_dic[key]:
                print(key, " --> ", item)
        print('--------------------------')


# 构建LR0自动机
node_num = 1
have_next_node = []  # 存储已被分析过邻接节点的节点
new_node = []  # 存储没有被分析过邻接节点的节点


def automaton_LR0(product_dic, start_char):
    global node_num, have_next_node, new_node
    node_left = []  # 该节点的产生式左部   待展开的非终结符
    node_left.append(start_char)  # 刚开始左部只有start_char
    # 创建开始节点
    start_node = node(node_num)
    new_node.append(start_node)
    node_num += 1

    index = 0
    while True:
        if index == len(node_left):  # 展开完成   退出展开
            break
        for item in product_dic[node_left[index]]:  # 展开该非终结符的所有内容
            start_node.add_product(node_left[index], "." + item)  # 首位加点,作为  活前缀  记号
            if type_char(item[0]) == 'N':  # 若点下一个字符为 非终结符,则添加到待展开列表
                if item[0] not in node_left:  # 防止重复
                    node_left.append(item[0])
        index += 1  # 扫描下一个非终结符
    # print("分析序号:",start_node.node_index)
    analyse_next_node(start_node, product_dic)
    have_next_node.append(start_node)  # 添加到已被分析的节点列表
    new_node.remove(start_node)  # 从new_node列表删除元素

    while True:
        # print(len(new_node))
        if len(new_node) == 0:
            break
        # print("分析序号:", new_node[0].node_index)
        # print( new_node[0].get_this_node_product_dic())
        analyse_next_node(new_node[0], product_dic)
        have_next_node.append(new_node[0])  # 添加到已被分析的节点列表
        new_node.remove(new_node[0])  # 从new_node列表删除元素
    return have_next_node


# 根据 father_node 找出与他连接的下一个节点
def analyse_next_node(father_node, product_dic):
    global node_num, node_dic, have_next_node, new_node
    father_node_product = father_node.get_this_node_product_dic()  # 获取产生式

    node_str_dic = {}
    # 扫描产生式,以确定下一节点
    for key in father_node_product.keys():
        for item in father_node_product[key]:
            item = str(item)
            pos_point = item.find('.')
            if pos_point == len(item) - 1:  # 到达末尾,规约状态    没有下一个节点
                pass
            else:  # 未到达末尾  可以产生下一个状态,移动点 . 即可(交换与点 . 相邻的两个字符)
                str_temp = item[pos_point] + item[pos_point + 1]
                str_temp = item.replace(str_temp, str_temp[::-1])
                if str_temp[pos_point] not in node_str_dic.keys():
                    node_str_dic[str_temp[pos_point]] = []
                node_str_dic[str_temp[pos_point]].append([key, str_temp])

    # 开始分析father的邻接节点
    for key in node_str_dic.keys():
        node_temp = node(node_num)  # 每个key对应一个节点

        node_left = []  # 待展开的非终结符

        for item in node_str_dic[key]:
            node_temp.add_product(item[0], item[1])  # 添加初始产生式

            item = str(item)
            # 根据初始产生式,开始分析,是否展开点 . 相邻的非终结符
            pos_point = item.find('.')
            if len(item) - 1 != pos_point:  # 没有到达末尾
                if item[pos_point + 1] not in node_left and type_char(item[pos_point + 1]) == 'N':
                    node_left.append(item[pos_point + 1])  # 假如待展开的非终结符
        index_node_left = 0
        while True:  # 展开node_temp所有的点相邻的 非终结符(node_left里所有的)
            if index_node_left == len(node_left):  # 所有非终结符已经展开  则该节点 简化完成
                break
            for item in product_dic[node_left[index_node_left]]:  # 展开该非终结符的所有内容
                node_temp.add_product(node_left[index_node_left], "." + item)  # 首位加点,作为  活前缀  记号
                if type_char(item[0]) == 'N':  # 若点下一个字符为 非终结符,则添加到待展开列表
                    if item[0] not in node_left:  # 防止重复
                        node_left.append(item[0])
            index_node_left += 1  # 扫描下一个非终结符

        # 判断该节点是否已存在,若重复内容,表示已存在,否则表示不存在,可以创建
        node_temp_product = node_temp.get_this_node_product_dic()
        should_node_num_add = True
        for key_2 in node_dic.keys():
            if key_2 != node_num:
                # print(key,node_num)
                node_pro = node_dic[key_2].get_this_node_product_dic()  # 已有的产生式状态
                if node_pro != node_temp_product:
                    # print(node_pro,"与",node_temp_product,"不相同")
                    continue
                else:
                    # print(node_pro,"与",node_temp_product,"相同")
                    father_node.add_next_node(key, key_2)  # 添加入father节点的下一节点字典中   指向已有的产生式状态
                    should_node_num_add = False  # 重复,则跳出判断
                    break
        if should_node_num_add:
            node_num += 1  # node数量+1
            father_node.add_next_node(key, node_temp.get_node_index())  # 添加入father节点的下一节点字典中
            new_node.append(node_temp)  # 添加到new_node里

    # print("father为:")
    # father_node.print_node()
    # print("child为:")
    # childs = father_node.get_next_node_dic()
    # for key in childs.keys():
    #     print("输入 ",key,"得到child状态:")
    #     child = node_dic[childs[key]]
    #     child.print_node()


def tree_of_preview(window, node_index, frame_x, frame_y):
    left = " "
    right = "  "
    cols = [left, "序号" + node_index, right]
    # 表格窗体容器
    frame = Frame(window, bg=bg)
    frame.place(x=frame_x, y=frame_y)
    ybar = Scrollbar(frame, orient='vertical')  # 竖直滚动条
    # 表格
    style = ttk.Style(window)
    # set ttk theme to "clam" which support the fieldbackground option
    style.theme_use("clam")
    style.configure("Treeview", background=bg,
                    fieldbackground=bg, foreground="green")
    tree = Treeview(frame, show='headings', columns=cols, yscrollcommand=ybar.set, height=5,
                    selectmode="browse")

    ybar['command'] = tree.yview

    tree.grid(row=0, column=0)  # grid方案
    ybar.grid(row=0, column=1, sticky='ns')

    tree.heading(left, text=left)
    tree.heading("序号" + node_index, text="序号" + node_index)
    tree.heading(right, text=right)
    tree.column(left, width=25, anchor=S)  # 定义列
    tree.column("序号" + node_index, width=60, anchor=S)  # 定义列
    tree.column(right, width=100, anchor='w')  # 定义列
    return tree


def preview_node(all_node_list: dict):
    window = tk.Tk()
    window.title("预览节点")
    CenterWindow(window, 1280, 710, bg)
    x_int = 210  # 每个卡片的间隔,横向间隔
    y_int = 200  # 每个卡片的间隔,纵向间隔
    now_x_y = [0,0]
    tree_num = 0
    for node_temp in all_node_list:#dict为每个index对应一个node
        node_product_temp = node_temp.get_this_node_product_dic()#每个键值对应一个list

        tree_temp = tree_of_preview(window,str(node_temp.get_node_index()),now_x_y[0],now_x_y[1])
        now_x_y[0] += x_int
        tree_num += 1
        if tree_num == 6:
            now_x_y[0] = 0
            now_x_y[1] += y_int
            tree_num = 0
        for key_temp in node_product_temp.keys():
            data = ()
            for item in node_product_temp[key_temp]:#扫描每个item填入卡片

                data += (key_temp,"-->",item,)
                tree_temp.insert("", "end", values=data)
                data = ()

#判断文法是否符合LR0文法
def is_correct_LR0(product_dic:dict):
    all_item = []
    #判断产生式是否能产生空
    for key in product_dic.keys():
        for item in product_dic[key]:
            all_item.append(item)
            if '@' == item:
                tk.messagebox.showerror('提示', '产生式能产生空 @!!移进-规约冲突,不符合LR(0)文法!!')
                return False
    all_item.sort()
    #判断是否两个右部相同
    for index in range(len(all_item)-1):#
        if all_item[index] == all_item[index+1]:
            tk.messagebox.showerror('提示', '产生式两个右部相同产生:'+all_item[index]+',规约-规约冲突,不符合LR(0)文法!!!!')
            return False
    return True

# 得到LR0的action表和goro表
def LR0_table():
    global terminal_char, product_list, node_dic,have_next_node
    if not is_correct_LR0(product_list):
        return ""

    # 创建终结符集合
    create_terminal()

    cols_goto = ['行']  # 列数据
    cols_action = ['行', 'Action']  # 列数据
    window_width = 50
    for item in terminal_char:
        cols_goto.append(item)
        window_width += 40

    for key in product_list.keys():
        cols_goto.append(key)
        window_width += 40
    window_width += 250

    LR0_window = Tk()
    LR0_window.title("LR0表")
    CenterWindow(LR0_window, window_width, 600)

    def init_LR0_window(label_temp, btn, E):
        LR0_window.wm_attributes('-topmost', 0)
        start_c = E.get()

        #若开始的产生式没有结束符,则自动加上
        for index in range(len(product_list[start_c])):
            item_index = product_list[start_c][index]
            if '#' not in item_index:
                product_list[start_c][index] = product_list[start_c][index]+'#'


        if start_c < 'A' or start_c > 'Z':
            tk.messagebox.showerror('提示', '请输入大写字符!!')
            LR0_window.wm_attributes('-topmost', 1)
            return ""
        elif start_c not in product_list.keys():
            tk.messagebox.showerror('提示', '没有该产生式!!请检查!!')
            LR0_window.wm_attributes('-topmost', 1)
            return ""
        label_temp.destroy()
        btn.destroy()
        E.destroy()
        label = Label(LR0_window, text="LR(0) Goto表", bg=bg)
        label.place(x=100, y=0)
        label2 = Label(LR0_window, text="LR(0) Action表", bg=bg)
        label2.place(x=window_width - 150, y=0)
        close_btn = tk.Button(LR0_window, text="关闭", activebackground="#40E0D0", bg=bg,
                              command=lambda: close_window(LR0_window))
        close_btn.place(x=220, y=550, width=100, height=50)
        preView_btn = tk.Button(LR0_window, text="预览每个序号的节点内容", activebackground="#40E0D0", bg=bg,
                                command=lambda: preview_node(have_next_node))
        preView_btn.place(x=420, y=550, width=200, height=50)

        tree_goto_action = create_tree_goto_action(LR0_window, cols_goto, cols_action, window_width)  # 创建表格
        tree_goto = tree_goto_action[0]
        tree_action = tree_goto_action[1]
        for col in cols_goto:
            tree_goto.heading(col, text=col)
            tree_goto.column(col, width=40, anchor=S)  # 定义列

        tree_action.heading(cols_action[0], text=cols_action[0])
        tree_action.column(cols_action[0], width=40, anchor=S)  # 定义列
        tree_action.heading(cols_action[1], text=cols_action[1])
        tree_action.column(cols_action[1], width=150, anchor=S)  # 定义列

        all_node = automaton_LR0(product_list, start_c)
        # 获取分析数据
        for node_item in all_node:
            data_goto = ()
            data_action = ()
            line = node_item.get_node_index()
            data_goto += (line,)
            data_action += (line,)
            temp = tree_goto.insert("", "end", values=data_goto)
            for key in node_item.get_next_node_dic().keys():
                tree_goto.set(temp, column=key, value=node_item.get_next_node_dic()[key])

            if node_item.get_next_node_dic() != {}:  # 含下一个节点   要执行Shift操作
                data_action += ('Shift',)
                tree_action.insert("", "end", values=data_action)
                # print(node_item.get_next_node_dic())
            else:  # 不含下一个操作,操作分为Reduce  或  Accept
                is_Accept = False
                node_item_pro = node_item.get_this_node_product_dic()
                for key_pro in node_item_pro.keys():
                    if len(node_item_pro[key_pro]) == 0:
                        continue
                    if '#.' in node_item_pro[key_pro][0]:
                        is_Accept = True
                    str_reduce_or_accept = node_item_pro[key_pro][0].replace('.', '')
                    key_str = key_pro
                if is_Accept:
                    data_action += ('Accept :  ' + key_str + " --> " + str_reduce_or_accept,)
                    tree_action.insert("", "end", values=data_action)
                else:
                    data_action += ('Reduce :  ' + key_str + " --> " + str_reduce_or_accept,)
                    tree_action.insert("", "end", values=data_action)

    label = Label(LR0_window, text="请输入开始字符:", bg=bg)
    label.place(x=10, y=200)
    comvalue = tkinter.StringVar()  # 窗体自带的文本,新建一个值
    start_char = ttk.Combobox(LR0_window, textvariable=comvalue, width=10)  # 初始化下拉列表
    N_T_char = ()  # 记录全部非终结符
    for key in product_list.keys():
        N_T_char += (key,)
    start_char["values"] = N_T_char
    start_char.current(0)  # 选择第一个作为开始字符
    start_char.place(x=120, y=200)
    btn_sure = Button(LR0_window, text="确定", command=lambda: init_LR0_window(label, btn_sure, start_char), bg=bg,
                      activebackground="#40E0D0", width=4, height=2)
    btn_sure.place(x=250, y=190)

if __name__ == "__main__":
    init_window()

  • 7
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
由于题目中并未指定具体的文法,因此我们可以先介绍一下预测分析法的基本思路和算法,再给出一个具体的文法作为示例进行说明。 ## 预测分析法基本思路 预测分析法是一种自顶向下的语法分析方法,它的基本思路是:根据当前输入符号和栈顶符号的情况,预测接下来应该使用哪个产生式进行推导,然后将该产生式右侧的符号依次压入栈中。具体的算法流程如下: 1. 将开始符号 $S$ 压入栈中,并将输入串的第一个符号 $a_1$ 读入。 2. 从栈顶开始,取出栈顶符号 $X$。 3. 如果 $X$ 是终结符号,则比较 $X$ 和 $a_1$ 是否相同: * 如果相同,则将 $X$ 和 $a_1$ 都弹出,读入下一个输入符号 $a_2$。 * 如果不相同,则出现了错误,分析结束。 4. 如果 $X$ 是非终结符号,则查找预测分析中 $(X,a_1)$ 对应的产生式 $X \rightarrow \alpha$。 * 如果存在这样的产生式,则将 $\alpha$ 中的符号依次压入栈中,即将 $\alpha$ 从左到右依次压入栈中。 * 如果不存在这样的产生式,则出现了错误,分析结束。 5. 重复步骤 2~4,直到分析成功或出现错误。 预测分析法的关键在于构造预测分析,该的行示非终结符号,列示终结符号,格中的元素示对应的产生式。对于每一个 $(X,a)$,预测分析中的元素应该是 $X \rightarrow \alpha$,其中 $\alpha$ 是一个符号串。 ## 判断一个文法是否为LL(1)文法 在进行预测分析法之前,需要先判断给定的文法是否为LL(1)文法。LL(1)文法是指,对于文法中的任意一个非终结符号 $A$ 和文法中的任意一个终结符号 $a$,都只有一个产生式可以被用来推导出 $a$ 开头的字符串。具体的判断方法如下: 1. 将每个非终结符号 $A$ 和每个终结符号 $a$ 的 FOLLOW 集合求出来。 2. 对于任意一个非终结符号 $A$ 和任意一个终结符号 $a$,如果存在多个产生式 $A \rightarrow \alpha_1$ 和 $A \rightarrow \alpha_2$,满足: * $\alpha_1$ 和 $\alpha_2$ 的首符号集合不相交; * $a$ 属于 $\alpha_1$ 的首符号集合,并且 $a$ 也属于 $\alpha_2$ 的首符号集合; * 如果 $\alpha_1$ 或 $\alpha_2$ 后面还有符号,则它们的 FOLLOW 集合也要不相交。 3. 如果不存在这样的产生式,则该文法是 LL(1) 文法。 ## 一个文法的示例 为了方便起见,我们采用一个经典的文法作为示例进行说明: ``` S -> ( S ) | ε ``` 该文法示一个只包左右括号的简单语言,其中 S 是开始符号,ε 示空串。 ## 构造预测分析和预测分析程序 下面我们就来演示一下如何构造预测分析和预测分析程序。首先需要求出每个非终结符号和终结符号的 FIRST 集合和 FOLLOW 集合: ``` FIRST(S) = { (, ε } FIRST(ε) = { ε } FOLLOW(S) = { $, ) } ``` 接下来我们可以根据预测分析法的算法流程,逐步构造预测分析和预测分析程序。 首先是预测分析。对于该文法,预测分析应该是一个 $2\times 2$ 的矩阵,其中行示非终结符号,列示终结符号。根据文法中的产生式和 FIRST 集合,可以得到如下的预测分析: | | ( | ) | | -- | - | - | | S | S -> ( S ) | S -> ε | 接下来是预测分析程序。由于该文法比较简单,我们可以直接用 Python 代码示预测分析程序。具体的代码如下所示: ```python def predict_analysis(grammar, start_symbol, input_str): # 构造预测分析 predict_table = { ('S', '('): ('S', ['(', 'S', ')']), ('S', ')'): ('S', ['ε']), } # 初始化栈和输入串 stack = [start_symbol] input_list = list(input_str) + ['$'] # 开始分析 while len(stack) > 0: # 取出栈顶符号和输入符号 X = stack[-1] a = input_list[0] # 如果 X 是终结符号,则直接比较和弹出 if X in grammar['terminals']: if X == a: stack.pop() input_list.pop(0) else: print('Error: mismatch between X and a') return # 如果 X 是非终结符号,则查找预测分析 elif X in grammar['nonterminals']: if (X, a) in predict_table: production = predict_table[(X, a)] stack.pop() if production[1][0] != 'ε': stack += production[1][::-1] print(production[0] + ' -> ' + ' '.join(production[1])) else: print('Error: no production for X and a') return # 如果既不是终结符号也不是非终结符号,则出现了错误 else: print('Error: unknown symbol') return # 如果栈已经为空且输入串也为空,则分析成功 if len(stack) == 0 and len(input_list) == 0: print('Success') else: print('Error: unexpected end of input') ``` 上述代码中,我们首先构造了预测分析 `predict_table`,然后初始化栈和输入串,开始进行分析。在每一步分析过程中,我们都取出栈顶符号 `X` 和输入符号 `a`,并根据它们在预测分析中查找对应的产生式。如果找到了对应的产生式,则将产生式右侧的符号依次压入栈中。如果找不到对应的产生式,则出现了错误。 最后,我们可以用以下代码来测试该预测分析程序: ```python grammar = { 'start_symbol': 'S', 'terminals': ['(', ')', '$'], 'nonterminals': ['S'], 'productions': { 'S': [('(', 'S', ')'), ('ε',)], }, } predict_analysis(grammar, 'S', '()') ``` 该代码会输出如下结果: ``` S -> ( S ) S -> ε Success ``` 这说明,该文法是 LL(1) 文法,而且输入串 `()` 能够被成功分析

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeEggs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值