微小类C编译器设计与实现-2016-5-21-16-49_第1页
微小类C编译器设计与实现-2016-5-21-16-49_第2页
微小类C编译器设计与实现-2016-5-21-16-49_第3页
微小类C编译器设计与实现-2016-5-21-16-49_第4页
微小类C编译器设计与实现-2016-5-21-16-49_第5页
已阅读5页,还剩56页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、大连理工大学本科毕业设计(论文)类C微小编译器的设计与实现Design and Implementation of C-like micro compiler学 院(系): 专 业: 学 生 姓 名: 学 号: 指 导 教 师: 评 阅 教 师: 完 成 日 期: 大连理工大学Dalian University of Technology类C微小编译器的设计与实现摘 要随着计算机的广泛应用,计算机编程语言从早期的机器语言到汇编语言进行不断地演进,直到现在的各种形式和理论基础不同的高级语言的形态。编译器技术是计算机技术发展的基石,同时也是进展速度最快的计算机科学,这个分支已经进行了几十年的研究,

2、形成了非常成熟的体系,编译器的设计集中体现了计算机的理论本质和工程发展的成果。编译器的核心思想是在机器语言和算法的逻辑结构之间,转换一种基础语法到另一种代表语言的过程。最终以高级别语言语法表现出来,甚至在高级语言上的虚拟平台上运行的机器语言,并且以硬件的机器指令实现。本系统采用Go作为编程语言。介绍了开发的相关细节,完成的功能,以及实现的过程。着重解释了一些编写编译器前后端分离设计的关键点和字节码虚拟机的技术要点和理论。关键词:编译技术;编程语言;虚拟机 - I -类C微小编译器的设计与实现Design and Implementation of C-like micro compilerAb

3、stract With the wide use of computer, computer programming language continuously evolves from the early machine code to assembly language, as well as to the high-level language with various forms and theoretical basis.Compiler technology is the cornerstone of the development of computer technology,

4、and is also the fastest progress of computer science, this branch has been carried out for decades of research, and has formed a very mature system. Compiler design embodies the results of computer science theory and engineering development.The core idea behind the compiler is the process of convert

5、ing a basic grammar to another language, which is based on the logical structure of machine code and algorithm. Finally it is presented as a form of high-level programming language, even running a platform supported by virtual machine byte code, which is finally implemented by machine code. The syst

6、em uses Go as a programming language. This article describes the development of the relevant details, the completion of the function, as well as the implementation of the process. The key points of the design as well as the theory of the virtual machine are emphatically explained.Key Words:compiler

7、technology;programming language;virtual machine- IV -目 录摘 要IAbstractII1文献综述11.1 开发的背景11.2 开发的目标和意义11.3 编译原理的发展情况22理论基础32.1 编译器介绍32.1.1 编译器系统概述32.1.2 编译器的产生历史42.2 编译器的结构42.2.1 编译器的分遍52.2.2 编译器前端62.2.3 编译器后端72.2.4 编译器错误处理73 编译器的实现83.1 语言标准的定义8运算符8类型8常量8变量及初始化8类型转换11流程控制11函数13多赋值14defer15类15构造函数15inclu

8、de语法16模块及 import16include 和 import 的区别17反射17样例代码183.2 词法分析203.2.1 TPL 实现原理213.3 语法分析293.4 编写VM实现后端44结 论53参 考 文 献54致 谢55类C微小编译器的设计与实现1 文献综述1.1 开发的背景在计算机技术与科学的迅猛发展下,计算技术应用在了非常广泛的领域当中,相当多的计算机应用层出不穷,极大地丰富了我们的生活,学习和工作。与此同时,也有许多用于编写高级应用的编程语言作为支撑,才得以构建非常复杂的系统和架构。程序设计是一门艺术,设计者通过特定语言的编译器将源代码翻译为体系结构相关的目标程序,从而

9、能够最终达到程序执行的目的,一个好的程序语言要满足工程的需要也要搭配良好的语法设计。从 20 世纪 60 年代开始,编译器的设计就是计算机科学技术发展和研发中的一个热门主题。虽然编译器设计的历史很长,作为计算机技术来说相对成熟,编译器还是一个由高级抽象的源代码向计算机机器指令转换的高效映射工具,随着电脑软硬件水平的不断提高,编译器的设计也在不断地变化,目标机体系结构也在不断地改进,软件越来越复杂,其规模也越来越大。编译器设计问题在高级层次上大致稳定,例如面向对象语言,面向过程语言,函数语言都在各自的领域发挥着重要的作用,并且每个领域的研究都有着非常积极的意义,不同领域相互学习,相互弥补,并且结

10、合工业界的积累,不断自我完善,从一个抽象的计算机科学问题,逐渐变成了一个计算机工业界不断追求和寻找优化的目标,大公司对于编程语言的要求非常高,因为一个良好的编程语言可以很大程度地提高程序员的工作效率,良好的程序组织结构也能保证项目的可重用性,这样实际生产中的工程能够保证高效和稳定。当我们深入其内部研究编译器时会不断明白,编译器的内部结构同样也在顺应着需求一直在变化。此外,由于我们能够提供给编译器本身的计算资源,例如内存等,也在不断增加。所以,现代编译器可以采用比以前更加复杂的算法,却又不会损失过多的时间。而且,编译器前后端分离,能够提供一种更加合理的方式,把编程过程解耦,比如在编辑时就主动触发

11、前端语法分析的过程,检查语法错误,而不需要等到最后编译的时候进行,或者通过解析条件自动生成代码,减少开发的工作量,很多编译“前端”技术,如文法、正则表达式、语法分析器以及语法制导翻译器等,仍然被广泛使用。1.2 开发的目标和意义GCC的复杂程度基本上可以和Linux操作系统比肩,代码量,工程量非常之大,是世界各地的优秀工程师合力通过版本控制系统贡献开发的,单凭一个人很难做到这样规模的编译器,所以编写甚至读懂这样的一个GCC的编译器是一件非常困难的事情。很多的计算机专业的同学从来没有编写过一个完整的编译器,但是,几乎任何程序都和编译器有关,而且任何一个软件工程师都应该知晓编译技术的基本结构和原理

12、。编译器设计的原理和技术还可以应用于编译器设计之外的众多领域。因此,这些原理和技术会在工程实践中被反复使用。研究编译器的实现和设计与程序语言、体系结构、算法和软件工程有着密切的关系。编译器的设计从本质上来说是一项工程,它所使用的方法必须很好地解决现实中出现的各种解释逻辑(即用真实的语言编译且在真实的机器上能够执行的程序)。在一般情况下,开发编译器的人必须面对机器结构,很少能够去改善设计。在开发过程中做什么样的分析和转换,以及什么时候去做,这些都是工程上的选择,但正是这些选择决定了一个编译器的性能高低。本实验就建立在一个自主开发的微型编译器基础之上的,该编译器虽然功能类似于C语言这样的经典编译器

13、,但是缺少必要的标准库和完整的实现,不过,也已经完全具备了一个编译器应有的所有特征。虽然本实验只是一个规模很小的微型编译器的开发,作为一次较为完整的编译开发实践,它已经足够透彻地了解一个编译器开发过程,又能更深刻地理解和运用编译开发过程中的众多技术和方法,并能在此基础上针对编译器的优化展开深入的讨论,这些对于自己以后的研究和发展方向将起到非常大的推动作用。1.3 编译原理的发展情况在编译器原理的进化历程里,提高编译的效率始终是编译器专家挖掘的领域之一,编译效率指的是根据编译器产生的目标代码的时间指标和空间指标的代价,所以编译优化要围绕时间和空间这两个方面来实施。在编译时针对过程进行优化的技术有

14、很多,它们通常是通过搜集源代码或中间代码的特定信息,然后利用这些元素对代码中的源代码组织结构或算法模型等实施等价的改进变换,从而致力于在时间效率和空间效率上达到一个比较理想的效果。编译器的设计者们一直想要能够将各种代码优化技术充分地运用在自己的语言设计中,但往往并不是尽如人意。本编译器开发过程中,虽然没有运用到非常优化的代码,但通过本实验的进行,在现有开发的编译器基础之上,能够在后续的开发中逐渐地提高程序的编译效率,对于自己以后的研究和发展方向将起到充分的促进效果。这正是本实验的研究意义所在。本实验是以微型编译器的项目开发为基础,该项目的开发目标是自定义一种高级语言,然后编码实现出语言的编译器

15、,完成将语言源程序翻译为基于虚拟机的目标代码的任务,这是这个实验的应用目标。编程语言的开发具有极高的工程价值和应用意义,高级语言编译器的性能决定了基于该语言应用所能表现的质量。所以国际上很多的科研和技术人员也在积极地开展这方面的技术探索和项目实践。大多是以特定的工程项目为前提来进行一些与编程语言设计相关或类似的项目分析,研究目标大多是基于某种实验型高级语言的编译器开发和优化改进,然后把有价值的研究成果移植或运用到工业级级的编译器开发中,最近十年以来,国外关于编译器设计的发展动态主要体现在:编译器采用了更加复杂的算法,主要用于推演或压缩程序中的元信息,这又与更为复杂的程序设计语言的发展结合在一起

16、,其中典型的有用于函数语言编译的 Hindley-Milner 类型检查的统一算法1。在九十年代,作为GNU项目或其它开放源代码以GCC为基础的子项目,许多开源的编译器或构造工具发布了。这些工具可用来编译程序语言的源程序。它们中的一些项目被认为是非常优秀的,而且对现代编译理论感兴趣的人都可以轻松地得到它们的免费源代码。比如Clang设计时比GCC编译过程中保留更多的信息,保留原始代码的整体形式。这样做的目的是使其更容易将映射误差引入原始源2。通过Clang提供的错误报告的目的还在于要更加详细和具体的错误提示,所以可以在IDE编译参数的输出中体现。编译器的模块化设计可以提供源代码索引,语法检查,

17、以及其他功能通常与快速应用开发系统相关。解析树也更适合于自动化配套代码重构,因为它直接代表着原始的源代码。2 理论基础2.1 编译器介绍2.1.1 编译器系统概述编译器是一个计算机程序(或一组程序),从编写的编程语言(源语言)到另一个计算机语言(目标语言)的源代码的转化程序,而后者往往具有称为对象代码二进制形式。“编译”主要用于从一个高级语言到较低水平的语言翻译源代码的程序(例如,汇编语言或机器代码)2。如果编译程序可以在不同CPU或操作系统上编译运行,则编译器被称为跨平台的编译。编译器从某种程度上说是一种翻译器。虽然这个过程这需要一套编程规范,并翻译它们,即所有程序创建的目标来执行这些中准则

18、,在技术上“编译”意味着,从编译器生成一个独立的可执行程序(即可能需要运行时间库或子系统来支持的程序),如果仅执行原规格编译器通常被称作“解释器”,因为不同的分析对于编译和解释之间是有区别的,在这两个词之间有一些重叠的方法,但是编译更倾向于生成二进制文件而解析则仅仅是对源代码进行翻译并且进行动态执行。从低水平的语言向更高一级翻译的程序是反汇编。高层次的语言之间转换的程序通常被称为一个从源代码到源代码的编译器。语言重写通常是翻译表达的形式。编译器编译有时用来指一个解释器的生成器,经常被用来帮助创建词法和语法分析器的工具。编译器很可能要进行以下操作:词法分析,预处理,解析,语义分析(语法制导翻译)

19、,代码生成和代码优化。2.1.2 编译器的产生历史早期计算机的软件主要是用汇编语言编写。虽然第一个高级语言产生时用着几乎一样古老的电脑,导致了在设计编译器时最大的技术挑战是早期的计算机有限的内存容量。第一个高级语言(Plankalkül)于1943年提出,是康拉德楚泽发明,在1952年,则为A-0编程语言;在A-0运作更象是一个装载机或连接器,不太接近现代的编译器。第一个编译器被阿利克Glennie于1952年实现了,当时在曼彻斯特大学的一台电脑上,被一些人认为是第一个编译的编程语言。IBM由约翰巴科斯领导的FORTRAN团队一般被作为在1957年诞生的COBOL的第一个完整的编译器

20、的作者,该编译器能够在多个架构上进行编译,在1960年在许多应用领域使用更高级别语言的想法很快就流行起来。因为新的编程语言支持的扩展功能和计算机体系结构的日益复杂,编译器变得更加复杂。早期的编译器是用汇编语言编写。第一个自举的编译器 - 能够编译自己的源代码的语言 - 是在1962年由蒂姆哈特和麦克莱文在麻省理工学院,用Lisp创建的。自1970年以来这已成为实现编译器一个常见的做法3。虽然Pascal和C这两者一直是实现语言最流行的选择。构建自举的编译器是一个非常重要的问题,第一个这样的编译器的语言必须通过用其他语言实现最初版本的编译器。2.2 编译器的结构高级语言编译器是源程序与底层硬件交

21、互的桥梁。编译器验证代码语法,生成高效的目标代码,运行时的组织,并格式化根据汇编器和链接约定的输出。编译器包括:前端:验证语法和语义,以及由中间端产生用于处理的源代码的中间表示。执行信息收集,对类型进行检查,产生错误和警告。前端的方面包括词法分析,语法分析和语义分析。中间端:进行优化,包括去除无用的或无法访问的代码,发现常量传播,计算迁移不太频繁执行的地方(例如,跳出了一条循环)。后端:生成汇编代码,在执行过程中进行寄存器分配。梳理如何并行执行以及整理执行单元,优化硬件的目标代码的应用(对于程序变量在可能的情况下分配处理器寄存器)。2.2.1 编译器的分遍在早期,编译器采取的设计方法复杂性受过

22、往经验和消耗资源的影响。这种通过一个人写一个比较简单的语言编译器可能是一个单一整体的软件。当源语言变得庞大而复杂后,高品质的输出是必需的,该设计可被分成若干相对独立的模块。具有单独的阶段意味着开发任务可以被分配成小部分,并给予不同的人来完成。通过改进替换的独立模块也变得容易得多。编译过程中阶段的划分是由生产质量编译器编译器项目(PQCC)在卡内基 - 梅隆大学的拥护4。该项目推出了前端,中端和后端的划分。一般只有前后端。然而,中间端通常被认为是前端或后端的一部分。在这两个平衡点之间取舍是公开议题。前端通常被认为在句法和语义处理发生时进行的处理,与翻译一起表示的较低的水平结构(低于源代码)。中间

23、端通常被设计为除源代码或机器代码以外的形式执行并优化。与源代码/机器代码独立,目的是能够支持不同的语言,对目标处理器的编译器版本之间共享通用的优化。后端需要从中间端输出。它可以执行的是针对特定计算机进行的更多的分析,转换和优化。然后,它为特定的处理器和操作系统生成代码。该前端/中间/后端的方法使得有可能结合用于与不同语言前端为不同的CPU绑定特定汇编语言。这种方法的实际例子是GNU编译器,LLVM,其中有多个前端和多种后端。按分遍次数分类的编译器有其计算机的硬件资源有限的原因。编译涉及执行大量的工作和早期计算机没有足够的内存来包含所有做这项工作的一个程序。所以编译器被划分成能够执行某些具有独立

24、功能的分析和翻译的方案,其中每个由前一个传过来的源(或它的某种表示)作为输入。在单次通过编译的能力已被看作是一个有益的过程,简化了的编译器和一个通用编译器通常执行编译比多遍编译器更快的工作。因此,在早期系统的资源限制带动下,许多早期的语言是专门设计,使他们能够在同一路径上进行编译。在一些情况下,语言功能的设计可能需要编译器执行一个以上的传过来的源。例如,声明出现在其引用出现的后面。在这种情况下,语句的翻译源代码,第一遍需要收集有关报表后出现的申明信息,他们的影响,与实际发生的翻译在随后的遍历中通过。单次通过编译的缺点是它不可能执行许多高质量的代码所需要的复杂的优化。它难以控制编译器优化。举例来

25、说,优化的不同阶段可以分析一个表达式很多次,但只有一次分析生成另一种表达。编译拆分成小的程序是生产可证明正确的编译器研究人员致力于使用的技术。证明了一组小程序的正确性往往比实现一个更大的,单一的程序的正确性更省力。而典型的多遍编译器从它的最终输出机器代码,还有其他几种类型:“源到源的编译器”是一种编译器,需要一个高级语言作为其输入和输出语言。例如,自动并行化编译器会经常把一个高级语言程序作为输入,然后转换代码和并行代码的注释或语言结构对其进行批注。编译器编译为一个理论机器的汇编语言,像一些虚拟机的实现,针对Java,Python和更多的字节码编译器也都是这样的类型。应用程序在字节码上的编译是编

26、译为机器码在执行之前的隔离层。2.2.2 编译器前端编译器前端通过分析源代码来构建程序,称为中间表示。它还管理符号表,数据结构在源代码相关的映射信息,如位置,类型和符号。而前端可以是一个单一的整体功能或程序,如在无扫描的解释器中,主要实现分析的几个阶段,其可以串行地或并发执行。特别是对于良好的工程来说:模块化和接口解耦。词法上有语法分析和语义分析。文法的解析包括句法分析(分别为记号的语法和表达式的语法),并在一般情况下,这些模块(词法分析器和解析器)可从该语言语法自动生成,但在更复杂的情况下,这些需要手动修改或手写。词法文法和短语语法通常上下文无关文法,能显著简化分析,在语义分析阶段处理上下文

27、的灵敏度上更有优势。语义分析阶段一般是更复杂的,并用手写的,但可使语法被部分或完全自动化。这些阶段本身可以进一步细分 - 词法扫描和评估,作为解析的第一步首先建立一个具体语法树(解析树),然后将其转变成一个抽象语法树。在一些情况下有附加的阶段需要涉及,特别是线性重建和预处理的时候,但这些比较少见的。可能的阶段的详细清单,包括:行改造,关键字或允许任意空间内标识需要分析的预处理阶段,输入字符序列转换为规范的形式提供给解释器语言。自上而下,递归下降,在20世纪60年代使用的表驱动的解释器通常一次读取源一个字符,并不需要一个单独的标记化阶段4。词法分析分割了源代码文本并切成独立的词法记号。每个记号是

28、语言的单个原子单位,比如关键字,标识符和符号名称。记号语法是典型的常规语言,所以来自正则表达式构成的有限状态自动机可以用来识别它。此阶段也被称为词法分析或扫描过程,而软件操作的词法分析被称为分词器或扫描器。这可能不是一个单独的步骤 - 它可以在解析步骤中无扫描解析,在这种情况下的解析是在字符级别,而不是记号级别进行组合的。有些语言,像C一样需要支持宏替换和条件编译预处理阶段。通常情况下,预处理阶段之前,语法和语义分析就会发生,预处理器操纵词法标记,而不是语法形式。然而,有些语言,支持基于语法形式宏替换。语法分析包括解析词法记号序列来识别程序的语法结构。此阶段通常生成解析树,根据一个正式的语法限

29、定的语言规则建立的树结构替换记号的线性序列。解析树通常由以后的阶段改变。语义分析是编译器增加了语义信息来解析解析树并建立符号表的位置。此阶段进行语义检查,类型检查(检查类型错误),或对象绑定(变量和函数引用与他们的定义关联),或明确赋值(要求所有本地变量在使用前进行初始化),拒绝不正确的代码或发出警告。语义分析通常需要一个完整的分析树,这意味着该逻辑下解析阶段的过程中,在逻辑上早于代码生成阶段,虽然它通常可以将多个过程折叠成一个编译器实现的代码。2.2.3 编译器后端编译器后端这个术语常常和生成汇编代码的功能重叠,有时与代码生成器混淆。所以使用中端区分通用的分析和优化阶段和机器相关的代码生成器

30、的后端。后端的主要阶段包括如下几点:分析:这是根据从输入的中间表示信息进行收集的过程,数据流分析是用来建立定义的工具链的,具有相关性分析,别名分析,指针分析,逃逸分析等精确分析,为编译器优化的基础。调用图和控制流图也常常在分析阶段建成。优化:中间语言表示变换成功,并转化为速度更快的形式。大多数优化是内联展开,无效代码消除,常量传播,循环转换,寄存器分配,甚至自动并行化等。代码生成:转化的中间语言被翻译成输出语言,通常是该系统的本机语言。这涉及到资源和存储决策,比如决定以适合哪些变量及其相关寻址模式以及寄存器和适当的机器指令和内存的选择和调度。调试数据也可能需要产生。编译器分析为任何编译器优化的

31、前提,之间紧密地进行工作。例如,相关分析是循环转换的关键。此外,编译器分析优化的范围相差很大,从小到基本块的程序/功能水平,甚至在整个程序(间优化)。编译器可能会做用更广阔的工作。但是,丰富的功能是不是无条件的:大范围的分析和优化是在编译时间和存储空间方面造成昂贵的开销,分析和优化尤其如此。2.2.4 编译器错误处理编译器错误处理单独提出的一个很重要的原因是,在编译器设计的过程中,错误处理是一个很容易忽视的过程,因为大部分编程语言的错误提示并不友好。编译器的正确性是软件工程与一个编译器语言规范行为的一个分支。包括使用形式化方法开发编译器和使用现有的编译器进行严格的测试(通常被称为编译器验证)。

32、3 编译器的实现3.1 语言标准的定义运算符基本上的操作符都支持,且操作符优先级相同。包括:(1) +、-、*、/、%、=(2) 、&、&、|、<<、>>(3) +=、-=、*=、/=、%=、+、(4) =、&=、&=、|=、<<=、>>=类型原理上支持所有的类型。典型的有:(1) 基本类型:int、float、string、byte、bool、var(类似于C的union)。(2) 复合类型:slice、map、chan(3) 用户自定义:函数(闭包)、类成员函数、类常量(1) 布尔类型:true、false(由

33、 builtin 模块支持)(2) var 类型:nil(由 builtin 模块支持)(3) 浮点类型:pi、e、phi (由 math 模块支持)变量及初始化基本操作a = 1 / 创建一个 int 类型变量,并初始化为 1 b = “hello” / string 类型 string 类型有如下内置操作a = “hello” + “world” / + 为字符串连接操作符 n = len(a) / 取 a 字符串的长度 b = a1 / 取 a 字符串的某个字符,得到的 b 是 byte 类型 c = a1:4 / 取子字符串slice 类型a = 1, 2, 3 / 创建一个 int

34、slice,并初始化为 1, 2, 3 b = 1, 2.3, 5 / 创建一个 float sliceslice内置的操作a = append(a, 4, 5, 6) / 含义与 Go 语言完全一致 n = len(a) / 取 a 的元素个数 m = cap(a) / 取 slice a 的容量 b1 = b2 / 取 b 这个 slice 中 index=2 的元素 b2 = 888 / 设置 b 这个 slice 中 index=2 的元素值为 888 b1, b2, b3 = 777, 888, 999 / 设置 b 这个 slice 中 index=1, 2, 3 的三个元素值 b

35、2 = b1:4 / 取子slice特别地,可以这样赋值:x, y, z = 1, 2, 3结果是 x = 1, y = 2, z = 3。这是基础设计导致的:map 类型a = “a”: 1, “b”: 2, “c”: 3 / 得到 mapstringint 类型的对象 b = “a”: 1, “b”, 2.3, “c”: 3 / 得到 mapstringfloat64 类型的对象 c = 1: “a”, 2: “b”, 3: “c” / 得到 mapintstring 类型的对象 d = “a”: “hello”, “b”: 2.0, “c”: true / 得到 mapstringint

36、erface 类型的对象 e = mkmap(“string:int”) / 创建一个空的 mapstringint 类型的对象 f = mkmap(mapOf(“string”, type(e) / 创建一个 mapstringmapstringint 类型的对象map类型操作n = len(a) / 取 a 的元素个数 x = a“b” / 取 a map 中 key 为 “b” 的元素 x = a.b / 含义同上,但如果 “b” 元素不存在会 panic a“e”, a“f”, a“g” = 4, 5, 6 / 同 Go 语言 a.e, a.f, a.g = 4, 5, 6 / 含义同

37、 a“e”, a“f”, a“g” = 4, 5, 6 delete(a, “e”) / 删除 a map 中的 “e” 元素需要注意的是,a"b" 的行为常见的范式是:x = “a”: 1, “b”: 2 a = x“a” / 结果:a = 1 if a != undefined / 判断a存在的逻辑 c = x“c” / 结果:c = undefined,注意不是0,也不是nil d = x“d” / 结果:d = undefined,注意不是0,也不是nilchan 类型ch1 = mkchan(“bool”, 2) / 得到 buffer = 2 的 chan bo

38、ol ch2 = mkchan(“int”) / 得到 buffer = 0 的 chan int ch3 = mkchan(mapOf(“string”, type(ch2) / 得到 buffer = 0 的 chan mapstringchan intchan 内置的操作n = len(ch1) / 取得chan当前的元素个数 m = cap(ch1) / 取得chan的容量 ch1 <- true / 向chan发送一个值 v = <-ch1 / 从chan取出一个值 close(ch1) / 关闭chan,被关闭的chan是不能写,但是还可以读(直到已经写入的值全部被取完

39、为止)需要注意的是,在 chan 被关闭后,<-ch 取得 undefined 值。所以应该这样:v = <-ch1 if v != undefined / 判断chan没有被关闭的逻辑 类型转换大部分情况下,不会自动类型转换。一些例外是:如果一个函数接受的是 float,但是传入的是 int,会进行自动类型转换。强制类型转换如下a = int(a) / 强制将 byte 类型转为 int 类型 b = float(b) / 强制将 int 类型转为 float 类型 c = string(a) / 强制将 byte 类型转为 string 类型流程控制if 语句if boolea

40、nExpr1 / elif booleanExpr2 / elif booleanExpr3 / else / switch 语句switch expr case expr1: / case expr2: / default: / for 语句for / 无限循环,需要在中间 break 或 return 结束 for booleanExpr / 类似很多语言的 while 循环 for initExpr; conditionExpr; stepExpr 另外也支持 for.range 语法:for range collectionExpr / 其中 collectionExpr 可以是 sl

41、ice, map 或 chan for index = range collectionExpr for index, value = range collectionExpr 函数函数和闭包基本语法funcName = fn(arg1, arg2, argN) / return expr 这就定义了一个名为 funcName 的函数。本质上来说,函数只是和 1、"hello" 类似的一个值,只是值的类型是函数类型。可以在一个函数中引用外层函数的变量。如:x = fn(a) b = 1 y = fn(t) return b + t return y(a) println(x

42、(3) / 结果为 4但是如果你直接修改外层变量会报错:x = fn(a) b = 1 y = fn(t) b = t / 这里会抛出异常,因为不能确定你是想定义一个的 b 变量,还是要修改外层 x 函数的 b 变量 y(a) return b 如果你想修改外层变量,需要先引用它,如下:x = fn(a) b = 1 y = fn(t) b; b = t / 现在正常了,我们知道你要修改外层的 b 变量 y(a) return b println(x(3) / 输出 3不定参数a = max(1.2, 3, 5, 6) / a 的值为 float 类型的 6 b = max(1, 3, 5,

43、6) / b 的值为 int 类型的 6也可以自定义一个不定参数的函数,如:x = fn(fmt, args) printf(fmt, args) 这样就得到了一个 x 函数,功能和内建的 printf 函数一模一样。多赋值x, y, z = 1, 2, 3.5 a, b, c = fn() return 1, 2, 3.5 / 返回的是 float slice ()需要注意的是,带上进行多赋值和不带进行多赋值在语义上有一点点不同。下面是例子:x1, y1, z1 = 1, 2, 3.5 x2, y2, z2 = 1, 2, 3.5 println(type(x1), type(x2)x1 的

44、类型为 int,而 x2 的类型是 float。defer这在处理系统资源(如文件、锁等)释放场景非常有用。一个典型场景:f, err = os.open(fname) if err != nil / 做些出错处理 return defer f.close()/ 正常操作这个 f 文件类一个用户自定义类型的基本语法如下:Foo = class fn setAB(a, b) this.a, this.b = a, b fn getA() return this.a 有了这个 class Foo,我们就可以创建 Foo 类型的 object 了:foo = new Foofoo.setAB(3,

45、"hello")a = foo.getA()println(a) / 输出 3构造函数构造函数只是一个名为 _init 的成员方法(method):Foo = class fn _init(a, b) this.a, this.b = a, b 有了这个 class Foo 后,我们 new Foo 时就必须携带2个构造参数了:foo = new Foo(3, "hello")println(foo.a) / 输出 3include语法 一个文件可以通过 include 文法来将另一个文件的内容包含进来。所谓包含,其实际的能力类似于将代码拷贝粘贴过来。例

46、如,在某个目录下有 a 和 b 两个文件。其中 a 内容如下:println("in script A")foo = fn() println("in func foo:", a, b)其中 b 内容如下:a = 1b = 2include "a"println("in script B")foo()模块及 import模块(module)是一个目录,该目录下要求有一个名为 main 的文件。模块中的标识(ident)默认都是私有的。想要导出一个标识(ident),需要用 export 语法。例如:a = 1b =

47、 2println("in script A")f = fn() println("in func foo:", a, b)export a, f这个模块导出了两个标识(ident):整型变量 a 和 函数 f。要引用这个模块,我们需要用 import 文法:import "foo/bar.v1"import "foo/bar.v1" as bar2bar.a = 100 / 将 bar.a 值设置为 100println(bar.a, bar2.a) / bar.a, bar2.a 的值现在都是 100bar.f

48、() 将一个模块 import 多次并不会出现什么问题,事实上第二次导入不会发生什么,只是增加了一个别名。include 和 import 的区别 include 是拷贝粘贴,比较适合用于模块内的内容组织。比如一个模块比较复杂,则可以用 include 语句分解到多个文件中。它永远基于 _dir_(即 include 代码所在脚本的目录) 来定位文件。import 是模块引用,适合用于作为业务分解的主要方式。反射 在任何时候,你都可以用 type 函数来查看一个变量的实际类型,如:t1 = type(1)t2 = type(fn() )我们得到了 *Function。这说明尽管用户自定义的函数

49、原型多样,即使定义多种多样但是类型是一致的。 我们也可以看看用户自定义的类型:Foo = class fn f() t1 = type(Foo)t2 = type(Foo.f)foo = new Foot3 = type(foo)t4 = type(foo.f) 可以看到,class Foo 的类型是 *Class,而 object foo 的类型是 *Object。而 Foo.f 和普通用户自定义函数一致,也是 *Function,但 foo.f 不一样,它是 *Method 类型。样例代码 求最大素数primes = 2, 3n = 1limit = 9isPrime = fn(v) fo

50、r i = 0; i < n; i+ if v % primesi = 0 return false return truelistPrimes = fn(max) v = 5 for for v < limit if isPrime(v) primes = append(primes, v) if v * v >= max return v += 2 v += 2 n; n+ limit = primesn * primesn maxPrimeOf = fn(max) if max % 2 = 0 max- listPrimes(max) n; n = len(primes

51、) for if isPrime(max) return max max -= 2 /if len(os.args) < 2 fprintln(os.stderr, "Usage: maxprime <Value>") returnmax, err = strconv.parseInt(os.args1, 10, 64)if err != nil fprintln(os.stderr, err) return 1max-v = maxPrimeOf(max)println(v)3.2 词法分析在计算机科学中,词法分析是字符(如在计算机程序或网页)的序列变换

52、为标记(具有确定的字符串)序列的过程。这样的词法通常与一个分析器相关,它们分析的编程语言,网页的语法,等等结合。词法分析一般是编译器的第一部分,而且词法分析很简单,就是一个有限状态机。开始词法分析的过程就是把源文件转换成一组预先定义好的词法记号的过程。这一组被统一表现的词法记号之后会被送入语法分析器进行语法解析,这里我们主要关注如何进行词法分析。 做词法分析就几种方法:(1) 直接使用工具比如lex。(2) 使用更低一层的正则表达式。(3) 使用状态动作,构造一个状态机。而真正实现一个语言的话,使用工具没有什么错,但是问题是,很难获得正确的错误提示。工具生成的错误处理很弱,而且需要学习另一门规则或特定的语法,生成的代码可能性能不好,难以优化,但是用工具可以非常简单实现词法分析。早期编译器的设计阶段都会使用lex来做词法分析器,比如gcc就是这么做的,但是到了后期一个真正生产化的语言可能不能依赖生成的代码,而需要做自己特定的修改和优化,所以自己实现一个词法分析器就显得比较重要了。正则表达被人诟病的一个话题就是效率问题,比如perl拥有功能最强大的正则表达式,但是整个正则表达式引擎的效率却很低,C在这方面牺牲了一些正则表达式的特性来保证正则表达式的效率不至于过低,但是正则表达式对于大量文本处理体现的弱势却是很明显的。因为可能我们要处理的状态其实不需要一个繁重的正则表达来解决。

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论