编译原理2.3习题 语法制导分析[C++]

news2024/11/15 18:21:19

图源:文心一言

编译原理习题整理~🥝🥝

作为初学者的我,这些习题主要用于自我巩固。由于是自学,答案难免有误,非常欢迎各位小伙伴指正与讨论!👏💡

  • 第1版:自己的解题,与AI老师的判卷~🧩🧩

编辑:梅头脑🌸 

审核:文心一言

题源:龙书《编译原理》 Alfre V.Aho 等著  赵建华 等译


目录

🧵参考答案

🧵练习2.3.1

🧵练习2.3.2

🧵练习2.3.3

🧵练习2.3.4

🔚结语


🧵参考答案

🌸fool2fish (Zhouxuan Yang) 博文:dragon-book-exercise-answers - GitCode

🧵练习2.3.1

🧩题目

构建一个语法制导翻译方案,该方案把算术表达式从中缀表示方式翻译成运算符在运算分量之前的前缀表达方式。例如:-xy是表达式 x-y 的前缀表示法。给出输入 9-5+2 和 9-5*2 的注释分析树。

📇答题

  • 产生式
expr → expr { ("+" | "-") term }  
term → factor { ("*" | "/") factor }  
factor → "(" expr ")" | digit  
digit → "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  • 语义规则
expr → {print ('+' | '-')} expr { ("+" | "-") term }  
term → {print ('*' | '/')} factor { ("*" | "/") factor }  
factor →  "(" expr ")" | {print ('digit')} digit  
digit → "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
  • 注释分析树(这个小问,好像找不到参考答案)

📇AI审核

1:我曾问过AI,它习惯将第一行写为expr → term { ("+" | "-") term },这实际与教材中书写的expr → expr { ("+" | "-") term }不同,为什么不写为后者呢?

  • 第一行产生式 expr → expr { ("+" | "-") term } ,。这种表示方法采用了左递归的形式,意味着一个表达式可以是一个更复杂的表达式后跟一个由加法或减法运算符和另一个项组成的序列。
  • 然而,左递归形式对于某些解析技术(如自顶向下的解析器,如递归下降解析器)来说可能会导致问题,因为它们可能无法有效地处理这种直接或间接的无限递归情况。为了避免这些问题,通常会使用等价的右递归形式来重写产生式,如下所示:
expr → term { ("+" | "-") term }
  • 在这个右递归版本中,expr 以一个 term 开始,后面可以跟零个或多个由加法或减法运算符和另一个 term 组成的序列。这种形式更适合于自顶向下的解析算法,因为它保证了解析过程在有限的步骤内会终止。
  • 在您提供的原始问题中,没有特别指明必须使用左递归还是右递归形式,因此两者在描述算术表达式的语法结构时都是有效的。但是,从实现解析器的角度来看,选择右递归形式通常是更可取的做法,因为它与更广泛使用的解析算法兼容
  • 因此,即使第一行写成 expr → expr { ("+" | "-") term } 在语法上是正确的,但考虑到解析器的实现,通常更倾向于使用右递归形式 expr → term { ("+" | "-") term }

📇知识扩展

  • 树是什么鬼,先序遍历又是什么鬼:🌸数据结构05:树与二叉树[C++]-CSDN博客
  • 中缀转后缀的C++代码:🌸数据结构03:栈、队列和数组-CSDN博客

🧵练习2.3.2

🧩题目

构建一个语法制导翻译方案,该方案把算术表达式从后缀表达式翻译成中缀表达式。给出输入 95-2* 和 95*2- 的注释分析树。

📇争议

  • 本题解题过程可能有误,可能的争议点如下,欢迎小伙伴指导与讨论~
  • 问题1:
    • 按照我看书的理解,表达式可能是这样的:
      • 产生式:expr → expr + term,这个用于描述中缀表达式;
      • 语义规则:expr = expr.t || term.t || '+',这个用于描述后缀表达式;
      • 语义动作:expr → expr + term {print('+')},这个用于将中缀表达式翻译为后缀,其中的{print('+')}除了输出‘+’号以外没有意义;
    • 那么后缀表达式:
      • 产生式可能是:expr → expr term + ,这个用于描述后缀表达式,这也是参考答案的写法;
      • 但是AI不同意,强烈不建议这样写,在AI的理解这个语句“expr → expr term +”不能被解析器正确解析,其余理由我贴到了答案后面;
      • 因此,目前还是按照中缀表达式书写的答案;虽然AI也不建议这样写,他认为没有什么作用,在真正的编译器设计中,print操作通常不会出现在文法产生式中,一个更实际的解决方案可能是编写一个解析器...
  • 问题2:
    • 除此以外:我的解法语法可能有问题,没有区分优先等级,且会非常随意地加很多括号给表达式;话说,这如果是算法题,答成这样一半分就没有了...
    • 答题后贴的参考答案避免了这个问题(答案链接已贴于文首),价值相对高一些~~

📇思路

  • 后缀表达式没有括号,按从左到右的顺序运算,运算符的优先级别是相等的(如果我没有记错的话),而转换为的中缀表达式可能有括号。例如执行"4 5 6 + *":
    • 读到 4,压入栈。
    • 读到 5,压入栈。
    • 读到 6,压入栈。此时栈中有三个操作数:4、5 和 6。
    • 读到 +,弹出 5 和 6,形成中缀子表达式 5 + 6,并将其作为一个整体结果(可能带有括号)暂存或输出。此时栈中应该只剩下 4
    • 读到 *,弹出 4 和刚才得到的中缀子表达式 5 + 6。形成最终的中缀表达式 4 * (5 + 6)

📇答题

  • 产生式
expr → expr { operator term }  
term → digit
digit → "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"  
operator → "+" | "-" | "*" | "/"
  • 语义规则
expr → {print('(')} expr { operator{print('operator')} term } {print(')')}
term → digit | print('digit');
digit → "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"  
operator → "+" | "-" | "*" | "/"

📇AI审核

1:我曾问过AI,它将后缀表达式的第一行写为expr → term { operator term },而非更符合后缀表达式形式的expr → term { term operator}呢?

  • 在后缀表达式(也称为逆波兰表示法)中,操作数(项,即term)位于操作符之前。因此,当我们将后缀表达式转换为其他形式(如中缀表达式)时,我们需要确保操作数的顺序正确。例如,在后缀表达式“3 4 +”中,加法操作符“+”位于操作数“3”和“4”之后。因此,在转换为中缀表达式时,我们需要将操作数重新排序为“3 + 4”,这正是“expr → term { operator term }”产生式所反映的结构。
  • 在文法定义中,“expr → term { operator term }”这样的产生式意味着表达式(expr)由一个项(term)开始,后面可能跟着一个或多个由操作符(operator)和另一个项(term)组成的序列。在后缀表达式中,操作符总是位于它操作的两个操作数之后。
  • 如果我们写成“expr → term { term operator }”,这实际上是不正确的,因为它暗示了操作符后面紧跟着的是另一个项,然后才是之前的项,这与后缀表达式的实际结构相反。简单来说,这个产生式错误地指示了解析器先去寻找一个项(term),然后去寻找另一个项,最后才去寻找操作符(operator)。这实际上与后缀表达式的结构不符,因为在后缀表达式中,操作符应该紧跟在它的操作数之后。。
  • 简而言之,正确的顺序“term operator term”反映了在后缀表达式中操作符位于两个操作数之后的事实,而错误的顺序“term term operator”则不符合这种结构。

📇参考答案

productions:

expr -> expr expr +
      | expr expr -
      | expr expr *
      | expr expr /
      | digit

 translation schemes:

expr -> expr {print("+")} expr +
      | expr {print("-")} expr -
      | {print("(")} expr {print(")*(")} expr {print(")")} *
      | {print("(")} expr {print(")/(")} expr {print(")")} /
      | digit {print(digit)}

 Another reference answer

E -> {print("(")} E {print(op)} E {print(")"}} op | digit {print(digit)}

🧵练习2.3.3

🧩题目

构建一个将整数翻译成罗马数字的语法制导方案。

📇思路

罗马数字的组合规则可以总结如下:

  1. 基本符号:罗马数字系统使用七个不同的符号来表示数值:I (1)、V (5)、X (10)、L (50)、C (100)、D (500) 和 M (1000)。

  2. 数值表示

    • IXC 和 M 可以被重复最多三次来表示它们的倍数(例如:II = 2, XXX = 30, CC = 200, 但 IIII 不是标准的表示法,应为 IV)。
    • VL 和 D 不可以被重复。
  3. 减法原则:当一个小数值的符号出现在一个较大数值的符号左边时,表示应从大数中减去小数(例如:IV = 4, IX = 9, XL = 40, XC = 90, CD = 400, CM = 900)。

  4. 加法原则:当一个小数值的符号出现在一个较大数值的符号右边时,或者相同数值的符号连续出现时,表示应将它们相加(例如:VI = 6, XI = 11, XX = 20, CCC = 300)。

  5. 避免重复四次:罗马数字中不会出现连续四个相同的符号(如 IIII 或 XXXX)。应使用减法原则来表示这些数值(如 IV 代替 IIIIXL 代替 XXXX)。

  6. 从左到右:罗马数字应从左到右按照上述规则进行解读。

  7. 千位以上的表示:千位以上的数字可以通过重复 M 来表示(例如:MM = 2000, MMM = 3000)。

  8. 组合顺序:在表示一个数时,应从最高位开始,逐步降低到最低位。例如,数字 1994 应表示为 MCMXCIV(1000 + (1000 - 100) + (100 - 10) + (5 - 1))。

  9. 零的表示:罗马数字系统中没有专门的符号来表示零。零值是通过省略相应的符号来表示的。

基于以上规则,一个更准确且简化的罗马数字文法可能是这样的;请注意,这个文法仍然不是完美的,因为它允许像 "IIII" 这样的非标准表示:

roman → thousand* hundred ten unit  
thousand → "M" {"M"}  
hundred → "CM" | "CD" | "C" {"C" | "CC" | "CCC"} | "D" {"C" | "CC" | "CCC"} | "M" {"CM" | "CD" | "C" {"C" | "CC" | "CCC"} | "D" {"C" | "CC" | "CCC"}}  
ten → "XC" | "XL" | "X" {"X" | "XX" | "XXX"} | "L" {"X" | "XX" | "XXX"} | "IX" | "IV" | "V" {"I" | "II" | "III"} | "I" {"X" | "V"}  
unit → "I" {"I" | "II" | "III"} | "IV" | "V" {"I" | "II" | "III"} | "VI" | "VII" | "VIII" | "IX"

📇答题

经过前3道题与AI无尽的争吵(具体来说,我对于语法学不明白,而AI觉得语法制导翻译方案不实用);因此,我们决定彻底摆烂,以下用C++代码解题(?):

#include <string> 
#include <iostream>
using namespace std;

std::string intToRoman(int num) {
    std::string roman = "";
    std::string thousands[] = { "", "M", "MM", "MMM" };
    std::string hundreds[] = { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
    std::string tens[] = { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
    std::string ones[] = { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };

    // 千位  
    roman += thousands[num / 1000];
    num %= 1000;

    // 百位  
    roman += hundreds[num / 100];
    num %= 100;

    // 十位  
    roman += tens[num / 10];
    num %= 10;

    // 个位  
    roman += ones[num];

    return roman;
}

int main() {
    int number = 0;
    cout << "请输入整数(0-3999):\t" ;
    cin >> number;
    string romanNumeral = intToRoman(number);
    cout << romanNumeral << std::endl;
    return 0;
}

🧵练习2.3.4

🧩题目

构建一个将罗马数字翻译成整数的语法制导方案。

📇答题

#include <iostream>  
#include <string>  
#include <unordered_map>  

int romanToInt(std::string s) {
    std::unordered_map<char, int> romanValues = {
        {'I', 1},
        {'V', 5},
        {'X', 10},
        {'L', 50},
        {'C', 100},
        {'D', 500},
        {'M', 1000}
    };

    int result = 0;
    // 从左向右遍历字符串,取出第i个字符
    for (size_t i = 0; i < s.length(); ++i) {
        int value = romanValues[s[i]];
        // 若第i+1个字符存在,且第i个字符<第i+1个字符,则结果减去第i个字符的值(第3条,减法原则),反之,则增加第i个字符的值(第4条,加法原则)
        if (i + 1 < s.length() && value < romanValues[s[i + 1]]) {
            result -= value;
        }
        else {
            result += value;
        }
    }

    return result;
}

int main() {
    std::string romanNumeral = "MMMDXLIX";
    int number = romanToInt(romanNumeral);
    std::cout << number << std::endl; // 输出: 3549  
    return 0;
}

🔚结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶‍🌫️😶‍🌫️

我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟

同系列的博文:🌸编译原理_梅头脑_的博客-CSDN博客

同博主的博文:🌸随笔03 笔记整理-CSDN博客

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

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

相关文章

帝国cms无限级分销的逻辑思路效果展示以及表结构的初步规划

#小李子9479# #帝国cms无限级分销# #帝国cms三级分销系统# 关于分销系统 &#xff0c;我们要解决以下几个重要的逻辑关系&#xff0c; 1&#xff0c;用户上下级关系&#xff0c;即A通过分享期邀请链接&#xff0c;B点击或扫码注册后&#xff0c;成为A的下线。 2。下级级别的…

从开发、部署到维护:SAAS与源代码小程序的全流程对比

在数字化时代&#xff0c;小程序已成为企业开展业务的重要工具。然而&#xff0c;小程序开发过程中存在多种形式&#xff0c;其中SAAS版本小程序和源代码小程序是最常见的两种。乔拓云SaaS系统作为业界领先的SaaS服务平台&#xff0c;为企业提供高效、便捷的小程序解决方案。与…

ctfshow反序列化(web254-web266)

目录 web254 web255 web256 web257 web258 web259 web260 web261 web262 web263 web264 web265 web266 web254 源码 <?php/* # -*- coding: utf-8 -*- # Author: h1xa # Date: 2020-12-02 17:44:47 # Last Modified by: h1xa # Last Modified time: 2020…

Vulnhub靶机:FunBox 5

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;FunBox 5&#xff08;10.0.2.30&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhub.com/entry/funb…

Window安装Python和开发Pycharm

准备&#xff1a; 1&#xff1a;安装Python环境 https://www.python.org/downloads/windows/ 2: 下载Pycharm https://www.jetbrains.com/pycharm/download/other.html

Java基础进阶01-类加载器,反射

目录 一、类加载器 1.概述 2.过程 &#xff08;1&#xff09;类加载时机 &#xff08;2&#xff09;类加载过程 加载 验证 准备 解析 初始化 3.分类 &#xff08;1&#xff09;启动类加载器&#xff08;Bootstrap ClassLoader&#xff09;&#xff1a;虚拟机内置的类…

数据结构笔记1

来自《Python数据结构学习笔记》&#xff08;张清云 编著&#xff09; 第一章 数据结构基础 1.逻辑结构 集合&#xff1a;结构中的数据元素除了同属于一种类型外&#xff0c;别无其他关系线性结构&#xff1a;数据元素之间一对一的关系树形结构&#xff1a;数据元素之间一对…

Django入门,十分钟学会登录网页

我们假定你已经阅读了 安装 Django。你能知道 Django 已被安装&#xff0c;且安装的是哪个版本&#xff0c;通过在命令提示行输入命令 cmd黑窗口运行&#xff0c;不懂cmd百度一下 python -m django --version 如果没出现版本&#xff0c;就是没安装&#xff0c;那么用pip安装…

搭建Android开发环境—— 熟悉Android开发工具,掌握Android移动端开发环境的搭建、项目导入,并能够将项目部署到模拟器和真机进行测试。

搭建Android开发环境 一、实验目的 熟悉Android开发工具&#xff0c;掌握Android移动端开发环境的搭建、项目导入&#xff0c;并能够将项目部署到模拟器和真机进行测试。 二、实验设备及器件 1、JDK1.8安装包 2、Android Studio安装包 三、实验内容 完成JDK和Android Stud…

Tomcat 优化

1、隐藏版本信息 隐藏 HTTP 头部的版本信息 # 为 Connector 添加 server 属性 vim /usr/local/tomcat/conf/server.xml <Connector port"8080" protocol"HTTP/1.1" connectionTimeout"20000" redirectPort"8443" server"w…

应用机器学习的建议 (Advice for Applying Machine Learning)

1.决定下一步做什么 问题&#xff1a; 假如&#xff0c;在你得到你的学习参数以后&#xff0c;如果你要将你的假设函数放到一组 新的房屋样本上进行测试&#xff0c;假如说你发现在预测房价时产生了巨大的误差&#xff0c;现在你的问题是要想改进这个算法&#xff0c;接下来应…

C#,入门教程(31)——预处理指令的基础知识与使用方法

上一篇&#xff1a; C#&#xff0c;入门教程(30)——扎好程序的笼子&#xff0c;错误处理 try catchhttps://blog.csdn.net/beijinghorn/article/details/124182386 Visual Studio、C#编译器以及C#语法所支持的预处理指令&#xff0c;绝对是天才设计。 编译程序的时候会发现&am…

【技术】SpringBoot 接口怎么加密解密

1. 介绍 在我们日常的Java开发中&#xff0c;免不了和其他系统的业务交互&#xff0c;或者微服务之间的接口调用 如果我们想保证数据传输的安全&#xff0c;对接口出参加密&#xff0c;入参解密。 但是不想写重复代码&#xff0c;我们可以提供一个通用starter&#xff0c;提…

代码随想录算法训练营第14天 | 二叉树的前序、中序、后序遍历(递归+迭代法)

二叉树的理论基础&#xff1a;&#xff08;二叉树的种类&#xff0c;存储方式&#xff0c;遍历方式 以及二叉树的定义&#xff09; https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 二叉树的递归遍历 Leetcode对应的三道习…

这种环境下腾讯64亿在北京拿地?

近期&#xff0c;金融市场出现较大波动&#xff0c;A股指数跌至2700点&#xff0c;同时恒生指数也下滑至15000点&#xff0c;引发了社会各界的关注和思考。与此同时&#xff0c;腾讯以64.2亿元拿下北京海淀区地块&#xff0c;马云和蔡崇信又增持阿里股票&#xff0c;这一系列的…

自学网安-DNS

01DNS Domain Name Service域名服务 作用&#xff1a;为客户机提供域名解析服务器 02域名组成 2.1域名组成概述 如"www.sina.com.cn"是一个域名&#xff0c;从严格意义上讲&#xff0c;"sina.com.cn"才被称为域名(全球唯一)&#xff0c;而"www"…

【若依】前后端分离框架部署

1.拉取若依项目代码 进入若依 基于SpringBootVue前后端分离的Java快速开发框架&#xff0c;并通过相应协议拉取到本地IDE 2.前端部署 2.1.下载nodejs 进入nodejs官网下载所需版本&#xff0c; 完成后在命令行运行npm -v可查询版本 2.2.配置依赖 在若依ui目录下运行 np…

Linux第33步_TF-A移植的第1步_创建新的设备树

TF-A移植第1步就是创建新的设备树&#xff0c;并命名为“stm32mp157d-atk”。 和“TF-A移植”有关的知识点&#xff1a; 1)设备树英文名字叫做Device tree&#xff0c;用来描述板子硬件信息的&#xff0c;比如开发板上的 CPU有几个核 、每个CPU核主频是多少&#xff0c;IIC、…

web安全学习笔记【10】——数据包分析

基础[1] [2] [3] [4] 入门-HTTP数据包&Postman构造&请求方法&请求头修改&状态码判断[5] [6] [7] #知识点&#xff1a; 1、Web常规-系统&中间件&数据库&源码等 2、Web其他-前后端&软件&Docker&分配站等 3、Web拓展-CDN&WAF&OS…

[go语言]数据类型

目录 知识结构 整型、浮点型 1.整型 2.浮点型 复数、布尔类型 1.复数 2.布尔类型 字符与字符串 1.字符串的格式化 2.字符串的截取 3.格式化好的字符串赋值给量 4.字符串的转换 5.strings包 知识结构 整型、浮点型 1.整型 在Go语言中&#xff0c;整型数据是一种基…