ANTLR:有简单的例子吗? [英] ANTLR: Is there a simple example?

查看:32
本文介绍了ANTLR:有简单的例子吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想开始使用 ANTLR,但在花了几个小时查看 antlr.org<上的示例之后/a> 站点,我仍然无法清楚地了解 Java 过程的语法.

有没有一些简单的例子,比如一个用 ANTLR 实现的四运算计算器,通过解析器定义,一直到 Java 源代码?

注意:此答案适用于 ANTLR3!如果您正在寻找 ANTLR4 示例,那么这个 Q&A 演示了如何使用 ANTLR4 创建简单的表达式解析器和评估器.

<小时>

您首先创建一个语法.下面是一个小语法,可用于评估使用 4 个基本数学运算符构建的表达式:+、-、* 和/.您还可以使用括号对表达式进行分组.

请注意,这个语法只是一个非常基本的语法:它不处理一元运算符(减号:-1+9)或小数,如 0.99(没有前导数字),仅举两个缺点.这只是一个你可以自己处理的例子.

这里是语法文件Exp.g的内容:

grammar Exp;/* 这将是我们解析器的入口点.*/评估: 加法;/* 加法和减法的优先级最低.*/附加费: 乘法( '+' 乘以Exp|'-' 乘以Exp)*;/* 乘法和除法有更高的优先级.*/乘以Exp: atomExp( '*' atomExp|'/' atomExp)*;/* 表达式原子是表达式的最小部分:一个数字.或者当我们遇到括号时,我们正在递归调用规则additionExp".如您所见,atomExp"具有最高优先级.*/原子指数:    数字|'('addExp')';/* 数字:可以是整数值,也可以是十进制值 */数字: ('0'..'9')+ ('.' ('0'..'9')+)?;/* 我们将忽略所有空白字符 */WS: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;};

(解析器规则以小写字母开头,词法规则以大写字母开头)

创建语法后,您需要从中生成解析器和词法分析器.下载 ANTLR jar 并将其存储在与您的语法文件相同的目录中.

在您的 shell/命令提示符下执行以下命令:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g

它应该不会产生任何错误消息,现在应该会生成文件 ExpLexer.javaExpParser.javaExp.tokens.

要查看是否一切正常,请创建此测试类:

import org.antlr.runtime.*;公共类 ANTLRDemo {public static void main(String[] args) 抛出异常 {ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");ExpLexer 词法分析器 = new ExpLexer(in);CommonTokenStream 令牌 = new CommonTokenStream(lexer);ExpParser parser = new ExpParser(tokens);parser.eval();}}

并编译它:

//*nix/MacOSjavac -cp .:antlr-3.2.jar ANTLRDemo.java//窗户javac -cp .;antlr-3.2.jar ANTLRDemo.java

然后运行它:

//*nix/MacOSjava -cp .:antlr-3.2.jar ANTLRDemo//窗户java -cp .;antlr-3.2.jar ANTLRDemo

如果一切顺利,控制台不会打印任何内容.这意味着解析器没有发现任何错误.当你把"12*(5-6)"改成"12*(5-6",然后重新编译运行,打印出来的应该是:

 行 0:-1 不匹配的输入 ''期待')'

好的,现在我们想在语法中添加一些 Java 代码,以便解析器真正做一些有用的事情.添加代码可以通过将 {} 放在语法中并在其中添加一些普通的 Java 代码来完成.

但首先:语法文件中的所有解析器规则都应该返回一个原始双精度值.您可以通过在每个规则后添加 returns [double value] 来实现:

grammar Exp;eval 返回 [双倍值]: 加法;addExp 返回 [double value]: 乘法( '+' 乘以Exp|'-' 乘以Exp)*;//...

不需要解释:每个规则都应该返回一个双精度值.现在要与代码块内部的返回值 double value(不在普通 Java 代码块 {...} 内)交互",您将需要在value前加一个美元符号:

grammar Exp;/* 这将是我们解析器的入口点.*/eval 返回 [双倍值]:addExp {/* 纯代码块!*/System.out.println("value 等于:"+$value);};//...

这是语法,但现在添加了 Java 代码:

grammar Exp;eval 返回 [双倍值]: exp=additionExp {$value = $exp.value;};addExp 返回 [double value]: m1=multiplyExp {$value = $m1.value;}( '+' m2=multiplyExp {$value += $m2.value;}|'-' m2=multiplyExp {$value -= $m2.value;})*;乘法返回 [双倍值]: a1=atomExp {$value = $a1.value;}( '*' a2=atomExp {$value *= $a2.value;}|'/' a2=atomExp {$value/= $a2.value;})*;atomExp 返回 [double value]: n=Number {$value = Double.parseDouble($n.text);}|'(' exp=additionExp ')' {$value = $exp.value;};数字: ('0'..'9')+ ('.' ('0'..'9')+)?;WS: (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;};

并且由于我们的 eval 规则现在返回双精度值,请将您的 ANTLRDemo.java 更改为:

import org.antlr.runtime.*;公共类 ANTLRDemo {public static void main(String[] args) 抛出异常 {ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");ExpLexer 词法分析器 = new ExpLexer(in);CommonTokenStream 令牌 = new CommonTokenStream(lexer);ExpParser parser = new ExpParser(tokens);System.out.println(parser.eval());//打印值}}

再次(重新)根据您的语法(1)生成新的词法分析器和解析器,编译所有类(2)并运行 ANTLRDemo(3):

//*nix/MacOSjava -cp antlr-3.2.jar org.antlr.Tool Exp.g//1javac -cp .:antlr-3.2.jar ANTLRDemo.java//2java -cp .:antlr-3.2.jar ANTLRDemo//3//窗户java -cp antlr-3.2.jar org.antlr.Tool Exp.g//1javac -cp .;antlr-3.2.jar ANTLRDemo.java//2java -cp .;antlr-3.2.jar ANTLRDemo//3

您现在将看到表达式 12*(5-6) 的结果打印到您的控制台!

再说一次:这是一个非常简短的解释.我鼓励您浏览 ANTLR wiki 并阅读一些教程和/或玩一些我刚刚发布的内容.

祝你好运!

这篇文章展示了如何扩展上面的例子这样就可以提供一个 Map 来保存所提供表达式中的变量.

为了使此代码与当前版本的 Antlr(2014 年 6 月)一起使用,我需要进行一些更改.ANTLRStringStream 需要变成ANTLRInputStream,返回值需要从parser.eval() 变成parser.eval().value,我需要删除最后的 WS 子句,因为 $channel 等属性值不再允许出现在词法分析器动作中.>

I'd like to get started with ANTLR, but after spending a few hours reviewing the examples at the antlr.org site, I still can't get a clear understanding of the grammar to Java process.

Is there some simple example, something like a four-operations calculator implemented with ANTLR going through the parser definition and all the way to the Java source code?

解决方案

Note: this answer is for ANTLR3! If you're looking for an ANTLR4 example, then this Q&A demonstrates how to create a simple expression parser, and evaluator using ANTLR4.


You first create a grammar. Below is a small grammar that you can use to evaluate expressions that are built using the 4 basic math operators: +, -, * and /. You can also group expressions using parenthesis.

Note that this grammar is just a very basic one: it does not handle unary operators (the minus in: -1+9) or decimals like .99 (without a leading number), to name just two shortcomings. This is just an example you can work on yourself.

Here's the contents of the grammar file Exp.g:

grammar Exp;

/* This will be the entry point of our parser. */
eval
    :    additionExp
    ;

/* Addition and subtraction have the lowest precedence. */
additionExp
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;

/* Multiplication and division have a higher precedence. */
multiplyExp
    :    atomExp
         ( '*' atomExp 
         | '/' atomExp
         )* 
    ;

/* An expression atom is the smallest part of an expression: a number. Or 
   when we encounter parenthesis, we're making a recursive call back to the
   rule 'additionExp'. As you can see, an 'atomExp' has the highest precedence. */
atomExp
    :    Number
    |    '(' additionExp ')'
    ;

/* A number: can be an integer value, or a decimal value */
Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;

/* We're going to ignore all white space characters */
WS  
    :   (' ' | '\t' | '\r'| '\n') {$channel=HIDDEN;}
    ;

(Parser rules start with a lower case letter, and lexer rules start with a capital letter)

After creating the grammar, you'll want to generate a parser and lexer from it. Download the ANTLR jar and store it in the same directory as your grammar file.

Execute the following command on your shell/command prompt:

java -cp antlr-3.2.jar org.antlr.Tool Exp.g

It should not produce any error message, and the files ExpLexer.java, ExpParser.java and Exp.tokens should now be generated.

To see if it all works properly, create this test class:

import org.antlr.runtime.*;

public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        parser.eval();
    }
}

and compile it:

// *nix/MacOS
javac -cp .:antlr-3.2.jar ANTLRDemo.java

// Windows
javac -cp .;antlr-3.2.jar ANTLRDemo.java

and then run it:

// *nix/MacOS
java -cp .:antlr-3.2.jar ANTLRDemo

// Windows
java -cp .;antlr-3.2.jar ANTLRDemo

If all goes well, nothing is being printed to the console. This means the parser did not find any error. When you change "12*(5-6)" into "12*(5-6" and then recompile and run it, there should be printed the following:

line 0:-1 mismatched input '<EOF>' expecting ')'

Okay, now we want to add a bit of Java code to the grammar so that the parser actually does something useful. Adding code can be done by placing { and } inside your grammar with some plain Java code inside it.

But first: all parser rules in the grammar file should return a primitive double value. You can do that by adding returns [double value] after each rule:

grammar Exp;

eval returns [double value]
    :    additionExp
    ;

additionExp returns [double value]
    :    multiplyExp 
         ( '+' multiplyExp 
         | '-' multiplyExp
         )* 
    ;

// ...

which needs little explanation: every rule is expected to return a double value. Now to "interact" with the return value double value (which is NOT inside a plain Java code block {...}) from inside a code block, you'll need to add a dollar sign in front of value:

grammar Exp;

/* This will be the entry point of our parser. */
eval returns [double value]                                                  
    :    additionExp { /* plain code block! */ System.out.println("value equals: "+$value); }
    ;

// ...

Here's the grammar but now with the Java code added:

grammar Exp;

eval returns [double value]
    :    exp=additionExp {$value = $exp.value;}
    ;

additionExp returns [double value]
    :    m1=multiplyExp       {$value =  $m1.value;} 
         ( '+' m2=multiplyExp {$value += $m2.value;} 
         | '-' m2=multiplyExp {$value -= $m2.value;}
         )* 
    ;

multiplyExp returns [double value]
    :    a1=atomExp       {$value =  $a1.value;}
         ( '*' a2=atomExp {$value *= $a2.value;} 
         | '/' a2=atomExp {$value /= $a2.value;}
         )* 
    ;

atomExp returns [double value]
    :    n=Number                {$value = Double.parseDouble($n.text);}
    |    '(' exp=additionExp ')' {$value = $exp.value;}
    ;

Number
    :    ('0'..'9')+ ('.' ('0'..'9')+)?
    ;

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

and since our eval rule now returns a double, change your ANTLRDemo.java into this:

import org.antlr.runtime.*;

public class ANTLRDemo {
    public static void main(String[] args) throws Exception {
        ANTLRStringStream in = new ANTLRStringStream("12*(5-6)");
        ExpLexer lexer = new ExpLexer(in);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExpParser parser = new ExpParser(tokens);
        System.out.println(parser.eval()); // print the value
    }
}

Again (re) generate a fresh lexer and parser from your grammar (1), compile all classes (2) and run ANTLRDemo (3):

// *nix/MacOS
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .:antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .:antlr-3.2.jar ANTLRDemo            // 3

// Windows
java -cp antlr-3.2.jar org.antlr.Tool Exp.g   // 1
javac -cp .;antlr-3.2.jar ANTLRDemo.java      // 2
java -cp .;antlr-3.2.jar ANTLRDemo            // 3

and you'll now see the outcome of the expression 12*(5-6) printed to your console!

Again: this is a very brief explanation. I encourage you to browse the ANTLR wiki and read some tutorials and/or play a bit with what I just posted.

Good luck!

EDIT:

This post shows how to extend the example above so that a Map<String, Double> can be provided that holds variables in the provided expression.

To get this code working with a current version of Antlr (June 2014) I needed to make a few changes. ANTLRStringStream needed to become ANTLRInputStream, the returned value needed to change from parser.eval() to parser.eval().value, and I needed to remove the WS clause at the end, because attribute values such as $channel are no longer allowed to appear in lexer actions.

这篇关于ANTLR:有简单的例子吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆