• Antlr词法分析之技巧——保留空白符


    Antlr是一个功能非常强大的编译器前端工具。

    之前我们都把关注点放在他的语法分析上,其实它在词法分析方面也有很多强大的功能。

    比方我们有一个SQL,但有些子查询没有写库名,我们想给他补充上库名,将SQL重新打印出来。

    这个看似简单,其实也要经历词法分析、语法分析,将所有表名符号识别出来。

    insert overwrite table test_user.user_info    
    select ab.id,ab.name,c.dept from 
    (select a.id,b.name from 
    (select id,name,sex,age from test_user.date_user)a 
    join
    (select id,name from d_user)b 
    on a.id=b.id)ab 
    JOIN 
    (select eid,name,dept from employee)c 
    on ab.id=c.eid

    如果走正常词法分析、语法分析的路子。

    public class InsertDbListenerTest {
        static String sql =
                "insert overwrite table test_user.user_info    \n" +
                        "select ab.id,ab.name,c.dept from \n" +
                        "(select a.id,b.name from \n" +
                        "(select id,name,sex,age from test_user.date_user)a \n" +
                        "join\n" +
                        "(select id,name from d_user)b \n" +
                        "on a.id=b.id)ab \n" +
                        "JOIN \n" +
                        "(select eid,name,dept from employee)c \n" +
                        "on ab.id=c.eid";
        public static void main(String[] args) {
            System.out.println(sql);
            CharStream input = CharStreams.fromString(sql);
            HqlLexer hqlLexer = new HqlLexer(input);
            CommonTokenStream tokenStream = new CommonTokenStream(hqlLexer);
            HqlParser hqlParser = new HqlParser(tokenStream);
            HqlParser.ProgramContext tree = hqlParser.program();
    //
            InsertDbListener listener = new InsertDbListener(tokenStream);
            ParseTreeWalker walker = new ParseTreeWalker();
            walker.walk(listener, tree);
            System.out.println(listener.rewriter.getText());
    
    
        }
    }
    
    
    
    
    public class InsertDbListener extends HqlBaseListener {
        TokenStreamRewriter rewriter;
    
        public InsertDbListener(TokenStream tokens) {
            this.rewriter = new TokenStreamRewriter(tokens);
        }
    
        @Override
        public void enterTable_name(HqlParser.Table_nameContext ctx) {
            String tb="test.";
            if(!ctx.getText().contains(".")){
                rewriter.insertBefore(ctx.start,tb);
            }
        }
    }

    我们启用了一个特别的listener,使用TokenStreamRewriter功能,在每个没有库名的表名前面加上库名,得到的输出结果是

    insertoverwritetabletest_user.user_infoselectab.id,ab.name,c.deptfrom(selecta.id,b.namefrom(selectid,name,sex,agefromtest_user.date_user)ajoin(selectid,namefromtest.d_user)bona.id=b.id)abJOIN(selecteid,name,deptfromtest.employee)conab.id=c.eid

    仔细一看,库名的确是补上了,但是既没有空格,也没有换行,结果简直没法看!

    这是为什么?

    我们知道,一般在词法分析中,空白符会被识别出来,然后丢弃,不会进入语法分析器。

    这个是可以理解的,毕竟空白符跟语法也没有半点关系。

    但有些时候,就比如我们这个需求里,我们需要保留空白符、甚至注释什么的,那该怎么做呢?

    其实非常简单,只需要修改一行语法文件

    将语法文件中的

    WS        : L_BLANK+ ->  skip ;

    修改为

    WS        : L_BLANK+ ->  channel(HIDDEN) ;

    即可。这个改动的意义是将词法分析匹配到的空白符写入一个隐藏通道,不传递给语法文件。但也没有丢弃。

    重新生成antlr的词法分析和语法分析文件,我们自己写的代码一点都不用改。

    再执行一遍,输出结果

    insert overwrite table test_user.user_info    
    select ab.id,ab.name,c.dept from 
    (select a.id,b.name from 
    (select id,name,sex,age from test_user.date_user)a 
    join
    (select id,name from test.d_user)b 
    on a.id=b.id)ab 
    JOIN 
    (select eid,name,dept from test.employee)c 
    on ab.id=c.eid

    完美!库名补充上了。换行符都在,空格也都在,正是我们想要的结果。

  • 相关阅读:
    xml配置文件使用-读取、转换
    .NET 4.0 缓存
    jQuery分页插件pagination.js 笔记
    Spring源码
    Shell编程(字符串篇)
    Linux DNS 相关
    Shell将命令执行结果写入文件
    Linux安装GCC
    Linux网络安全
    产品经理基础
  • 原文地址:https://www.cnblogs.com/wangbin2188/p/16673129.html
Copyright © 2020-2023  润新知