`
RednaxelaFX
  • 浏览: 3048084 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

一个简单的语言的语法(一):用ANTLR描述语法

阅读更多
系列链接:
一个简单的语言的语法(一):用ANTLR描述语法
一个简单的语言的语法(二):ANTLR的重写规则
一个简单的语言的语法(三):做些小调整,并将生成目标换到CSharp2

在JavaEye的博客频道逛,看到NeuronR的blog上有关于他的编译器实践的帖子,觉得有点意思,于是平行的用别的方法来做那个编译器。那边要求是用C来实现,我这边就用些方便些的语言来实现吧。
本篇将通过ANTLR 3.1描述Jerry语言,并在ANTLRWorks里实验,通过生成的解析器来得到Jerry程序代码对应的解析树。

关注过解析器生成器的话,ANTLR应该不会是个陌生的名字才对。Anyway简短说几句。ANTLR在生成解析器方面正得到越来越多的应用,几个实例,XRuby项目用了,Jython现在正在使用,SapphireSteel的Ruby和ActionScript IDE也用ANTLR来生成编译器前端。
ANTLR,ANother Tool for Language Recognition,是由Terence Parr教授开发的语言工具,用于协助生成解析器、编译器、解释器等与语言处理相关的程序。它由PCCTS发展而来,特征是可以生成采用LL(*)算法的递归下降式的解析器。它所使用的语法表述格式类似于常见的EBNF(extended Backus–Naur form),易于理解和维护。

读了编译器构造实践[任务布置]之后,大概琢磨着语法应该是像这样的吧:
Jerry.g:(ANTLR 3.1语法)
grammar Jerry;

program	:	statementList EOF
	;

statementList
	:	statement*
	;

statement
	:	expressionStatement
	|	variableDeclaration
	|	blockStatement
	|	ifStatement
	|	whileStatement
	|	breakStatement
	|	readStatement
	|	writeStatement
	;

expressionStatement
	:	expression SEMICOLON
	;

variableDeclaration
	:	typeSpecifier Identifier ( LBRACK Integer RBRACK )* initializer?
			( COMMA Identifier ( LBRACK Integer RBRACK )* initializer? )*
			SEMICOLON
	;

typeSpecifier
	:	INT | REAL
	;

initializer
	:	EQ ( expression | arrayLiteral )
	;

arrayLiteral
	:	LBRACE
			( expression | arrayLiteral ) ( COMMA ( expression | arrayLiteral ) )*
		RBRACE
	;

blockStatement
	:	LBRACE statementList RBRACE
	;

ifStatement
	:	IF LPAREN expression RPAREN statement ( ELSE statement )?
	;

whileStatement
	:	WHILE LPAREN expression RPAREN statement
	;

breakStatement
	:	BREAK SEMICOLON
	;

readStatement
	:	READ variableAccess SEMICOLON
	;

writeStatement
	:	WRITE expression SEMICOLON
	;

variableAccess
	:	Identifier ( LBRACK Integer RBRACK )*
	;

expression
	:	assignmentExpression
	|	logicalOrExpression
	;

assignmentExpression
	:	variableAccess EQ expression
	;

logicalOrExpression
	:	logicalAndExpression ( OROR logicalAndExpression )*
	;

logicalAndExpression
	:	relationalExpression ( ANDAND relationalExpression )*
	;

relationalExpression
	:	additiveExpression ( relationalOperator additiveExpression )?
	|	BANG relationalExpression
	;

additiveExpression
	:	multiplicativeExpression ( additiveOperator multiplicativeExpression )*
	;
  
multiplicativeExpression
	:	primaryExpression ( multiplicativeOperator primaryExpression )*
	;

primaryExpression
	:	variableAccess
	|	Integer
	|	RealNumber
	|	LPAREN expression RPAREN
	|	SUB primaryExpression
	;

relationalOperator   
	:	LT | GT | EQEQ | LE | GE | NE
	;

additiveOperator
	:	ADD | SUB
	;

multiplicativeOperator
	:	MUL | DIV
	;

// lexer rules

LPAREN	:	'('
	;

RPAREN	:	')'
	;

LBRACK	:	'['
	;

RBRACK	:	']'
	;

LBRACE	:	'{'
	;

RBRACE	:	'}'
	;

COMMA	:	','
	;

SEMICOLON
	:	';'
	;

ADD	:	'+'
	;

SUB	:	'-'
	;

MUL	:	'*'
	;

DIV	:	'/'
	;

EQEQ	:	'=='
	;

NE	:	'!='
	;

LT	:	'<'
	;

LE	:	'<='
	;

GT	:	'>'
	;

GE	:	'>='
	;

BANG	:	'!'
	;

ANDAND	:	'&&'
	;

OROR	:	'||'
	;

EQ	:	'='
	;

IF	:	'if'
	;

ELSE	:	'else'
	;

WHILE	:	'while'
	;

BREAK	:	'break'
	;

READ	:	'read'
	;

WRITE	:	'write'
	;

INT	:	'int'
	;

REAL	:	'real'
	;

Identifier
	:	LetterOrUnderscore ( LetterOrUnderscore | Digit )*
	;

Integer	:	Digit+
	;

RealNumber
	:	Digit+ '.' Digit+
	;

fragment
Digit	:	'0'..'9'
	;

fragment
LetterOrUnderscore
	:	Letter | '_'
	;

fragment
Letter	:	( 'a'..'z' | 'A'..'Z' )
	;

WS	:	( ' ' | '\t' | '\r' | '\n' )+ { $channel = HIDDEN; }   
	;

Comment
	:	'/*' ( options { greedy = false; } : . )* '*/' { $channel = HIDDEN; }
	;

LineComment
	:	'//' ~('\n'|'\r')* '\r'? '\n' { $channel = HIDDEN; }
	;


基本上是怎么简单怎么写弄出来的语法而已。有些地方,例如下标表达式(index expression),是可以显式写这么一条规则:
indexExpression
  : expression
  | indexExpression '[' Integer ']'
  ;

然后削除直接左递归:
indexExpression
  : expression ( '[' Integer ']' )*
  ;

然后看情况再消除间接左递归。
但在Jerry语言里,能使用下标的只有数组变量,那就干脆不定义单独的下标表达式,而直接把下标的操作合并到variableAccess规则的数组变量分支里,也就是:
variableAccess
  : Identifier ( '[' Integer ']' )*
  ;

另外Jerry语言里的左值也只可能用变量访问来表示,所以也没有针对左值写特殊的规则(赋值表达式和read语句都需要用到左值的概念),直接就用variableAccess了。


这语法只是大概猜的而已。有些细节在NeuronR的帖子里没有提到,所以语法或许与他的课程实践的要求不完全一样。Anyway,我就先以这个理解为基础来平行做后续的实现了。

几个不太肯定的细节:
1、if和while语句的条件表达式的类型有没有要求?
我这里是不在语法上对表达式类型做限制,到后面的语义分析的时候再检查。

2、int/real与逻辑/比较运算表达式的类型(boolean)是否有隐式转换的关系?
我这里假设是“有”。如果有的话,relationalExpression那里就能偷懒;不然可能得做点麻烦些的处理……
主要是关系到那个一元布尔否定运算符('!'),C-like语言里它应该是跟一元算术求反运算符('-')的优先级一样吧?在这个Jerry语言它的优先级里却比所有算术和比较运算符要低。
看像这样的Java代码:
public class Test {
    public static void main(String[] args) {
        if (! 1 != 1 || 1 == 1 ) { System.out.println("true"); }
        else { System.out.println("false"); }
    }
}

编译的时候会有错误,因为!的优先级比!=高,而!不能作用在类型为int的1上;
但这样的Jerry代码:
if (! 1 != 1 || 1 == 1 ) write 1;
else write 0;

却应该能正确编译,且运行结果为1,因为!的优先级比!=低而比||高,所以1 != 1是false,它的反是true;然后1 == 1是true,跟前面的true做或运算也还是true。
这语法比较的诡异……

3、变量声明是否一定要一并出现在同一个作用域的其它语句的前面(就像C89那样)?
我这里假设是“否”。变量可以在任何能出现语句的地方出现。作用域从声明开始,到它的包围块结束为止。

4、浮点数的字面量是否要求小数点前必须有至少一位数字?浮点数字面量是否允许或要求后缀修饰(例如'r'或者'f'或者'd'之类)?
我的假设是浮点数只有一种字面量,满足这种形式:(用Perl兼容的正则表达式表示)
/\d+\.\d+/

也就是小数点前后都必须有数字,而且没有后缀。

5、多维数组的语义是怎样的?
能否支持这样的声明和赋值:
int array2d[2][];
int array1d[10];
array2d[0] = array1d;

这个没办法假设……只能暂时认为数组声明的时候是多少维在访问的时候也必须用多少维来访问;这样比较简单,哈哈。

6、数组声明的时候,是否允许用数组字面量来初始化?
我这里假设是“是”。

7、数组字面量是否允许空元素?同时,数组可否声明为零长度的?
我这里假设数组字面量不允许内容为空(跟C一样),数组也不可以声明为零长度的。

8、是否有强制类型转换的表达式?(根据原帖,赋值表达式有类型转换语义)
这里假设是“否”。有C-style的强制类型转换的话语法会麻烦不少……

暂时就先这样吧。上面的语法里有经典的dangling-else问题;不过ANTLR默认是匹配优先,能自动消除这个二义性,所以就没做特别的处理。

对这样的一段代码来测试:
// line comment
// declare variables with/without initializers
int i = 1, j;
int x = i + 2 * 3 - 4 / ( 6 - - 7 );
int array[2][3] = {
  { 0, 1, 2 },
  { 3, 4, 6 }
};

/*
  block comment
*/

while (i < 10) i = i + 1;
while (!x > 0 && i < 10) {
  x = x - 1;
  if (i < 5) break;
  else read i;
}

write x - j;


可以生成这样的解析树(parse tree):

(点击放大)
这个测试是通过ANTLRWorks的Interpreter模式来做的。编写本文的时候,最新版是1.2.2。可以在这里获取。

观察此解析树:
输入进来的源码是一维的,而现在生成的解析树已经有了层次结构,变成二维的了。在得到层次结构后,原本用来标识层次结构的标点符号就变得冗余了,但这棵解析树仍然含有这些冗余的标点符号。
另外,可以观察到解析树里的许多子树中每个节点都只有一个子节点;这样的子树的中间节点都可以认为是冗余的。出现这种冗余的原因是:为了描述表达式中运算符的优先级高低,用LL语法需要为每一个优先级都写一条规则,每匹配到一个中间规则解析树里就多一个中间节点。
这些冗余对后续处理来说都是不利的。为了得到更干净更便于处理的表示,我们需要消除冗余,把解析树转换为抽象语法树(abstract syntax tree, AST)。这个时候ANTLR的重写规则(rewrite rule)就非常有用了。下一篇就来看看如何应用重写规则来得到抽象语法树。
分享到:
评论

相关推荐

    一个简单的语言的语法(二):ANTLR的重写规则

    在本主题中,我们将深入探讨ANTLR中的重写规则,特别是在创建一个简单语言上下文中如何运用这些规则。 ANTLR允许开发者定义自己的语法规则,并自动生成解析器和词法分析器。在ANTLR中,重写规则是用来定制输出语法...

    antlr-2.7.7.jar.zip

    1. **定义语法规则**:编写ANTLR语法规则文件(通常扩展名为.g或.gram),定义你想要解析的语言结构。 2. **生成解析器和词法分析器**:ANTLR工具会根据语法规则文件生成相应的解析器和词法分析器类。对于Java,这些...

    java Antlr 4 语法文件合集

    Java Antlr 4 是一个强大的解析工具,用于生成解析器和词法分析器,它使得开发者可以轻松地处理各种语言的语法。Antlr 4 支持多种编程语言,包括 Java,C#,Python,JavaScript 等,而在这个“java Antlr 4 语法文件...

    antlr java语法分析程序

    ANTLR通过提供一个简单的语法描述语言(Grammar)让开发者能方便地定义自己的语言规范,然后自动产生相应的词法分析器(Lexer)和语法分析器(Parser)。 ANTLR的工作流程通常包括以下几个步骤: 1. **定义语法规则...

    ANTLR V3 语法解析工具

    它能够读取一个语言的语法规则定义(通常是以ANTLR特有的语法文件格式,扩展名为.g或.gram),然后生成解析器和词法分析器,这些生成器可以解析符合该语法规则的输入文本。ANTLR V3 版本是ANTLR系列中的一个重要版本...

    使用antlr4实现的CMM语言解释器

    CMM可能是一个自定义的中间语言或者类C的微型语言。解释器的作用是读取CMM源代码,然后逐行或逐表达式地执行,而无需先将其编译成机器码。 ANTLR4的工作原理是通过读取特定的语言语法(通常以EBNF或BNF形式)来生成...

    ANTLR参考手册

    1. **定义语法**:编写ANTLR语法文件(.g4扩展名),定义输入语言的词法规则和语法规则。 2. **生成解析器和词法分析器**:使用ANTLR工具将.g4文件转换为目标语言的解析器和词法分析器源代码。 3. **编译和运行**...

    语法分析程序:通过设计、编制、调试一个典型的语法分析程序

    本主题将深入探讨如何设计、编制和调试一个典型的语法分析程序。 首先,我们需要了解语法分析的目的。语法分析的目标是确保输入的源代码按照预定的语法规则进行组织,这通常通过解析器(parser)来完成。解析器将...

    antlr转语法图工具.rar

    总之,“antlr转语法图工具”提供了一个实用的功能,帮助开发者更好地理解和处理ANTLR4文法,提升语言设计和实现的效率。通过图形化的表示,复杂文法规则变得易于理解,无论是初学者还是经验丰富的开发者,都能从中...

    antlr中文文档预览版

    5. **自定义语法规则(Custom Grammar Definition)**:ANTLR支持用户定义自己的语法规则,通过简单的语法文件(通常为.g4扩展名)来描述语言结构,极大地简化了解析器的开发。 6. **错误处理**:ANTLR提供了强大的...

    Antlr3.Runtime_C#_

    ANTLR(ANother Tool for Language Recognition)是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件。它广泛应用于构建语言、工具和框架。ANTLR可以生成Java、C#、Python、JavaScript等...

    The Definitive ANTLR 4 Reference.pdf_antlr_

    3. **LL(*)和预测LL(*)解析**:ANTLR 4使用预测LL(*)算法,这是一个自适应的左到右解析策略,能够处理许多复杂的上下文无关语法,包括左递归和右递归。 4. **树解析器**:ANTLR 4不仅生成词法分析器和语法解析器,...

    Antlr-Practice:使用ANTLR编写词法分析器和解析器

    ANTLR,全称是ANother Tool for Language Recognition,是一款强大的解析工具,主要用于生成词法分析器(Lexer)和语法解析器(Parser)。这个“Antlr-Practice”项目是针对ANTLR的实际应用,旨在帮助开发者学习如何...

    探索Antlr3.0

    以一个仅能计算两个数相加的简易计算器为例,Antlr3.0的使用流程如下: - **定义语法**:在语法文件中,明确输入语言的结构,如`expr: INT PLUS INT;`。 - **编译语法文件**:使用Antlr的编译工具,将语法文件编译...

    开源项目-antlr-antlr4.zip

    ANTLR(ANother Tool for Language Recognition)是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件。它广泛应用于构建语言、工具和框架。ANTLR4是ANTLR项目的最新版本,提供了许多改进...

    antlr中文济南

    ANTLRWorks 是ANTLR的官方IDE,它提供了一个图形化的界面来设计和调试ANTLR语法。用户可以在ANTLRWorks中直接编辑语法,查看生成的解析树,以及进行错误检测和调试。 在实际项目中,ANTLR生成的类可以与其他编程...

    antlr4-master 源码

    ANTLR4(ANTLR Version 4)是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件。它广泛应用于构建语言、工具和框架,包括SQL解析器、配置文件解析器、DSL(领域特定语言)以及各种编程...

    antlr-python-runtime-3.1.3.zip

    1. **语法定义**:首先,你需要用ANTLR的语法定义语言(通常扩展名为.g或.grammar)来定义你的语法规则。 2. **生成解析器/词法分析器**:ANTLR将这个语法文件转换为解析器和词法分析器的源代码,这些代码可以处理...

Global site tag (gtag.js) - Google Analytics