我有一项任务是使用JavaCC为讲师提供的语言制作一个带有语义分析的自上而下的解析器。我已经写出了生产规则,没有错误。 我完全坚持如何将JJTree用于我的代码,而我在互联网上搜索教程的时间并没有让我任何地方。 只是想知道是否有人可以抽出时间来解释如何在代码中实现JJTree? 或者,如果有一个隐藏的分步教程,那将是一个很好的帮助!
以下是我的一些生产规则,以防他们提供帮助。 提前谢谢!
void program() : {}
{
(decl())* (function())* main_prog()
}
void decl() #void : {}
{
(
var_decl() | const_decl()
)
}
void var_decl() #void : {}
{
<VAR> ident_list() <COLON> type()
(<COMMA> ident_list() <COLON> type())* <SEMIC>
}
void const_decl() #void : {}
{
<CONSTANT> identifier() <COLON> type() <EQUAL> expression()
( <COMMA> identifier() <COLON> type() <EQUAL > expression())* <SEMIC>
}
void function() #void : {}
{
type() identifier() <LBR> param_list() <RBR>
<CBL>
(decl())*
(statement() <SEMIC> )*
returnRule() (expression() | {} )<SEMIC>
<CBR>
}
答案 0 :(得分:40)
使用JavaCC创建AST看起来很像创建“普通”解析器(在jj
文件中定义)。如果你已经有了一个有效的语法,那就相当容易了:)
以下是创建AST所需的步骤:
jj
语法文件重命名为jjt
jjtree
语法上调用jjt
,这将为您生成jj
个文件javacc
语法jj
java
源文件这是一个快速的分步教程,假设您正在使用MacOS或* nix,将javacc.jar
文件放在与语法文件相同的目录中,并java
和{ {1}}在您系统的路径中:
假设您的javac
语法文件名为jj
,请将其重命名为:
TestParser.jj
现在是棘手的部分:装饰你的语法,以便创建正确的AST结构。您装饰 AST(或节点或生产规则(完全相同)),方法是添加mv TestParser.jj TestParser.jjt
后跟一个标识符(在#
之前)。在您的原始问题中,您在不同的制作中有很多:
,这意味着您为不同的制作规则创建了相同类型的AST:这不是您想要的。
如果您没有装饰您的作品,则将作品的名称用作节点的类型(因此,您可以删除#void
):
#void
现在规则只返回规则void decl() :
{}
{
var_decl()
| const_decl()
}
或var_decl()
返回的任何AST。
现在让我们看一下(简化的)const_decl()
规则:
var_decl
我用void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void id() #ID :
{}
{
<ID>
}
void expr() #EXPR :
{}
{
<ID>
}
类型装饰。现在这意味着此规则将返回以下树结构:
#VAR
如您所见,终端从AST中被丢弃!这也意味着 VAR
/ | \
/ | \
ID ID EXPR
和id
规则会松开其expr
终端匹配的文本。当然,这不是你想要的。对于需要使内部文本与终端匹配的规则,您需要将树的<ID>
显式设置为匹配终端的.value
:
.image
使输入void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
看起来像这样:
"var x : int = i;"
这是为AST创建适当结构的方法。下面是一个小语法,它是你自己语法的一个非常简单的版本,包括一个小的 VAR
|
.---+------.
/ | \
/ | \
ID["x"] ID["int"] EXPR["i"]
方法来测试它:
main
让// TestParser.jjt
PARSER_BEGIN(TestParser)
public class TestParser {
public static void main(String[] args) throws ParseException {
TestParser parser = new TestParser(new java.io.StringReader(args[0]));
SimpleNode root = parser.program();
root.dump("");
}
}
PARSER_END(TestParser)
TOKEN :
{
< OPAR : "(" >
| < CPAR : ")" >
| < OBR : "{" >
| < CBR : "}" >
| < COL : ":" >
| < SCOL : ";" >
| < COMMA : "," >
| < VAR : "var" >
| < EQ : "=" >
| < CONST : "const" >
| < ID : ("_" | <LETTER>) ("_" | <ALPHANUM>)* >
}
TOKEN :
{
< #DIGIT : ["0"-"9"] >
| < #LETTER : ["a"-"z","A"-"Z"] >
| < #ALPHANUM : <LETTER> | <DIGIT> >
}
SKIP : { " " | "\t" | "\r" | "\n" }
SimpleNode program() #PROGRAM :
{}
{
(decl())* (function())* <EOF> {return jjtThis;}
}
void decl() :
{}
{
var_decl()
| const_decl()
}
void var_decl() #VAR :
{}
{
<VAR> id() <COL> id() <EQ> expr() <SCOL>
}
void const_decl() #CONST :
{}
{
<CONST> id() <COL> id() <EQ> expr() <SCOL>
}
void function() #FUNCTION :
{}
{
type() id() <OPAR> params() <CPAR> <OBR> /* ... */ <CBR>
}
void type() #TYPE :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void id() #ID :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void params() #PARAMS :
{}
{
(param() (<COMMA> param())*)?
}
void param() #PARAM :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
void expr() #EXPR :
{Token t;}
{
t=<ID> {jjtThis.value = t.image;}
}
类(包含在jjtree
中)为您创建一个javacc.jar
文件:
jj
上一步创建了文件java -cp javacc.jar jjtree TestParser.jjt
(如果一切正常)。让TestParser.jj
(也出现在javacc
中)处理它:
javacc.jar
要编译所有源文件,请执行以下操作:
java -cp javacc.jar javacc TestParser.jj
(在Windows上,执行:javac -cp .:javacc.jar *.java
)
真相的时刻到了:让我们看看一切是否真的有效!让解析器处理输入:
javac -cp .;javacc.jar *.java
执行以下操作:
var n : int = I;
const x : bool = B;
double f(a,b,c)
{
}
您应该会在控制台上看到以下内容:
PROGRAM decl VAR ID ID EXPR decl CONST ID ID EXPR FUNCTION TYPE ID PARAMS PARAM PARAM PARAM
请注意,您没有看到java -cp . TestParser "var n : int = I; const x : bool = B; double f(a,b,c) { }"
匹配的文字,但请相信我,他们就在那里。方法ID
根本没有显示它。
HTH
对于包含表达式的工作语法,您可以查看我的以下表达式求值程序:https://github.com/bkiers/Curta(语法在dump()
中)。您可能希望了解如何在二进制表达式的情况下创建根节点。
答案 1 :(得分:1)