正如我在上一篇文章中所写的那样,我最近开始研究一种名为Turin的新编程语言。 可以在GitHub上找到适用于languag初始版本的编译器。 我目前正在改进语言,并正在开发Maven和IntelliJ插件。 在这里和下一篇文章中,我将介绍编译器和相关工具的不同组件。

编译器的结构

编译器需要做几件事:

  1. 获取源代码并生成抽象语法树(AST)
  2. 通过不同阶段转换AST以简化处理。 我们基本上希望从非常接近语法的表示形式过渡到更易于处理的表示形式。 例如,我们可以对语言进行“去糖化”,将几种(显然)不同的结构表示为同一结构的变体。 一个例子? Java编译器将字符串连接转换为对StringBuffer.append的调用
  3. 执行语义检查。 例如,我们要检查所有表达式是否都使用可接受的类型(我们不想对字符求和,对吗?)
  4. 产生字节码

第一步需要构建两个组件:词法分析器和解析器。 词法分析器对文本进行操作并生成标记序列,而解析器将标记组合到用于创建AST的构造(类型声明,语句,表达式等)中。 为了编写词法分析器和解析器,我使用了ANTLR。

在本文的其余部分,我们将研究词法分析器。 解析器和编译器的其他组件将在以后的文章中讨论。

为什么要使用ANTLR?

ANTLR是用于编写词法分析器和解析器的非常成熟的工具。 它可以生成多种语言的代码,并具有良好的性能。 它维护良好,我确信它具有处理可能遇到的所有极端情况所需的所有功能。 除此之外,ANTLR 4可以编写简单的语法,因为它可以为您解决左递归定义。 因此,您不必编写许多中间节点类型即可为表达式指定优先级规则。 我们将在分析器中对此进行更多介绍。

Xtext使用了ANTLR(我已经使用了很多),并且在为.NET平台 (一种用于.NET的EMF)构建模型驱动的开发框架时 ,我使用了ANTLR。 因此,我知道并信任ANTLR,因此没有理由寻找其他选择。

当前的词法分析器语法

这是词法分析器语法的当前版本。

lexer grammar TurinLexer;@header {}@lexer::members {public static final int WHITESPACE = 1;public static final int COMMENTS = 2;
}// It is suggested to define the token types reused in different mode.
// See mode in-interpolation below
tokens { VALUE_ID, TYPE_ID, INT, LPAREN, RPAREN, COMMA, RELOP, AND_KW, OR_KW, NOT_KW }// Of course keywords has to be defined before the rules for identifiers
NAMESPACE_KW        : 'namespace';
PROGRAM_KW          : 'program';
PROPERTY_KW         : 'property';
TYPE_KW             : 'type';
VAL_KW              : 'val';
HAS_KW              : 'has';
ABSTRACT_KW         : 'abstract';
SHARED_KW           : 'shared';
IMPORT_KW           : 'import';
AS_KW               : 'as';
VOID_KW             : 'Void';
RETURN_KW           : 'return';
FALSE_KW            : 'false';
TRUE_KW             : 'true';
IF_KW               : 'if';
ELIF_KW             : 'elif';
ELSE_KW             : 'else';// For definitions reused in mode in-interpolation we define and refer to fragments
AND_KW              : F_AND;
OR_KW               : F_OR;
NOT_KW              : F_NOT;LPAREN              : '(';
RPAREN              : ')';
LBRACKET            : '{';
RBRACKET            : '}';
LSQUARE             : '[';
RSQUARE             : ']';
COMMA               : ',';
POINT               : '.';
COLON               : ':';
// We use just one token type to reduce the number of states (and not crash Antlr...)
// https://github.com/antlr/antlr4/issues/840
EQUAL               : '==' -> type(RELOP);
DIFFERENT           : '!=' -> type(RELOP);
LESSEQ              : '<=' -> type(RELOP);
LESS                : '<'  -> type(RELOP);
MOREEQ              : '>=' -> type(RELOP);
MORE                : '>'  -> type(RELOP);
// ASSIGNMENT has to comes after EQUAL
ASSIGNMENT          : '=';
// Mathematical operators cannot be merged in one token type because
// they have different precedences
ASTERISK            : '*';
SLASH               : '/';
PLUS                : '+';
MINUS               : '-';PRIMITIVE_TYPE      : F_PRIMITIVE_TYPE;
BASIC_TYPE          : F_BASIC_TYPE;VALUE_ID            : F_VALUE_ID;
// Only for types
TYPE_ID             : F_TYPE_ID;
INT                 : F_INT;// Let's switch to another mode here
STRING_START        : '"' -> pushMode(IN_STRING);WS                  : (' ' | '\t')+ -> channel(WHITESPACE);
NL                  : '\r'? '\n';COMMENT             : '/*' .*? '*/' -> channel(COMMENTS);LINE_COMMENT        : '//' ~[\r\n]* -> channel(COMMENTS);mode IN_STRING;STRING_STOP         : '"' -> popMode;
STRING_CONTENT      : (~["\\#]|ESCAPE_SEQUENCE|SHARP)+;
INTERPOLATION_START : '#{' -> pushMode(IN_INTERPOLATION);mode IN_INTERPOLATION;INTERPOLATION_END   : '}' -> popMode;
I_PRIMITIVE_TYPE    : F_PRIMITIVE_TYPE -> type(PRIMITIVE_TYPE);
I_BASIC_TYPE        : F_BASIC_TYPE -> type(BASIC_TYPE);
I_FALSE_KW          : 'false' -> type(FALSE_KW);
I_TRUE_KW           : 'true' -> type(TRUE_KW);
I_AND_KW            : F_AND -> type(AND_KW);
I_OR_KW             : F_OR -> type(OR_KW);
I_NOT_KW            : F_NOT -> type(NOT_KW);
I_IF_KW             : 'if' -> type(IF_KW);
I_ELSE_KW           : 'else' -> type(ELSE_KW);
I_VALUE_ID          : F_VALUE_ID   -> type(VALUE_ID);
I_TYPE_ID           : F_TYPE_ID -> type(TYPE_ID);
I_INT               : F_INT -> type(INT);
I_COMMA             : ',' -> type(COMMA);
I_LPAREN            : '(' -> type(LPAREN);
I_RPAREN            : ')' -> type(RPAREN);
I_LSQUARE           : '[' -> type(LSQUARE);
I_RSQUARE           : ']' -> type(RSQUARE);I_ASTERISK          : '*' -> type(ASTERISK);
I_SLASH             : '/' -> type(SLASH);
I_PLUS              : '+' -> type(PLUS);
I_MINUS             : '-' -> type(MINUS);I_POINT             : '.' -> type(POINT);
I_EQUAL             : '==' -> type(RELOP);
I_DIFFERENT         : '!=' -> type(RELOP);
I_LESSEQ            : '<=' -> type(RELOP);
I_LESS              : '<'  -> type(RELOP);
I_MOREEQ            : '>=' -> type(RELOP);
I_MORE              : '>'  -> type(RELOP);
I_STRING_START      : '"' -> type(STRING_START), pushMode(IN_STRING);
I_WS                : (' ' | '\t')+ -> type(WS), channel(WHITESPACE);fragment F_AND            : 'and';
fragment F_OR             : 'or';
fragment F_NOT            : 'not';
fragment F_VALUE_ID       : ('_')*'a'..'z' ('A'..'Z' | 'a'..'z' | '0'..'9' | '_')*;
// Only for types
fragment F_TYPE_ID        : ('_')*'A'..'Z' ('A'..'Z' | 'a'..'z' | '0'..'9' | '_')*;
fragment F_INT            : '0'|(('1'..'9')('0'..'9')*);
fragment F_PRIMITIVE_TYPE : 'Byte'|'Int'|'Long'|'Boolean'|'Char'|'Float'|'Double'|'Short';
fragment F_BASIC_TYPE     : 'UInt';fragment ESCAPE_SEQUENCE  : '\\r'|'\\n'|'\\t'|'\\"'|'\\\\';
fragment SHARP            : '#'{ _input.LA(1)!='{' }?;

我已经做了一些选择:

  • 有两种不同类型的ID: VALUE_IDTYPE_ID。 由于可以轻松地区分值和类型,因此语法上的歧义性较小。 在Java中,当遇到(foo)时,我们不知道它是表达式(对括号之间foo表示的值的引用)还是类型foo的强制转换。 我们需要看下面的内容才能理解它。 我认为这很愚蠢,因为实际上每个人都只对类型使用大写的标识符,但是由于这不是由语言强制执行的,因此编译器无法从中受益
  • 换行符与都灵相关,因此我们有针对它们的标记,我们基本上希望语句以换行符终止,但我们在逗号后接受可选的换行符
  • 空格(但换行符)和注释是在它们自己的通道中捕获的,因此我们可以在解析器语法中忽略它们,但可以在需要时检索它们。 例如,我们需要它们来突出显示语法,并且通常需要IntelliJ插件,因为它需要为源文件中的每个单个字符定义标记,而没有空格
  • 最棘手的部分是在Ruby中解析字符串插值,例如“我的名字是#{user.name}”。 我们使用模式:遇到字符串开始(“)时,我们切换到词法分析器模式IN_STRING。 在IN_STRING模式下,如果遇到插值(#{)的开始,我们将移至词法分析器模式IN_INTERPOLATION。 在IN_INTERPOLATION模式下,我们需要接受表达式中使用的大多数标记(可悲的是,这意味着我们的词法分析器语法有很多重复)。
  • 我不得不将关系运算符折叠为一种令牌类型,以使生成的词法分析器的状态数不会太大。 这意味着我将不得不查看RELOP令牌的文本,以确定需要执行哪个操作。 没什么可怕的,但是您必须知道如何解决此类问题。

测试词法分析器

我写了很多针对词法分析器的测试。 特别是,我测试了最复杂的部分:有关字符串插值的部分。

一些测试的示例:

@Testpublic void parseStringWithEmptyInterpolation() throws IOException {String code = "\"Hel#{}lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.INTERPOLATION_START, TurinLexer.INTERPOLATION_END, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseStringWithInterpolationContainingID() throws IOException {String code = "\"Hel#{foo}lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.INTERPOLATION_START,TurinLexer.VALUE_ID,TurinLexer.INTERPOLATION_END, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseStringWithSharpSymbol() throws IOException {String code = "\"Hel#lo!\"";verify(code, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}@Testpublic void parseMethodDefinitionWithExpressionBody() throws IOException {String code = "Void toString() = \"foo\"";verify(code, TurinLexer.VOID_KW, TurinLexer.VALUE_ID, TurinLexer.LPAREN, TurinLexer.RPAREN, TurinLexer.ASSIGNMENT, TurinLexer.STRING_START, TurinLexer.STRING_CONTENT, TurinLexer.STRING_STOP);}

如您所见,我只是在字符串上测试令牌,并验证它是否生成了正确的令牌列表。 简单而直接。

结论

我在ANTLR上使用该语言的经验并不完美:存在问题和局限性。 必须在单个令牌类型中折叠多个运算符并不好。 必须为不同的词法分析器模式重复几个标记定义是不好的。 但是,ANTLR被证明是在实践中可用的工具:它可以完成它需要做的所有事情,并且对于每个问题都有一个可接受的解决方案。 解决方案可能不是理想的,也许不是理想的解决方案,但是有一个解决方案。 因此,我可以使用它并继续进行编译器中更有趣的部分。

翻译自: https://www.javacodegeeks.com/2015/09/turin-programming-language-for-the-jvm-building-advanced-lexers-with-antlr.html

都灵JVM编程语言:使用ANTLR构建高级词法分析器相关推荐

  1. jvm与非jvm语言优劣_都灵JVM编程语言:使用ANTLR构建高级词法分析器

    jvm与非jvm语言优劣 正如我在上一篇文章中所写的那样,我最近开始研究一种名为Turin的新编程语言. 可以在GitHub上找到适用于languag初始版本的编译器. 我目前正在改善语言,并正在开发 ...

  2. DO447构建高级作业工作流--作业调度和配置通知

    DO447构建高级作业工作流–作业调度和配置通知

  3. 已知x=python是一种非常好的编程语言-为什么用Python,高级的Python是一种高级编程语言...

    Python特性 如果有人问我Python最大的特点是什么,我会毫不犹豫地告诉他:它简单易学,功能强大.作为一个纯自由软件,Python有许多优点: 很简单.基于"优雅".&quo ...

  4. 用于构建高级媒体应用程序的工具

    在前些天闭幕的//Build/大会上,微软媒体平台部的首席布道师Mike Downey检视了用于Windows 8应用程序开发的工具和框架,其特性包括高级媒体播放功能,像流媒体,DVR控制.广告和关闭 ...

  5. sv编程语言_SV及UVM高级话题篇之一:SystemVerilog开源公共库(上)

    伴随着SV推广的热浪,SV已经不只是作为一种验证语言流行开来,实际上它同样在早期也作为了一种硬件描述语言和一种通用编程语言得到了应用.在软件编程的过程中,SV同Java一般也有了更为丰富的数据类型和类 ...

  6. sbt编程语言scala的构建工具配置及项目构建(附带网盘下载)

    SBT简介 SBT 是 Scala 的构建工具,全称是 Simple Build Tool, 类似 Maven 或 Gradle. Java可以用Maven快速构建项目,scala用SBT快速构建一个 ...

  7. ssr客户端android!一次关于JVM的面试经历,高级面试题+解析

    缘起 随着互联网企业的不断发展,产品项目中的模块越来越多,用户体验要求也越来越高,想实现小步快跑.快速迭代的目的越来越难,还有应用之间的互相调用等等问题,插件化技术应用而生.如果没有插件化技术,美团. ...

  8. Flex 3快速入门: 构建高级用户界面 添加拖放支持

    在典型的可视化开发环境中,你能够在应用程序中选中并且拖动一个对象在屏幕中来回移动.  Flex Drag and Drop管理器能够让用户选中一个对象,比如List控件的一个元素,或者一个控件比如Im ...

  9. DO447构建高级作业工作流--创建作业模板调查以设置工作的变量

最新文章

  1. 【Ubuntu】Ubuntu14.04添加163的源
  2. jQuery+ajax中,让window.open不被拦截(转)
  3. 12/12 day06
  4. 视频监控/存储系统设计要点
  5. kubernetes入门mysql_Kubernetes 1.13 完全入门 (10) Mysql 数据例子
  6. Mac 安装redis
  7. chrome 插件 页面请求转发_巧用Chrome插件二三事
  8. 15条常用的视频音频编辑脚本命令(mencoder/ffmpeg等)
  9. (计算机组成原理)第二章数据的表示和运算-第二节9:本节习题
  10. 码农30岁后的体检——你最需要的是直面的勇气
  11. C#调用Matlab生成的dll方法
  12. ARP协议详解,ARP协议执行原理、ARP协议如何根据IP地址寻找Mac地址?
  13. 更加方便地使用Markdown
  14. 排队论的计算机模拟,8.2 排队论模型(二)-----计算机模拟.pdf
  15. 小白如何通俗地理解 - - 遗传算法以及其实际应用?
  16. 一个人知道自己为什么而活,他就可以忍受生活加诸他的一切苦难
  17. Window 配置RabbitMQ
  18. conda查找安装包并安装指定版本的安装包
  19. java实现图片平铺倾斜水印效果--转载
  20. python画国际象棋棋盘图片_python3 turtle 画国际象棋棋盘

热门文章

  1. PPT 2010实现使用自定义主题付下载
  2. css实现一级下拉菜单
  3. 179. 最大数---LeetCode---JAVA
  4. eclipse 创建ssm spring+springmvc+mybatis 实现登录注册
  5. 面试项目 java-服务端 18h58
  6. python中变量怎么定义_python中的变量的使用定义以及使用规则
  7. kali mysql停止服务器_MySQL 的主从复制(高级篇)
  8. spring(2)装配Bean
  9. java集合——遗留的集合
  10. jms面试题_最新的20多个JMS面试问答(2020)