• 编译原理实验--实验三 预测分析法判断算术表达式的正确性--Python实现


    目录

    一、实验目的和要求

    二、实验内容

    三、实验环境

    四、实验步骤

    1、语法分析所依据的文法;

    2、给出消除左递归及提取左公因子的LL(1)文法;

    3、预测分析表

     4、关键代码

    五、实验结果与分析


    一、实验目的和要求

    1. 理解自顶向下语法分析方法;
    2. 用预测分析技术实现语法分析器;
    3. 熟练掌握预测分析程序的构造方法。

    二、实验内容

            算术表达式的文法是G[E]:

    E→E+T| E-T | T

    T→T*F| T/F | F

    F→(E)| i

            用预测分析法按文法G[E]对算术表达式(包括+、-、*、/、(、)的算术表达式)进行语法分析,判断该表达式是否正确。

    三、实验环境

    处理器     AMD Ryzen 7 5800H with Radeon Graphics3.20 GHz

    机带RAM   16.0 GB(13.9 GB可用)

    Win10家庭版20H2 X64  

    PyCharm 2012.2

    Python 3.10

    四、实验步骤

    1、阅读课本有关章节,将上述算术表达式的文法改造成LL(1)文法G’[E],即消除左递归和提取左公因子;

    2、设计出文法G’[E]的预测分析表;

    3、按算法4.5(P132)编写预测分析程序。(思考:预测分析程序包括四种动作——推导、匹配、接受、出错,详见P131,这些动作如何实现?)

    1、语法分析所依据的文法;

          算术表达式的文法是G[E]:

    E→E+T| E-T | T

    T→T*F| T/F | F

    F→(E)| i

    2、给出消除左递归及提取左公因子的LL(1)文法;

    将文法G[E]改造为LL(1)文法如下:

    G’[E]:

    E →  TE’

    E’ → +TE’| -TE’|ε

    T  →  FT’

    T’→  *FT’|/FT’|ε

    F  → (E)| i

    3、预测分析表

     

     4、关键代码

    <简要说明程序中所用到的主要变量、数组的作用,主要函数或过程的功能;给出主要功能实现的关键代码,如推导或匹配动作的实现,并加上适当的注释>

    1. #!/usr/bin/env python
    2. # -*- coding: utf-8 -*-
    3. # @Time : 2021/12/11 9:58
    4. # @Author : LanXiaoFang
    5. # @Site :
    6. # @File : yufafenxi_LL1.py
    7. # @Software: PyCharm
    8. import cifafenxi_LL1
    9. class Stack(): # 定义类
    10. def __init__(self): # 产生一个空的容器
    11. self.__list = []
    12. def push(self, item): # 入栈
    13. self.__list.append(item)
    14. def pop(self): # 出栈
    15. return self.__list.pop()
    16. def speek(self): # 返回栈顶元素
    17. return self.__list[-1]
    18. def is_empty(self): # 判断是否已为空
    19. return not self.__list
    20. def size(self): # 返回栈中元素个数
    21. return len(self.__list)
    22. # 用来存储从txt文件中读取的字符串
    23. aa = " "
    24. # 用来标记字符
    25. pp = 0
    26. # 非终结符表
    27. VN = ["E", "e", "T", "t", "F"]
    28. # 终结符表 l->/ m->% i->id n->num
    29. VT = ['*', 'l', 'm', '+', '-', '(', ')', 'i', 'num', '#']
    30. Fa = ["Te", "+Te", "-Te", "", "Ft", "*Ft", "nFt", "mFt", "", "(E)", "i", "num"]
    31. F = ["E->", "E'->", "E'->", "E'->", "T->", "T'->", "T'->", "T'->", "T'->", "F->", "F->", "F->"]
    32. # 构造预测分析表,-1表示出错,-2表示该行终结符的follow集合,用于错误处理
    33. analysis_table = [
    34. [-2, -2, -2, -2, -2, 0, -1, 0, 0, -1],
    35. [-2, -2, -2, 1, 2, -2, 3, -2, -2, 3],
    36. [-2, -2, -2, -1, -1, 4, -1, 4, 4, -1],
    37. [5, 6, 7, 8, 8, -2, 8, -2, -2, 8],
    38. [-2, -2, -1, -1, -1, 9, -1, 10, 11, -2]
    39. ]
    40. # 定义栈
    41. stack = Stack()
    42. # 判断字符x是否是终结符
    43. def includevt(x):
    44. if x in VT:
    45. return 1
    46. else:
    47. return 0
    48. # 查找非终结符,终结符 在预测分析表中的坐标,返回坐标对应信息
    49. def includean(x, a):
    50. # 非终结符
    51. for i in range(len(VN)):
    52. if VN[i] == x:
    53. break
    54. # 终结符
    55. for j in range(len(VT)):
    56. if VT[j] == a:
    57. break
    58. return analysis_table[i][j]
    59. # 错误处理
    60. def destory():
    61. flag = 0
    62. flagg = 0
    63. # 将 "#"和文法开始符依次压入栈中
    64. stack.push('#')
    65. # 将第一个非终结符入栈
    66. stack.push(VN[0])
    67. # 把第一个输入符号读入a, aa
    68. global pp
    69. a = aa[pp]
    70. x = ""
    71. while x != '#':
    72. # print(stack.__dict__.values())
    73. if flag == 0:
    74. # 把栈顶符号弹出并放入
    75. x = stack.pop()
    76. flag = 0
    77. # 如果a是终结符
    78. if includevt(a) == 1:
    79. if includevt(x) == 1:
    80. if a == x:
    81. if a == '#':
    82. flagg = 1
    83. print(x, "\t\t", a, "\t\t", " 结束")
    84. return 1
    85. else:
    86. print(x, "\t\t", a, "\t\t", " 匹配终结符", x)
    87. pp = pp + 1
    88. # 将下一输入符号读入a
    89. a = aa[pp]
    90. else:
    91. flag = 1
    92. print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
    93. pp = pp + 1
    94. a = aa[pp]
    95. elif includean(x, a) >= 0:
    96. h = includean(x, a)
    97. print(x, "\t\t", a, "\t\t", " 展开非终结符 ", F[h], Fa[h])
    98. if Fa[h] in aa:
    99. stack.push(Fa[h])
    100. else:
    101. aF = Fa[h][::-1]
    102. for i in range(len(aF)):
    103. stack.push(aF[i])
    104. elif includean(x, a) == -1:
    105. flag = 1
    106. print(x, "\t\t", a, "\t\t", " 出错 ,从栈顶弹出", x)
    107. x = stack.pop()
    108. else:
    109. flag = 1
    110. print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
    111. pp = pp + 1
    112. a = aa[pp]
    113. else:
    114. flag = 1
    115. print(x, "\t\t", a, "\t\t", " 出错 ,跳过", a)
    116. pp = pp + 1
    117. a = aa[pp]
    118. if flagg == 0:
    119. print(x, "\t\t", a, "\t\t", " 结束 \n")
    120. aa = cifafenxi_LL1.main()
    121. print("语法分析工程如下 :")
    122. print('-' * 20, '文法如下', '-' * 20)
    123. print("E->TE'")
    124. print("E'->+TE'|-TE'|~")
    125. print("T->FT'")
    126. print("T'->*FT'|/FT'|%FT'|~")
    127. print("F->(E)|id|num")
    128. print("要分析的语句是 :", aa)
    129. print("语法分析工程如下 :")
    130. print("栈顶元素 \t\t 当前单词记号 \t\t 动作 ")
    131. print("-" * 50)
    132. destory()

    1. #!/usr/bin/env python
    2. # -*- coding: utf-8 -*-
    3. # @Time : 2021/12/11 9:57
    4. # @Author : LanXiaoFang
    5. # @Site :
    6. # @File : cifafenxi_LL1.py
    7. # @Software: PyCharm
    8. import re
    9. class Token(object):
    10. # 初始化
    11. def __init__(this):
    12. # 存储分词的列表
    13. this.results = []
    14. # 行号
    15. this.lineno = 1
    16. # 关键字
    17. this.keywords = ['auto', 'struct', 'if', 'else', 'for', 'do', 'while', 'const',
    18. 'int', 'double', 'float', 'long', 'char', 'short', 'unsigned',
    19. 'switch', 'break', 'defalut', 'continue', 'return', 'void', 'static',
    20. 'auto', 'enum', 'register', 'typeof', 'volatile', 'union', 'extern']
    21. '''
    22. regex中:*表示从0-, +表示1-, ?表示0-1。对应的需要转义
    23. { 表示限定符表达式开始的地方 \{
    24. () 标记一个子表达式的开始和结束位置。子表达式可以获取共以后使用:\( \)
    25. r表示原生字符串。
    26. '''
    27. Keyword = r'(?P(auto){1}|(double){1}|(int){1}|(if){1}|' \
    28. r'(#include){1}|(return){1}|(char){1}|(stdio\.h){1}|(const){1})'
    29. # 运算符
    30. Operator = r'(?P\+\+|\+=|\+|--|-=|-|\*|#|\*=|/=|/|%=|%)'
    31. # 分隔符/界符
    32. Separator = r'(?P[,:\{}:)(<>])'
    33. # 数字: 例如:1 1.9
    34. Number = r'(?P\d+[.]?\d+)'
    35. # 变量名 不能使用关键字命名
    36. ID = r'(?P[a-zA-Z_][a-zA-Z_0-9]*)'
    37. # 方法名 {1} 重复n次
    38. Method = r'(?P(main){1}|(printf){1})'
    39. # 错误 \S 匹配任意不是空白符的字符
    40. # Error = r'(?P.*\S+)'
    41. Error = r'\"(?P.*)\"'
    42. # 注释 ^匹配行的开始 .匹配换行符以外的任意字符 \r回车符 \n换行符
    43. Annotation = r'(?P/\*(.|[\r\n])*/|//[^\n]*)'
    44. # 进行组装,将上述正则表达式以逻辑的方式进行拼接, 按照一定的逻辑顺序
    45. # compile函数用于编译正则表达式,生成一个正则表达式对象
    46. this.patterns = re.compile('|'.join([Annotation, Keyword, Method, ID, Number, Separator, Operator, Error]))
    47. # 读文件
    48. def read_file(this, filename):
    49. with open(filename, "r", encoding="UTF-8") as f_input:
    50. return [line.strip() for line in f_input]
    51. # 结果写入文件
    52. def write_file(this, lines, filename='results.txt'):
    53. with open(filename, "a") as f_output:
    54. for line in lines:
    55. if line:
    56. f_output.write(line)
    57. else:
    58. continue
    59. def get_token(this, line):
    60. # finditer : 在字符串中找到正则表达式所匹配的所有字串, 并把他们作为一个迭代器返回
    61. for match in re.finditer(this.patterns, line):
    62. # group():匹配的整个表达式的字符 # yield 关键字:类似return ,返回的是一个生成器,generator
    63. yield (match.lastgroup, match.group())
    64. def run(this, line, flag=True):
    65. tokens = []
    66. for token in this.get_token(line):
    67. if flag:
    68. tokens.append(token)
    69. print("line %3d :" % this.lineno, token)
    70. '''
    71. else:
    72. yield "line %3d :" % this.lineno + str(token) + "\n"
    73. '''
    74. return tokens
    75. def printrun(this, line, flag=True):
    76. for token in this.get_token(line):
    77. if flag:
    78. print("lines x: ", token)
    79. def main():
    80. token = Token()
    81. filepath = input("请输入文件路径:")
    82. print('-' * 20, '词法分析', '-' * 20)
    83. lines = token.read_file(filepath)
    84. tokenss = []
    85. for line in lines:
    86. tokens = token.run(line, True)
    87. for i in tokens:
    88. tokenss.append(i[1])
    89. return tokenss

    五、实验结果与分析

  • 相关阅读:
    开源博客项目Blog .NET Core源码学习(4:生成验证码)
    【JavaSE】如何简化Java的异常处理?try-with-resource语句的使用
    kubernetes--数据存储
    ARM体系架构
    C#:实现区域生长算法(附完整源码)
    TensorFlow 用 hashtable 的意义
    虚拟DOM,diff
    【从零开始学习Redis | 第一篇】快速了解Redis
    SSH免密失败并报错:no mutual signature algorithm
    全志G2D实现屏幕旋转,开机logo实现手动旋转。
  • 原文地址:https://blog.csdn.net/c_lanxiaofang/article/details/127997602