首选语言 :C / C ++,Java和Ruby。

我正在寻找一些有用的书籍/教程,以了解如何仅出于教育目的编写自己的编译器。 我最熟悉C / C ++,Java和Ruby,因此我更喜欢涉及这三种资源之一的资源,但是任何好的资源都是可以接受的。

===============>>#1 票数:1063 已采纳

资源大清单:

传说:

  • ¶链接到PDF文件
  • $链接到印刷书籍

===============>>#2 票数:70

我认为这是一个非常模糊的问题。 只是因为涉及的话题很深。 但是,编译器可以分解为两个独立的部分。 上半部分和下半部分。 上半部通常采用源语言并将其转换为中间表示,下半部负责平台特定的代码生成。

尽管如此,一种用于实现该主题的简便方法的想法(至少在我们的编译器类中使用了该想法)是在上述两部分中构建编译器。 具体来说,仅通过构建上半部分就可以对整个过程有所了解。

仅做上半部分就可以使您获得编写词法分析器和解析器的经验,并生成一些“代码”(我提到的中间表示形式)。 因此,它将采用您的源程序并将其转换为另一种表示形式,并进行一些优化(如果需要),这是编译器的核心。 然后,下半部分将采用该中间表示形式,并生成在特定体系结构上运行程序所需的字节。 例如,下半部分将采用您的中间表示形式并生成PE可执行文件。

关于该主题的一些书对我特别有用,是《 编译器原理和技巧》 (或《龙书》,这是由于封面上有可爱的龙)。 它有一些很棒的理论,并且确实以一种真正可访问的方式涵盖了上下文无关文法。 同样,为了构建词法分析器和解析器,您可能会使用* nix工具lex和yacc。 不足为奇的是,这本名为《 lex and yacc 》的书在这本《龙书》停下来的地方找到了。

===============>>#3 票数:55

我认为ML中的现代编译器实现是编写文本的最佳入门编译器。 还有一个Java版本和一个C版本 ,鉴于您的语言背景,这两个版本可能更易于访问。 本书包含许多有用的基本资料(扫描和解析,语义分析,激活记录,指令选择,RISC和x86本机代码生成)和各种“高级”主题(编译OO和功能语言,多态性,垃圾回收,优化和静态分配表格)放入相对较小的空间(约500页)。

我更喜欢《现代编译器实现》而不是《 Dragon》一书,因为《现代编译器》实现只研究了很少的领域,相反,它确实涵盖了编写严肃,体面的编译器所需的所有主题。 阅读完本书后,如果需要,您将可以直接处理研究论文,以进行更深入的了解。

我必须承认,我对Niklaus Wirth的Compiler Construction情有独钟 它以PDF格式在线提供 我发现Wirth的编程美学简直很漂亮,但是有些人觉得他的风格太小了(例如Wirth偏爱递归下降解析器,但是大多数CS课程都专注于解析器生成器工具; Wirth的语言设计相当保守。)编译器构造是非常简洁的提炼关于Wirth的基本思想,因此无论您是否喜欢他的风格,我都强烈建议您阅读本书。

===============>>#4 票数:44

我同意《龙书》的参考; IMO,它是编译器构造的权威指南。 不过,请为一些核心理论做好准备。

如果您想要一本理论上较浅的书,那么Game Scripting Mastery可能是一本更好的书。 如果您是编译器理论的新手,那么它会为您提供较温和的介绍。 它没有涵盖更实用的解析方法(在不讨论LL或LR解析的情况下选择非预测性递归下降),而且正如我记得的那样,它甚至没有讨论任何优化理论。 另外,它可以编译为应该在还要编写的VM上运行的字节码,而不是编译为机器代码。

它仍然是不错的读物,特别是如果您可以在Amazon上以便宜的价格买到它。 如果只希望对编译器进行简单介绍,那么掌握Game Scripting并不是一个坏方法。 如果您想成为硬派,那么您应该比《龙书》更满意。

===============>>#5 票数:27

“让我们构建一个编译器”很棒,但是有点过时了。 (我并不是说这会使它的有效性降低一点。)

或查看SLANG 这类似于“让我们构建一个编译器”,但是对于初学者而言,它是更好的资源。 随附pdf教程,该教程采用7步方法来教您编译器。 添加quora链接,因为它具有到C ++,Java和JS中SLANG的所有各个端口的链接,还具有最初使用C#和.NET平台编写的python和Java解释器。

===============>>#6 票数:23

如果您要使用功能强大的高级工具,而不是自己构建所有内容 ,那么遍历项目和阅读本课程是一个不错的选择。 这是Java解析器引擎ANTLR的作者编写的语言课程。 您可以从Pragmatic Programmers以PDF的形式获得本课程的书。

该课程介绍了您在其他地方看到的标准编译器编译器内容:解析,类型和类型检查,多态性,符号表和代码生成。 几乎没有的唯一内容是优化。 最终项目是一个编译C子集的程序。 因为您使用的是ANTLR和LLVM之类的工具,所以在一天之内就可以编写整个编译器是可行的(尽管我确实有大约24小时的时间,但我已经证明了这一点。 使用现代工具进行实际工程的工作量很大,理论上也较轻。

顺便说一句,LLVM简直太棒了。 在许多情况下,通常您可以编译成汇编,因此最好改编译为LLVM的中间表示 它是更高级别的,跨平台的,并且LLVM非常擅长从中生成优化的装配。

===============>>#7 票数:20

如果您没有时间,我会推荐Niklaus Wirth的“编译器构造”(Addison-Wesley。1996) ,这是一本可以在一天内阅读的小小册子,但是它解释了基础知识(包括如何实现词法分析器,递归下降解析器,和您自己的基于堆栈的虚拟机)。 此后,如果您想深入研究,就没有其他评论者建议的围绕《龙》的书了。

===============>>#8 票数:17

您可能想要研究Lex / Yacc(或Flex / Bison,无论您想称呼它们如何)。 Flex是一个词法分析器,它将分析和识别您语言的语义成分(“令牌”),而Bison将用于定义解析每个令牌时发生的情况。 对于可以编译为C的编译器或动态运行指令,这可能是但绝对不限于打印C代码。

该常见问题解答应为您提供帮助, 本教程看起来非常有用。

===============>>#9 票数:17

一般而言,对于编译器而言,没有五分钟的教程,因为这是一个复杂的主题,编写编译器可能需要几个月的时间。 您将必须进行自己的搜索。

通常会解释Python和Ruby。 也许您也想从翻译开始。 通常比较容易。

第一步是编写正式的语言描述,即您的编程语言的语法。 然后,您必须根据语法将要编译或解释的源代码转换为抽象语法树,这是计算机可以理解并可以对其进行操作的源代码的内部形式。 此步骤通常称为解析,而解析源代码的软件称为解析器。 解析器通常由解析器生成器生成,解析器生成器将形式语法转换为源代码或其他机器代码。 有关解析的一个很好的非数学解释,我建议您使用“解析技术-实用指南”。 Wikipedia对解析器生成器进行了比较,您可以从中选择最适合自己的解析器。 根据您选择的解析器生成器,您可以在Internet上找到教程,对于真正流行的解析器生成器(如GNU bison)也有书籍。

为您的语言编写解析器可能非常困难,但这取决于您的语法。 因此,我建议您保持语法简单(与C ++不同); LISP就是一个很好的例子。

在第二步中,将抽象语法树从树结构转换为线性中间表示。 经常引用Lua的字节码作为一个很好的例子。 但是中间表示确实取决于您的语言。

如果要构建解释器,则只需解释中间表示。 您也可以及时编译它。 我建议使用LLVM和libjit进行即时编译。 为了使该语言可用,您还必须包括一些输入和输出功能以及一个小的标准库。

如果要编译语言,它将更加复杂。 您将不得不为不同的计算机体系结构编写后端,并从这些后端的中间表示形式生成机器代码。 我建议为此任务使用LLVM。

关于此主题的书籍很少,但我不推荐它们作为一般用途。 他们大多数都太学术或太实用。 没有“ 21天之内教自己的编译器编写”,因此,您将必须购买几本书才能对整个主题有一个很好的理解。 如果您搜索Internet,则会发现一些在线书籍和讲义。 也许您附近有一个大学图书馆,您可以在其中借用有关编译器的书籍。

如果您要使您的项目认真,我还建议您具有理论计算机科学和图论方面的良好背景知识。 计算机科学学位也将有所帮助。

===============>>#10 票数:14

看看下面的书。 作者是ANTLR的创建者。

语言实现模式:创建您自己的领域特定和通用编程语言

替代文字

===============>>#11 票数:11

一本尚未提出但很重要的书是约翰·莱文(John Levine)的“链接器和加载器” 如果您不使用外部汇编器,则需要一种输出可以链接到最终程序中的目标文件的方法。 即使您使用的是外部汇编程序,您也可能需要了解重定位以及整个程序加载过程如何工作以制作可用的工具。 本书收集了关于此过程的大量随机知识,涉及各种系统,包括Win32和Linux。

===============>>#12 票数:10

我记得大约七年前,我刚接触编程时曾问过这个问题。

当我问的时候,我非常小心,令人惊讶的是,我得到的批评没有您来这里的那么多。 但是,他们的确向我指出了“ 龙书 ”的方向,这是一本非常出色的书,它解释了编写编译器所需的一切(当然,您必须精通一两种语言。)语言,更好的语言。)

是的,很多人都说读那本书太疯狂了,您从中学不到任何东西,但是我完全不同意。

许多人还说编写编译器是愚蠢且毫无意义的。 好吧,编译器开发有用的原因有很多:

  • 因为很有趣。
  • 这是有教育意义的,在学习如何编写编译器时,您将学到很多有关计算机科学和其他技术的知识,这些技术在编写其他应用程序时很有用。
  • 如果没有人编写编译器,那么现有的语言将再好不过。

我没有立即编写自己的编译器,但是在询问之后我知道从哪里开始。 而现在,在学习了许多不同的语言并阅读了《龙书》之后,写作并不是什么大问题。 (我也在学习计算机工程atm,但是我对编程的大部分了解都是自学的。)

总之,《龙书》是一个很棒的“教程”。 但是在尝试编写编译器之前,请花一些时间精通一两种语言。 不过,不要期望在未来十年左右的时间里成为编译器专家。

如果您想学习如何编写解析器/解释器,这本书也很好。

===============>>#13 票数:10

如果您愿意使用LLVM,请查看以下网址: http : //llvm.org/docs/tutorial/ 它教您如何使用LLVM的框架从头开始编写编译器,并且不假定您具有有关该主题的任何知识。

本教程建议您编写自己的解析器和词法分析器等,但是我建议您一旦了解了这一点,就研究一下bison和flex。 它们使生活变得更加轻松。

===============>>#14 票数:10

Dragon Book无疑是一本“构建编译器”的书,但是如果您的语言不像当前的语言那么复杂,那么您可能想看看Design Patterns的Interpreter 模式

本书中的示例设计了一种类似于正则表达式的语言,并且经过了深思熟虑,但是正如他们在书中所说的那样,这对于思考过程是有好处的,但实际上仅对小语言有效。 但是,使用这种模式为小语言编写解释器要比必须学习所有不同类型的解析器(yacc和lex等)要快得多。

===============>>#15 票数:10

我发现《龙》一书太难读了,过多地侧重于语言理论,而在实践中实际上并不需要编写语言。

我要添加一本Oberon书,其中包含了一个惊人的快速和简单的Oberon编译器Project Oberon的完整资料

替代文字

===============>>#16 票数:9

“ ...让我们构建一个编译器...”

我第二次通过@sasb http://compilers.iecc.com/crenshaw/ 暂时不要购买更多的书。

为什么? 工具和语言。

所需的语言是Pascal,如果我没记错的话,则基于Turbo-Pascal。 碰巧,如果你去http://www.freepascal.org/和下载Pascal编译器的所有实例从页面直接工作〜 http://www.freepascal.org/download.var的BEAUT件事免费Pascal是几乎可以使用的任何处理器或操作系统,都可以使用它。

一旦您掌握了这些课程,然后尝试更高级的龙书 〜http://en.wikipedia.org/wiki/Dragon_book

===============>>#17 票数:9

我正在研究相同的概念,并且发现了Joel Pobar的这篇很有前途的文章,

为.NET Framework创建语言编译器-不确定它的去向

为.NET Framework创建语言编译器-原始文档的pdf副本

他讨论了编译器的高级概念,并为.Net框架发明了自己的语言。 尽管它的目标是.Net框架,但许多概念仍应能够复制。 该文章涵盖:

  1. 语言定义
  2. 扫描器
  3. 解析器(主要是我感兴趣的位)
  4. 面向.Net框架
  5. 代码生成器

还有其他主题,但是您公正。

它的目标读者是用C#编写的(不是Java)

高温超导

骨头

===============>>#18 票数:8

您应该在6页以上的代码中签出Darius Bacon的“ ichbins ”,这是一个针对Lisp小方言的编译器,目标语言为C。 与大多数玩具编译器相比,它的优点是语言足够完整,可以用它编写编译器。 (压缩包还包括一个引导程序来引导事物。)

关于在我的Ur-Scheme网页上学习编写编译器的有用知识,还有很多东西。

===============>>#19 票数:8

创建编译器的一种简单方法是使用bison和flex(或类似方法),构建树(AST)并在C语言中生成代码。生成C代码是最重要的步骤。 通过生成C代码,您的语言将自动在具有C编译器的所有平台上运行。

生成C代码与生成HTML一样容易(只需使用打印或等效的HTML),比编写C解析器或HTML解析器容易得多。

===============>>#20 票数:8

来自comp.compilers常见问题解答

Per Brinch Hansen Prentice-Hall 1982年的“对个人计算机进行编程” ISBN 0-13-730283-5

这本书的标题很不幸,它使用类似于Pascal的语言称为Edison的说明,说明了如何为微型计算机设计和创建单用户编程环境。 作者提供了Edison编译器和简单支持的操作系统的逐步实现的所有源代码和说明,所有这些均由Edison本身编写(除了用PDP 11/23的符号汇编程序编写的小型支持内核);还可以为IBM PC订购完整的源代码)。

本书最有趣的事情是:1)能够演示如何创建完整,自包含,自维护,有用的编译器和操作系统的能力,以及2)关于语言设计和规范问题以及交易的有趣讨论。第二章。

Per Brinch Hansen Prentice-Hall 1985年的“ Brinch Hansen在Pascal编译器上”,ISBN 0-13-083098-4

另一本理论上的轻量级实用文章是这本书的编码方法。 作者介绍了Pascal-(Pascal“减”)的编译器和p代码解释器的设计,实现和完整源代码,Pascal是布尔型和整数类型(但没有字符,实数,子范围或枚举类型)的Pascal子集。 ,常量和变量定义以及数组和记录类型(但不包括打包,变体,集合,指针,无名,重命名或文件类型),表达式,赋值语句,带有值和变量参数的嵌套过程定义,if语句,while语句,和开始到结束块(但没有函数定义,过程参数,goto语句和标签,case语句,repeat语句,for语句和with语句)。

编译器和解释器以Pascal *(Pascal“星号”)编写,Pascal是Pascal子集,扩展了一些Edison风格的功能,用于创建软件开发系统。 作者出售了用于IBM PC的Pascal *编译器,但是很容易将本书的Pascal-编译器移植到任何方便的Pascal平台上。

本书使编译器的设计和实现看起来容易。 我特别喜欢作者关注质量,可靠性和测试的方式。 编译器和解释器可以很容易地用作更复杂的语言或编译器项目的基础,尤其是当您被迫快速启动并运行某些东西时。

===============>>#21 票数:7

  1. 这是一个广阔的主题。 不要小看这一点。 并且不要低估我的观点,不要低估它。
  2. 我听说《 龙书》是一个(“?”)学习的起点。 :)善于搜索,最终将成为您的生活。
  3. 构建自己的编程语言绝对是一个好练习! 但是要知道,最终它永远不会用于任何实际目的。 例外情况很少, 而且相差远。

===============>>#22 票数:7

Fraser和Hanson的LCC编译器( Wikipedia )( 项目主页 )在其书“可重定位的C编译器:设计和实现”中进行了介绍。 它非常易读,可以解释整个编译器,直至代码生成。

===============>>#23 票数:7

抱歉,它是西班牙文,但这是阿根廷一门名为“ Compiladores eIntérpretes”(编译器和口译员)的课程的目录。

该课程从形式语言理论到编译器构建,这些都是您至少需要构建简单编译器的主题:

  • C语言中的编译器设计
    艾伦·霍鲁布

    普伦蒂斯厅。 1990年。

  • 编译器。 TeoríayConstrucción。
    SanchísLlorca,FJ,GalánPascual,C。社论Paraninfo。 1988年。

  • 编译器构造。
    尼克劳斯·沃思

    艾迪生-韦斯利。 1996年。

  • Lenguajes,Gramáticas和Autómatas。 Un enquequepráctico。
    Pedro IsasiViñuela,帕洛玛·马丁内斯·费尔南德斯,丹尼尔·博拉霍·米兰。 Addison-Wesley Iberoamericana(西班牙)。 1997年。

  • 编译器设计的艺术。 理论与实践。
    托马斯·皮特曼,詹姆斯·彼得斯。

    普伦蒂斯厅。 1992年。

  • 面向对象的编译器构造。
    吉姆·福尔摩斯。
    1995年,新泽西州恩格尔伍德悬崖,普伦蒂斯·霍尔(Prentice Hall)

  • 编译器。 Conceptos基础。
    B. Teufel,S。Schmidt,T。Teufel。

    艾迪生-韦斯利Iberoamericana。 1995年。

  • 自动机理论,语言和计算简介。

    约翰·霍普克罗夫特。 Jeffref D. Ullman。
    艾迪生-韦斯利。 1979年。

  • 正式语言简介。
    GyörgyE.Révész。

    麦格劳希尔。 1983年。

  • 解析技术。 实用指南。
    迪克·格鲁尼(Dick Grune),塞里尔·雅各布(Ceriel Jacobs)。
    自动播放。 1995年
    http://www.cs.vu.nl/~dick/PTAPG.html

  • Yacc:另一个编译器。
    斯蒂芬·约翰逊
    1975年《计算机科学技术报告》第32期。贝尔实验室。 新州默里山
    泽西岛。

  • Lex:词法分析器生成器。
    我莱斯克,E。施密特。 1975年第39号计算机科学技术报告。贝尔实验室。 新泽西州默里山。

  • lex&yacc。
    约翰·莱文,托尼·梅森,道格·布朗。
    O'Reilly&Associates。 1995年。

  • 计算理论的要素。
    哈里·刘易斯(Harry R. Lewis),克里斯托斯(Christos H.) SegundaEdición。 学徒大厅。 1998年。

  • 控制权的基本构成法》。
    萨尔瓦多·卡瓦迪尼(Salvador V.Cavadini)。
    Trabajo Final de Grado para obtener elTítulode Ingeniero enComputación。
    Facultad deMatemáticaAplicada。 UCSE 2001。

===============>>#24 票数:7

Python捆绑了用Python编写的python编译器。 您可以看到源代码,它包括解析,抽象语法树,发出代码等所有阶段。

===============>>#25 票数:6

如果您想了解更多有关编译器(和元编译器)的信息,则不是一本书,而是一本技术论文和一个非常有趣的学习经验...该网站指导您构建一个完全独立的编译器系统,该系统可以编译自身和其他语言:

教程:元编译器第1部分

这全部基于一篇令人惊叹的10页技术论文:

META II: A Syntax-Oriented Compiler Writing Language META II:一种面向语法的编译器编写语言

从诚实到1964年。我从1970年开始学习如何构建编译器。当您终于想知道编译器如何重新生成自身时,会有一个令人叹为观止的时刻。

我从大学时代就认识该网站的作者,但与该网站无关。

===============>>#26 票数:5

如果您有兴趣为功能语言(而不是程序语言)编写编译器,Simon Peyton-Jones和David Lester的“ 实现功能语言:教程 ”是一个很好的指南。

功能评估工作原理的概念基础是以简单但功能强大的称为“核心”的功能语言中的示例为指导的。 此外,Miranda(一种与Haskell非常相似的纯功能语言)中的代码示例解释了Core语言编译器的每个部分。

描述了几种不同类型的编译器,但是即使您仅遵循所谓的Core模板编译器,您也将对使函数式编程产生影响的内容有很好的了解。

===============>>#27 票数:5

我也喜欢Crenshaw教程 ,因为它非常清楚地表明编译器只是另一个程序,它读取一些输入并写入一些输出。

阅读。

如果需要的话,请尝试使用它,然后再看看另一篇有关如何真正编写更大,更完整的编译器的参考。

并阅读On Trusting Trust ,以获取有关可以在此域中完成的显而易见的事情的线索。

===============>>#28 票数:5

您可以使用Apache Software Foundation的BCEL 使用此工具可以生成类似汇编程序的代码,但是它是带有BCEL API的Java。 您可以学习如何生成中间语言代码(在这种情况下为字节码)。

简单的例子

  1. 使用此函数创建一个Java类:

     public String maxAsString(int a, int b) { if (a > b) { return Integer.valueOf(a).toString(); } else if (a < b) { return Integer.valueOf(b).toString(); } else { return "equals"; } } 

现在使用此类运行BCELifier

BCELifier bcelifier = new BCELifier("MyClass", System.out);
bcelifier.start();

您可以在控制台上看到整个类的结果(如何构建字节码MyClass.java)。 该函数的代码是这样的:

private void createMethod_1() {
  InstructionList il = new InstructionList();
  MethodGen method = new MethodGen(ACC_PUBLIC, Type.STRING, new Type[] { Type.INT, Type.INT }, new String[] { "arg0", "arg1" }, "maxAsString", "MyClass", il, _cp);

  il.append(InstructionFactory.createLoad(Type.INT, 1)); // Load first parameter to address 1
  il.append(InstructionFactory.createLoad(Type.INT, 2)); // Load second parameter to adress 2
    BranchInstruction if_icmple_2 = InstructionFactory.createBranchInstruction(Constants.IF_ICMPLE, null); // Do if condition (compare a > b)
  il.append(if_icmple_2);
  il.append(InstructionFactory.createLoad(Type.INT, 1)); // Load value from address 1 into the stack
  il.append(_factory.createInvoke("java.lang.Integer", "valueOf", new ObjectType("java.lang.Integer"), new Type[] { Type.INT }, Constants.INVOKESTATIC));
  il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
  il.append(InstructionFactory.createReturn(Type.OBJECT));
  InstructionHandle ih_13 = il.append(InstructionFactory.createLoad(Type.INT, 1));
  il.append(InstructionFactory.createLoad(Type.INT, 2));
    BranchInstruction if_icmpge_15 = InstructionFactory.createBranchInstruction(Constants.IF_ICMPGE, null); // Do if condition (compare a < b)
  il.append(if_icmpge_15);
  il.append(InstructionFactory.createLoad(Type.INT, 2));
  il.append(_factory.createInvoke("java.lang.Integer", "valueOf", new ObjectType("java.lang.Integer"), new Type[] { Type.INT }, Constants.INVOKESTATIC));
  il.append(_factory.createInvoke("java.lang.Integer", "toString", Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
  il.append(InstructionFactory.createReturn(Type.OBJECT));
  InstructionHandle ih_26 = il.append(new PUSH(_cp, "equals")); // Return "equals" string
  il.append(InstructionFactory.createReturn(Type.OBJECT));
  if_icmple_2.setTarget(ih_13);
  if_icmpge_15.setTarget(ih_26);
  method.setMaxStack();
  method.setMaxLocals();
  _cg.addMethod(method.getMethod());
  il.dispose();
}

===============>>#29 票数:5

这里有很多好的答案,所以我想我会再添加一个到列表中:

十多年前,我有一本书叫Project Oberon,该书的编译器文字非常好。 本书的真正意义在于其源代码和说明非常动手且易于阅读。 全文(2005年版)已提供pdf版本,因此您可以立即下载。 第12章讨论了编译器:

http://www-old.oberon.ethz.ch/WirthPubl/ProjectOberon.pdf

尼古拉斯·沃斯(Jirg Gutknecht)

(这种处理方法没有他关于编译器的书那样广泛)

我已经读过几本有关编译器的书,并且我可以第二本龙书,花在这本书上的时间非常值得。

===============>>#30 票数:4

到目前为止,这本书未包含在清单中:

编译器设计基础(Torben Mogensen) (来自哥本哈根大学计算机科学系)

我也对学习编译器感兴趣,并计划在未来几年内进入该行业。 就我所知,这本书是开始学习编译器的理想理论书籍。 它是免费复制和复制,干净且精心编写的,并以纯正的英语形式(没有任何代码)提供给您,但仍通过说明和图表等方式展示其原理。值得一看。

  ask by community wiki translate from so

未解决问题?本站智能推荐: