我有一个已编译的语法,我想用它将输入序列转换为XML。请注意,在我的情况下,我有一个非常大的语法,有很多规则,我想避免覆盖我的代码中的每个语法规则。
我将用一个例子来避免混淆。让我们有以下语法
grammar expr;
prog: stat+ ;
stat: expr NEWLINE
| ID '=' expr NEWLINE
| NEWLINE
;
expr: expr ('*'|'/') expr
| INT
| ID
| '(' expr ')'
;
ID : [a-zA-Z]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace
输入序列
A = 10
B = A * A
预期输出
<prog>
<stat>
A =
<expr> 10
</expr>
\r\n
</stat>
<stat>
B =
<expr>
<expr>A</expr>
*
<expr> A</expr>
</expr>
\r\n
</stat>
</prog>
对应于解析树
目前我使用的方法是创建ParseTree
并使用toStringTree
方法生成以下字符串
(prog (stat A = (expr 10) \r\n) (stat B = (expr (expr A) * (expr A)) \r\n))
我随后将其转换为上面显示的XML(我使用简单的通用代码来处理任何语法)。我发现这种方法是假的。没有toStringTree
可以解决它吗?我想避免覆盖访问者中的每个语法规则。 (我有数百个)。
编辑
我基本上需要某种通用的ParseTree序列化为XML格式。主要目标是我不必为每个规则在Java中编写特殊的序列化方法。
答案 0 :(得分:3)
您可以使用ANTLR4的访问者功能。根据您使用的工具,您可能需要在生成类时添加-visitor
command line parameter。
为了更好地工作,我添加了一些labels to your parser rules:
prog
: stat+ EOF
;
stat
: expr NEWLINE #exprStat
| ID '=' expr NEWLINE #assignStat
| NEWLINE #emptyStat
;
expr
: lhs=expr op=('*'|'/') rhs=expr #multExpr
| INT #intExpr
| ID #idExpr
| '(' expr ')' #nestedExpr
;
您的访问者可能如下所示:
public class XmlVisitor extends exprBaseVisitor<String> {
@Override
public String visitProg(exprParser.ProgContext ctx) {
StringBuilder builder = new StringBuilder("<prog>");
for (exprParser.StatContext stat : ctx.stat()) {
builder.append(super.visit(stat));
}
return builder.append("</prog>").toString();
}
@Override
public String visitExprStat(exprParser.ExprStatContext ctx) {
return "expr";
}
@Override
public String visitAssignStat(exprParser.AssignStatContext ctx) {
return "<stat>" + ctx.ID() + " = " + super.visit(ctx.expr()) + "\\r\\n</stat>";
}
@Override
public String visitEmptyStat(exprParser.EmptyStatContext ctx) {
return "\\r\\n";
}
@Override
public String visitMultExpr(exprParser.MultExprContext ctx) {
return "<expr>" + super.visit(ctx.lhs) + ctx.op.getText() + super.visit(ctx.rhs) + "</expr>";
}
@Override
public String visitIntExpr(exprParser.IntExprContext ctx) {
return "<expr>" + ctx.INT().getText() + "</expr>";
}
@Override
public String visitIdExpr(exprParser.IdExprContext ctx) {
return "<expr>" + ctx.ID().getText() + "</expr>";
}
@Override
public String visitNestedExpr(exprParser.NestedExprContext ctx) {
return "<expr>" + super.visit(ctx.expr()) + "</expr>";
}
}
要测试此访问者,请运行以下代码:
String source = "A = 10\nB = A * A\n";
exprLexer lexer = new exprLexer(CharStreams.fromString(source));
exprParser parser = new exprParser(new CommonTokenStream(lexer));
ParseTree tree = parser.prog();
String xml = new XmlVisitor().visit(tree);
System.out.println(xml);
将打印:
<prog><stat>A = <expr>10</expr>\r\n</stat><stat>B = <expr><expr>A</expr>*<expr>A</expr></expr>\r\n</stat></prog>
答案 1 :(得分:1)
这种方法可能适合您的需求。为了便于阅读,我用额外的标签t
包裹终端符号,同时跳过带有空格的那些符号。然而,如果需要,调整输出应该不是一个大问题。
final exprLexer lexer = new exprLexer(CharStreams.fromString("A=10\nB = A * A\n"));
final CommonTokenStream tokens = new CommonTokenStream(lexer);
final exprParser parser = new exprParser(tokens);
final ParseTree tree = parser.prog();
ParseTreeWalker.DEFAULT.walk(new exprBaseListener()
{
final String INDENT = " ";
int level = 0;
@Override
public void enterEveryRule(final ParserRuleContext ctx)
{
System.out.printf("%s<%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]);
++level;
super.enterEveryRule(ctx);
}
@Override
public void exitEveryRule(final ParserRuleContext ctx)
{
--level;
System.out.printf("%s</%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]);
super.exitEveryRule(ctx);
}
@Override
public void visitTerminal(final TerminalNode node)
{
final String value = node.getText();
if (!value.matches("\\s+"))
{
System.out.printf("%s<t>%s</t>%n", indent(), node.getText());
}
super.visitTerminal(node);
}
private String indent()
{
return String.join("", Collections.nCopies(level, INDENT));
}
}, tree);
答案 2 :(得分:0)
我创建了一个ANTLR语法,它读取ParseTree.toStringTree
方法生成的LISP样式树。项目可访问here。它有以下几部分
<强>语法强>
grammar str;
expr:
STRING expr # exprString
| LR_BRACKET expr RR_BRACKET expr # exprParenthesis
| LR_STRING_BRACKET expr RR_BRACKET expr # exprRule
| <EOF> # exprEnd
| EOF_MARK # exprEOF
| # exprEpsilon
;
EOF_MARK: '<EOF>' ;
LR_STRING_BRACKET: '(' ~[ ()]+;
LR_BRACKET: '(';
RR_BRACKET: ')';
STRING: ~[ ()]+;
SPACE: [ \t\r\n]+ -> skip; // toss out whitespace
<强> strXMLVisitor.java 强>
public class strXMLVisitor extends strBaseVisitor<String> {
@Override
public String visitExprString(strParser.ExprStringContext ctx)
{
return ctx.STRING().getText() + super.visit(ctx.expr());
}
@Override
public String visitExprParenthesis(strParser.ExprParenthesisContext ctx) {
return "(" + super.visit(ctx.expr(0)) + ")" + super.visit(ctx.expr(1));
}
@Override
public String visitExprRule(strParser.ExprRuleContext ctx) {
String value = ctx.LR_STRING_BRACKET().getText().substring(1);
return "<" + value + ">" + super.visit(ctx.expr(0)) + "</" + value + ">" + super.visit(ctx.expr(1));
}
@Override
public String visitExprEnd(strParser.ExprEndContext ctx) {
return "";
}
@Override
public String visitExprEOF(strParser.ExprEOFContext ctx) {
return "";
}
@Override
public String visitExprEpsilon(strParser.ExprEpsilonContext ctx) {
return "";
}
}
<强> main.java 强>
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
public class main {
public static void main(String[] args) throws Exception {
// create a CharStream that reads from standard input
ANTLRInputStream input = new ANTLRInputStream(System.in);
// create a lexer that feeds off of input CharStream
strLexer lexer = new strLexer(input);
// create a buffer of tokens pulled from the lexer
CommonTokenStream tokens = new CommonTokenStream(lexer);
// create a parser that feeds off the tokens buffer
strParser parser = new strParser(tokens);
ParseTree tree = parser.expr(); // begin parsing at init rule
String xml = "<?xml version=\"1.0\"?>" + new strXMLVisitor().visit(tree);
System.out.println(xml);
}
}
一旦你准备好antlr4(以及CLASSPATH中的引用),你可以使用以下命令来运行它:
antlr4 -visitor str.g4
javac *.java
java main < file
文件必须包含LISP树格式的输入,结果是标准输出上的XML。