过去几个小时我一直在网上搜索,试图学习一个使用ANTLR的简单例子。但是我很难理解这些例子。是否有任何正文可以在Java中输出:
如果我输入的话
printf("Hello World");
输出应为:
Hello World
如果我的输入是
inx = 1;
它应该给出错误信息。
我正在尝试使用java创建一个c ++编译器(从词汇开始直到语义部分),我真的想知道我应该做什么。
答案 0 :(得分:5)
这是一种几乎可以满足你想要的语法:
grammar PrintLang;
sentence
: statement
;
statement
: functionCall '(' argument ')' ';'
{
if ($functionCall.funName.equals("printf")) {
System.out.println($argument.arg);
}
}
;
functionCall returns [String funName]
: ID
{ $funName = $ID.text; }
;
argument returns [String arg]
: STRING
{ $arg = $STRING.text; }
;
ID : ('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
;
WS : ( ' '
| '\t'
| '\r'
| '\n'
) {$channel=HIDDEN;}
;
STRING
: '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
;
fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;
fragment
ESC_SEQ
: '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
| UNICODE_ESC
| OCTAL_ESC
;
fragment
OCTAL_ESC
: '\\' ('0'..'3') ('0'..'7') ('0'..'7')
| '\\' ('0'..'7') ('0'..'7')
| '\\' ('0'..'7')
;
fragment
UNICODE_ESC
: '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
;
我在AntlrWorks中生成了这个。所有令牌规则都是为我生成的。
这是测试它的java文件。
import org.antlr.runtime.*;
public class PrintIt {
public static void main(String args[]) {
String inputString = "printf(\"HelloWorld\");";
// Create an input character stream from standard in
ANTLRStringStream input = new ANTLRStringStream(inputString);
// Create an ExprLexer that feeds from that stream
PrintLangLexer lexer = new PrintLangLexer(input);
// Create a stream of tokens fed by the lexer
CommonTokenStream tokens = new CommonTokenStream(lexer);
// Create a parser that feeds off the token stream
PrintLangParser plParser = new PrintLangParser(tokens);
try {
plParser.sentence();
} catch (Exception e) {
e.printStackTrace();
}
}
}
你会注意到这个java代码几乎是来自Antlr网站示例的逐字复制/粘贴(我不相信我甚至改变了评论,这就是为什么评论引用标准,但代码实际使用一个字符串)。这是我过去使用的命令行。
bash$ java -cp ./antlr-3.4-complete.jar org.antlr.Tool PrintLang.g
bash$ javac -cp ./:./antlr-3.4-complete.jar PrintIt.java
bash$ java -cp antlr-3.4-complete.jar:. PrintIt
"HelloWorld"
糟糕,我忘记了我想要打印的字符串不是匹配的标记(“HelloWorld”,包括引号),它是引号中的字符串。
另外,您会注意到我将printf的查找硬编码为字符串比较。实际上,您需要一个包含在给定范围内可访问的符号的环境(相关,请参阅antlr的“范围”构造。更难,但有时有用:创建一个传递给每个解析规则的环境)。
最重要的是:通过搜索SO寻找更多的问题来找到Bart Kiers的答案。他发布了优秀的示例。
答案 1 :(得分:2)
从ANTLR这里是解析(和评估)表达式的简单例子。
grammar Expr;
@header {
package test;
import java.util.HashMap;
}
@lexer::header {package test;}
@members {
/** Map variable name to Integer object holding value */
HashMap memory = new HashMap();
}
prog: stat+ ;
stat: expr NEWLINE {System.out.println($expr.value);}
| ID '=' expr NEWLINE
{memory.put($ID.text, new Integer($expr.value));}
| NEWLINE
;
expr returns [int value]
: e=multExpr {$value = $e.value;}
( '+' e=multExpr {$value += $e.value;}
| '-' e=multExpr {$value -= $e.value;}
)*
;
multExpr returns [int value]
: e=atom {$value = $e.value;} ('*' e=atom {$value *= $e.value;})*
;
atom returns [int value]
: INT {$value = Integer.parseInt($INT.text);}
| ID
{
Integer v = (Integer)memory.get($ID.text);
if ( v!=null ) $value = v.intValue();
else System.err.println("undefined variable "+$ID.text);
}
| '(' e=expr ')' {$value = $e.value;}
;
ID : ('a'..'z'|'A'..'Z')+ ;
INT : '0'..'9'+ ;
NEWLINE:'\r'? '\n' ;
WS : (' '|'\t')+ {skip();} ;
但正如我在评论中提到的,C ++很难正确解析。有许多含糊之处,需要*前瞻(ANTLR确实提供)。所以以任何有效的形式这样做是很复杂的。这就是为什么我建议实现像PL/0这样的东西,它是为学生编写的第一个编译器而设计的。 Tiny BASIC也是一个好的开始。通过执行recursive descent,可以在不使用ANTLR等工具的情况下实现这两个功能。我已经在1000行以下实现了(分别用C ++和C#)。
ANTLR虽然是一个很棒的工具,但是一旦你的脑袋绕着递归下降,你可能想要升级到更强大的解析器。我推荐Terrence Parr的书籍ANTLR Reference和Language Implementation Patterns。 ANTLR书将告诉你想要了解的关于ANTLR的一切(加上一些)。第二本书将教你所有解析器和编译器,从递归下降到黑魔法回溯。
可以找到来自类似SO问题的更多资源here。如果您使用的是Lisp或Scheme,则可以查看JScheme,它是用Java编写的(我相信不到1000行)。