ANTLR:采用特定的方式解耦

采用特定的方式解耦

语法树解析出来后,还需要进行动作执行,而执行动作的这个过程,则是Listener去干的

Listener和Visitor模式

原始语法

grammar PropertyFile;
file : {«start file»} prop+ {«finish file»} ;
prop : ID '=' STRING '\n' {«process property»} ;
ID : [a-z]+ ;
STRING : '"' .*? '"' ;

但是这样的重用性就很低,稍作修改

grammar PropertyFile;
@members {
    void startFile() { } // blank implementations
    void finishFile() { }
    void defineProperty(Token name, Token value) { }
}
file : {startFile();} prop+ {finishFile();} ;
prop : ID '=' STRING '\n' {defineProperty($ID, $STRING)} ; ID : [a-z]+ ;
STRING : '"' .*? '"' ;
class PropertyFilePrinter extends PropertyFileParser
{
  void defineProperty(Token name, Token value) {
      System.out.println(name.getText()+"="+value.getText());
  }
}

//所以在启动的时候,使用的类就要用我们自己的了

PropertyFileLexer lexer = new PropertyFileLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
PropertyFilePrinter parser = new PropertyFilePrinter(tokens);
parser.file(); // launch our special version of the parser

优化的第一种方法是侦听器方法,因为Listener是在Parse完成后才执行的,里面的转换动作都是自动执行的

public static class PropertyFileLoader extends PropertyFileBaseListener {
  Map<String,String> props = new OrderedHashMap<String, String>();
  public void exitProp(PropertyFileParser.PropContext ctx) {
          String id = ctx.ID().getText(); // prop : ID '=' STRING '\n' ;
          String value = ctx.STRING().getText();
          props.put(id, value);
        }
}

...

//然后就可以walk了

ByteArrayInputStream is = new ByteArrayInputStream("a=b\nc=d\n".getBytes());
ANTLRInputStream input = new ANTLRInputStream(is);
ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
fileLexer lexer = new fileLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
fileParser fileParser = new fileParser(tokens);
SampleLoader sampleLoader = new SampleLoader();
parseTreeWalker.walk(sampleLoader, fileParser.file());
System.out.println(sampleLoader.props);

而且有个好处是,这里的实例是同一个

另外一种方式是访问者模式


//进行访问

ByteArrayInputStream is = new ByteArrayInputStream("a=b\nc=d\n".getBytes());
ANTLRInputStream input = new ANTLRInputStream(is);
fileLexer lexer = new fileLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
fileParser fileParser = new fileParser(tokens);
SampleVisitor sampleLoader = new SampleVisitor();
sampleLoader.visit(fileParser.file());
System.out.println(sampleLoader.props);

在语法中使用标签

例如有如下语法

grammar Expr;
s : e ;
e : e op=MULT e    // MULT is '*'
  | e op=ADD e     // ADD is '+'
  | INT
  ;

MULT: '*' ;
ADD : '+' ;
INT : [0-9]+ ;
WS : [ \t\n]+ -> skip ;

这个时候,操作起来就好麻烦了,因为exitE会被连续调用多次,以1+2为例,1会被调用一次,2会被调用一次 1+2完成后会被调用一次

public void exitE(ExprParser.EContext ctx) {
    if ( ctx.getChildCount()==3 ) { // operations have 3 children
        int left = values.get(ctx.e(0));
        int right = values.get(ctx.e(1));
        if ( ctx.op.getType()==ExprParser.MULT ) {
            values.put(ctx, left * right);
        }
        else {
            values.put(ctx, left + right);
        }
    }
    else {
      values.put(ctx, values.get(ctx.getChild(0))); // an INT
    }
}

所以我们需要在3个参数都凑齐的时候才做操作,所以,ANTLR提供了一个方便的方法,可以标签化操作,把 语法修改如下

grammar LExpr;

s : e ;

e : e MULT e 		# Mult
  | e ADD e 		# Add
  | INT        	# Int
  ;

MULT: '*' ;
ADD : '+' ;
INT : [0-9]+ ;
WS : [ \t\n]+ -> skip ;

这个时候使用语法树查看器会看到语法生成树上面多了Mult和ADD这样的标签

public class ExprListener extends ExprBaseListener {

    @Override
    public void exitMult(ExprParser.MultContext ctx) {
        super.exitMult(ctx);
    }

    @Override
    public void exitAdd(ExprParser.AddContext ctx) {
        super.exitAdd(ctx);
    }
}

通过使用标签化语法,可以使得逻辑更加清晰

在事件中共享信息

采用Visitor模式

 ByteArrayInputStream is = new ByteArrayInputStream("1+2*3".getBytes());
 ANTLRInputStream input = new ANTLRInputStream(is);
 ExprLexer lexer = new ExprLexer(input);
 CommonTokenStream tokens = new CommonTokenStream(lexer);
 ExprParser exprParser = new ExprParser(tokens);
 ExprResultVisitor exprVisitor = new ExprResultVisitor();
 int result = exprVisitor.visit(exprParser.s());
 System.out.println(result);

visitor代码如下

public class ExprResultVisitor extends ExprBaseVisitor<Integer> {
    @Override
    public Integer visitAdd(ExprParser.AddContext ctx) {
        return visit(ctx.e(0)) + visit(ctx.e(1));
    }

    @Override
    public Integer visitInt(ExprParser.IntContext ctx) {
        return Integer.valueOf(ctx.INT().getText());
    }

    @Override
    public Integer visitMult(ExprParser.MultContext ctx) {
        return visit(ctx.e(0)) * visit(ctx.e(1));
    }
}

每当触发visit的时候,就会根据情况往下调用一次,多层次的调用把结果回到顶层,然后就获取到最终的结果了,当我们walk完一次就要回结果的时候,这个方式挺适合的

使用堆栈来模拟值返回

Listener模式下是没有返回值的,这个时候我们可以使用栈的方式来模拟值的返回

        ByteArrayInputStream is = new ByteArrayInputStream("1+2*3".getBytes());
        ANTLRInputStream input = new ANTLRInputStream(is);
        ExprLexer lexer = new ExprLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExprParser exprParser = new ExprParser(tokens);
        ExprListener exprListener = new ExprListener();
        ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
        parseTreeWalker.walk(exprListener, exprParser.s());
        System.out.println(exprListener.stack.pop());
  

Listener采用stack来做数据结构

public class ExprListener extends ExprBaseListener {

    Stack<Integer> stack = new Stack<Integer>();

    public void exitMult(ExprParser.MultContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push(left * right);
    }

    public void exitAdd(ExprParser.AddContext ctx) {
        int right = stack.pop();
        int left = stack.pop();
        stack.push(left + right);
    }

    public void exitInt(ExprParser.IntContext ctx) {
        stack.push(Integer.valueOf(ctx.INT().getText()));
    }
}

麻烦是麻烦了一些,不过Listener返回多个值这样的情况还是很直观的

解析树上增加变量

这种做法是在解析数运算的时候,每次运算完,都把自己这个节点算出来的放回自己的节点上,这样,下一个节点计算的时候,看到自己这个节点就已经是一个运算完的结果,直接计算就好了

        ByteArrayInputStream is = new ByteArrayInputStream("1+2*3".getBytes());
        ANTLRInputStream input = new ANTLRInputStream(is);
        ExprLexer lexer = new ExprLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);
        ExprParser exprParser = new ExprParser(tokens);
        ExprListener exprListener = new ExprListener();
        ParseTreeWalker parseTreeWalker = new ParseTreeWalker();
        ExprParser.SContext tree = exprParser.s();
        parseTreeWalker.walk(exprListener, tree);
        System.out.println(exprListener.values.get(tree));

在上下文中添加结果

public class ExprListener extends ExprBaseListener {
    ParseTreeProperty<Integer> values = new ParseTreeProperty<>();

    public void exitS(ExprParser.SContext ctx) {
        values.put(ctx, values.get(ctx.e()));
    }

    public void exitMult(ExprParser.MultContext ctx) {
        int left = values.get(ctx.e(0));
        int right = values.get(ctx.e(1));
        values.put(ctx, left * right);
    }

    public void exitAdd(ExprParser.AddContext ctx) {
        int left = values.get(ctx.e(0));
        int right = values.get(ctx.e(1));
        values.put(ctx, left + right);
    }

    public void exitInt(ExprParser.IntContext ctx) {
        values.put(ctx, Integer.valueOf(ctx.INT().getText()));
    }
}

假如要用HashMap的话,要用IdentityHashMap而不是普通的HashMap

模式优缺点
返回值模式易读
模拟堆栈容易出错,不过多返回值的时候易读
解析树标记优先选择这种,比较好操作,也不容易出错

转载于:https://my.oschina.net/xiaomaijiang/blog/1503541

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值