重写输入流

现在准备要构建一个工具,用来把前面idata.txt里的数据按group分行显示,就像这样:

  1. 2 9 10
  2. 3 1 2 3

我们可以借助语法分析树的Listener机制来对词法分析结束后生成的记号流进行改写,我们不需要实现每一个Listener接口方法,只需要在捕获到group的时候把换行符插到它末尾就行。实现改写的代码如下所示:

  1. import org.antlr.v4.runtime.TokenStream;
  2. import org.antlr.v4.runtime.TokenStreamRewriter;
  3. public class RewriteListener extends IDataBaseListener {
  4. TokenStreamRewriter rewriter;
  5. public RewriteListener(TokenStream tokens) {
  6. rewriter = new TokenStreamRewriter(tokens);
  7. }
  8. @Override
  9. public void enterGroup(IDataParser.GroupContext ctx) {
  10. rewriter.insertAfter(ctx.stop, '\n');
  11. }
  12. }

接着就是写一个小程序来调用我们上面的改写类:

  1. import org.antlr.v4.runtime.*;
  2. import org.antlr.v4.runtime.tree.*;
  3. import java.io.FileInputStream;
  4. import java.io.InputStream;
  5. public class IData {
  6. public static void main(String[] args) throws Exception {
  7. InputStream is = args.length > 0 ? new FileInputStream(args[0]) : System.in;
  8. ANTLRInputStream input = new ANTLRInputStream(is);
  9. IDataLexer lexer = new IDataLexer(input);
  10. CommonTokenStream tokens = new CommonTokenStream(lexer);
  11. IDataParser parser = new IDataParser(tokens);
  12. ParseTree tree = parser.file();
  13. RewriteListener listener = new RewriteListener(tokens);
  14. System.out.println("Before Rewriting");
  15. System.out.println(listener.rewriter.getText());
  16. ParseTreeWalker walker = new ParseTreeWalker();
  17. walker.walk(listener, tree);
  18. System.out.println("After Rewriting");
  19. System.out.println(listener.rewriter.getText());
  20. }
  21. }

这里的关键是TokenStreamRewriter对象知道如何在不修改流的情况下提供一个记号流的修改过的视图。它把所有的操作方法当作指令并把它们排进队列,等到在遍历记号流把它作为文本渲染回去的时候延迟执行。每次我们调用getText()时rewriter就会执行那些指令。

最后就是构建和测试应用:

  1. antlr IData.g
  2. compile *.java
  3. run IData idata.txt

以下是输出结果:

  1. Before Rewriting
  2. 29103123
  3. After Rewriting
  4. 2910
  5. 3123

仅用几行代码,我们就能够没有任何烦恼地对某些内容做轻微的调整。这种策略对于源代码检测或重构这类一般性的问题是非常有效的。TokenStreamRewriter是一个非常强大且有效的操作记号流的方法。