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

正如我在上一篇文章中所写的那样,我最近开始研究一种名为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

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

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

相关文章

Flutter快速构建集美观与⾼性能于⼀体的APP

先上干货 github:https://github.com/flutter/flutter 官网&#xff1a;http://flutter.io/ 中文资源&#xff1a;https://flutter-io.cn/ 当然我也用其他敏捷式平台开发过APP&#xff0c;比如APICloud、uiapp,相比Flutter难度会小一些。 用户的需求 移动软件开发的困难 这…

一个线程加一运算,一个线程做减一运算,多个线程同时交替运行--synchronized...

使用synchronized package com.pb.thread.demo5;/**使用synchronized* 一个线程加一运算&#xff0c;一个线程做减法运算&#xff0c;多个线程同时交替运行* * author Denny**/ public class Count {private int num 0;private boolean flag false; // 标识//加法public sync…

如何使用UI技术提升租房类APP的看房体验?

这个看你处理的数据够不够复杂啦。 越个性化,越流程的APP越需要处理复杂的数据。 使用SVG制作户型结构编辑器。

jboss 发布web_JBoss模块示例–模块化Web应用程序

jboss 发布web最近&#xff0c;我读到了为什么没有标准来开发真正的模块化Web应用程序&#xff1f; 由Patroklos Papapetrou撰写&#xff08; 在Java Code Geeks中也有介绍 &#xff09;。 受本文的启发&#xff0c;我决定检查实际使用的JBoss模块 。 这篇文章逐步描述了我的实…

南方数据后台的WEBSHEL

南方数据企业系统&#xff0c;后台上传截断拿webshell 在“新闻资讯”-“添加新闻”模块中&#xff0c;在“新闻图片”中点击“上传”按钮&#xff0c;会出现一个弹窗&#xff0c;复制弹窗的地址&#xff0c;在新标签页中打开 本地准备个asp类型的一句话木马&#xff1a;<%…

【APICloud系列|20】如何使用使用APICloud开发出优质的Hybrid App

谈到这里,我先抛出几个问题,虽然他们社区比较活跃,我曾经也在上面开发过一款办公类应用并成功上架到各大安卓应用商店与APPstore,有兴趣的可以看看我以前的帖子。新手可以学习一下他们七天的教学,可以很快的入门,至于他们官方的一些视频可能比较陈旧,凑合着看吧,影响不是…

java对象的序列化机制详解

Java对象的序列化机制 Java对象的序列化&#xff0c;是将内存中的java对象转化为二进制的字节流&#xff0c;然后保存到磁盘中或者在网络上。这就是序列化对象&#xff0c;反序列化顾名思义就是将对象的二进制字节流恢复成原来的对象。注意只有对象的类名和属性能被序列化&…

测试并发应用

本文是我们学院课程中名为Java Concurrency Essentials的一部分 。 在本课程中&#xff0c;您将深入探讨并发的魔力。 将向您介绍并发和并发代码的基础知识&#xff0c;并学习诸如原子性&#xff0c;同步和线程安全之类的概念。 在这里查看 &#xff01; 目录 1.简介 2. Sim…

无忧企业系统的getshell

方法一&#xff1a;数据库备份 写入后进行数据库备份 路径 shell工具连接 方法二&#xff1a;修改允许上传后缀&#xff0c;直接上传马

如何使用FinalShell、FileZilla上传网站代码到服务器?这两个都是神器

这段时间想做一个导航网站来着,然后就简单写了一个网页,买了一个域名、一台ECS服务器,都是比较便宜的那种,https://www.aliyun.com/minisite/goods?userCode=1k1odmgm 这个学生或者新用户基本都是一折,还能玩得起。所有软件的安装除了选择安装路径,都可以无脑按安装。 …

[CareerCup] 9.6 Generate Parentheses 生成括号

9.6 Implement an algorithm to print all valid (e.g., properly opened and closed) combinations of n-pairs of parentheses.EXAMPLEInput: 3Output: ((())), (()()), (())(), ()(()), ()()() LeetCode上的原题&#xff0c;请参见我之前的博客Generate Parentheses 生成括号…

神经网络:深度学习优化方法

1.有哪些方法能提升CNN模型的泛化能力 采集更多数据&#xff1a;数据决定算法的上限。 优化数据分布&#xff1a;数据类别均衡。 选用合适的目标函数。 设计合适的网络结构。 数据增强。 权值正则化。 使用合适的优化器等。 2.BN层面试高频问题大汇总 BN层解决了什么问…

PDF.js如何添加放大缩小的功能,转换成图片应该如何实现?

把官方的安装包搞下来,自己的PDF文件及index.html添加进去,上面的目录结构是未添加的,我先把PDF文件搞成canvas然后搞成图片,然后再图片上添加按钮对图片进行放大缩小操作,方便对用户行为进行录屏。 <!DOCTYPE HTML> <html data-dpr="1" style="…

mimikatz免杀过360和火绒

mimikatz mimikatz是一款能够从Windows认证&#xff08;LSASS&#xff09;的进程中获取内存&#xff0c;并且获取名闻密码和NTLM哈希值的工具&#xff0c;攻击者可以利用这种功能漫游内网。也可以通过明文密码或者hash值进行提权。这款工具机器出名所以被查杀的几率极高。 1、…

ibatis Parameter index out of range (1 number of parameters, which is 0)

这个错误除了网上常见的like写错之外&#xff0c;这里列出其中一种写法like concat(%, #keyword#, %),还有另外的多写单引号什么的以外&#xff0c;今天遇到另一个原因,百度谷歌各种查也没查到&#xff0c;最后才意识到由于我是在navicat中的写好的再粘贴上去的&#xff0c;注释…

SVN:请求不到主机,应该如何解决?

连了公司的局域网&#xff0c;可能一个账号人多的缘故&#xff0c;给你们讲一下思路。确保一点在同一个局域网。 然后ping一下域名。也是不通的 把域名换成IP进行访问试试&#xff0c;这个检查一下路径没有问题即可。

MSSQL提权

之前对MSSQL提权了解较少&#xff0c;所以在这里记录一些关于这类提权的几种姿势 1.xp_cmdshell提权 存储过程为数据库提供了强大的功能&#xff0c;其类似UDF&#xff0c;在MSSQL中xp_cmdshell可谓臭名昭著了。MSSQL强大的存储过程也为黑客提供了遍历&#xff0c;在相应的权…

如何以及为什么序列化Lambda

总览 lambda序列化在许多用例中很有用&#xff0c;例如持久配置或作为远程资源的访客模式 。 远程访客 例如&#xff0c;因此我想访问远程Map上的资源&#xff0c;可以使用get / put&#xff0c;但是说我只想从Map的值中返回一个字段&#xff0c;我可以将lambda作为访问者来传…

NSTimer注意内存泄露(真该死)

NSTimer可以用来执行一些定时任务&#xff0c;比较常用的方法就是&#xff1a; (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; 可是&#xff0c;仔细看官方文档中对于参数t…

Redis漏洞利用的4种方法

Redis简介 redis是一个key-value存储系统。和Memcached类似&#xff0c;它支持存储的value类型相对更多&#xff0c;包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash&#xff08;哈希类型&#xff09;。这些数据类型都支持push/pop、add/remov…