antlr4-添加标签(lable)(五)

粒度问题

由于以前生成的方法仅仅能够在规则上产生,当我们想要更小的粒度时,针对每个规则进行更小的粒度控制时,我们就显得力不从心了。

这样,就产生了lable…

无标签情况下

下面是一个计算器。

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 ;

public static class Evaluator extends ExprBaseListener {
    Stack<Integer> stack = new Stack<Integer>();
    public void exitE(ExprParser.EContext ctx) {
        if ( ctx.getChildCount()==3 ) { // operations have 3 children
            int right = stack.pop();
            int left = stack.pop();
            if ( ctx.op.getType()==ExprParser.MULT ) {
                stack.push( left * right );
            }
            else {
                stack.push( left + right ); // must be add
            }
        }
    }

    public void visitTerminal(TerminalNode node) {
        Token symbol = node.getSymbol();
        if ( symbol.getType()==ExprParser.INT ) {
            stack.push( Integer.valueOf(symbol.getText()) );
        }
    }
}

从上面看出,原始的方式下,我们不得不对所有的操作符进行判断,即op=MULT和op=Add。

有标签情况下

grammar LExpr;

s : e ;

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

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

注意不同点,我们使用#号来表示标签。这样,antlr就会对每个标签生成一个方法和特定的上下文对象。

antlr4-遍历器例子(四)

在上文中,我们介绍了antlr4的2中遍历方式。下面,我们将对着2中遍历方式进行更为实际的例子,以确定他们各自的使用场合。

使用Parse-Tree Listeners

为了构建一个不混淆应用代码和语法的应用,关键点是解析器创建一个解析树然后遍历它,在遍历的过程中触发应用代码。我们可以使用我们最喜爱的技术或者antlr生成的树遍历机制来遍历。在这一小节中,我们将用antlr内置的ParseTreeWalker构建一个基于监听版本的属性文件应用。

//语法
grammar PropertyFile;
file : prop+ ;
prop : ID '=' STRING '\n' ;
ID   : [a-z]+ ;
STRING : '"' .*? '"' ;

//文件
user="parrt"
machine="maniac"

解析树

PropertyFileListener

PropertyFileBaseListener

public static class PropertyFileLoader extends PropertyFileBaseListener {
    Map<String,String> props = new HashMap<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);
    }
}

下面的类的依赖图,我就不解释了:

最后直接看结果吧,so easy:

public static void main(String[] args) throws Exception {
    String inputFile = "E:\\code\\antlr\\antlr4-example\\part2\\listeners\\src\\main\\resources\\t.properties";
    InputStream is = new FileInputStream(inputFile);
    ANTLRInputStream input = new ANTLRInputStream(is);
    PropertyFileLexer lexer = new PropertyFileLexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    PropertyFileParser parser = new PropertyFileParser(tokens);
    ParseTree tree = parser.file();

    // create a standard ANTLR parse tree walker
    ParseTreeWalker walker = new ParseTreeWalker();
    // create listener then feed to walker
    PropertyFileLoader loader = new PropertyFileLoader();
    walker.walk(loader, tree);        // walk parse tree
    System.out.println(loader.props); // print results
    //print {machine="maniac", user="parrt"}
}

使用Parse-Tree Visitors

当使用访问器时,我们仅仅是实现Visitor的代码即可。所以,无图无真相,无代码无…直接上代码了…

public static class PropertyFileVisitor extends
        PropertyFileBaseVisitor<Void>
{
    Map<String,String> props = new HashMap<String, String>();
    public Void visitProp(PropertyFileParser.PropContext ctx) {
        String id = ctx.ID().getText(); // prop : ID '=' STRING '\n' ;
        String value = ctx.STRING().getText();
        props.put(id, value);
        return null; // Java says must return something even when Void
    }
}

在这个例子中,prop节点没有子节点,所以,visitProp()方法不需要调用visit()方法,在下一节中我们将查看更通用一点的例子。

antlr4-遍历机制概念(三)

antlr4如何遍历树?

antlr4提供了2种树遍历方式。在默认情况下,antlr生成了一个解析树监听接口,它用内置的树遍历器响应事件触发。这个监听器正如SAX文档处理xml解析对象一样。SAX监听器接收通知事件,例如startDocument()和endDocumnet()。他们在监听器中仅仅被回调。

解析树监听器(Parse-Tree Listeners)

为了在遍历树的时候触发对监听器的调用,antlr的运行时包提供类ParseTreeWalker。

antlr生成了一个ParerTreeListener的子类,这个类会对每个语法规则指定一个enter和exit方法。当遍历器遇到节点规则assign时,会触发enterAssign()方法然后传递给他解析树节点的AssignConext。在遍历器访问了assign的所有子节点后,会触发exitAssign()方法。下面的树状图显示了ParseTreeWalker执行了深度优先的遍历:

接下来的图片显示了一个更完整的ParseTreeWalker调用:

解析树访问器(Parse-Tree Visitors)

有这样一种情形,就是我们想自己控制遍历方式,明确的调用方法去访问子节点。可选的 -visitor 命令让ANTLR生产了一个访问器接口,这个接口的每个规则都对应一个visit方法。下图是的访问器模式:

为了实例化一个遍历树,应用代码需要创建vistor的实现类并且调用visit()方法。

ParseTree tree = ...;//tree is result of parsing
MyVisitor v = new MyVisitor();
v.visit(tree);

antlr访问器支持编码调用visitStat()方法当遇到根节点的时候。visitStat()方法的时候将带着子节点参数调用visit()方法以便继续遍历。或者,visitMethod()也可以明确的调用visitAssian()方法,等等。

比较

监听器和遍历器的最大不同在于,监听方法并不为明确的嗲用方法区遍历他们的子节点负责。在另一方面,访问器必须明确的触发访问子节点,以便保持遍历的继续运行。

访问器控制了遍历的顺序,由于明确的调用了子节点,也控制的树是否会被 遍历。

antlr4-核心符号(二)

符号

语言的本质都是一样的,他们都具有各种表达式。下图是antlr的作者总结的antlr的核心语法符号,大部分语言都是其中一种或者多种。

通用语言模式

序列

这是一个有限的或者循环的长序列符号或者子句。例如包含变量声明(输入在标识符后面)和整形列表。
下面是一些例子:

x y ... z // x followed by y, ..., z
'[' INT+ ']' // Matlab vector of integers

带终止符的序列

这是一个被符号分离的循环的序列符号或者子句。这个符号通常是分号或者新行。
例如python以新行结束,java以分号结束。

(statement ';' )* // Java statement list
(row '\n')* // Lines of data

带分离符的序列

这是一个非空的长序列。通常被标识符,逗号,分号或者周期性的分离。

expr (',' expr)* // function call arguments
( expr (',' expr)* )? // optional function call arguments
'/' ? name ('/' name)* // simplified directory name
stat ('.' stat)* // SmallTalk statement list

可选

这是一系列可选的短语。
例如包含不同种类的类型,语句,表达式,或者xml标签。

type : 'int' | 'float' ;
stat : ifstat | whilestat | 'return' expr ';' ;
expr : '(' expr ')' | INT | ID ;
tag : '<' Name attribute* '>' | '<' '/' Name '>' ;

符号依赖

一个标准符的存在要求更多的标准符。
例如(),{},等。

'(' expr ')' // nested expression
ID '[' expr ']' // array index
'{' stat* '}' // statements grouped in curlies
'<' ID (',' ID)* '>' // generic type specifier

嵌套语句

这是一个自相似性的语言结构。例如表达式,嵌套java类,嵌套的代码块,和嵌套的python功能定义等。

expr : '(' expr ')' | ID ;
classDef : 'class' ID '{' (classDef|method|field) '}' ;

antlr4快速开始(一)

antlr是什么?

ANTLR (ANother Tool for Language Recognition) 是一个强大的解析生成器,它可以读,处理,执行或者转换格式化的文本或者二进制文件。它被广泛的用于构建语言,工具,和框架。从语法上来说,antlr生成了一个能够构建和遍历解析树的解析器。

快速开始

Windows
Download http://antlr.org/download/antlr-4.4-complete.jar.
Add antlr4-complete.jar to CLASSPATH, either:
Permanently: Using System Properties dialog > Environment variables > Create or append to CLASSPATH variable
Temporarily, at command line:
SET CLASSPATH=.;C:\Javalib\antlr4-complete.jar;%CLASSPATH%
Create batch commands for ANTLR Tool, TestRig in dir in PATH
 antlr4.bat: java org.antlr.v4.Tool %*
 grun.bat:   java org.antlr.v4.runtime.misc.TestRig %*

例子

grammar Expr;        
prog:    (expr NEWLINE)* ;
expr:    expr ('*'|'/') expr
    |    expr ('+'|'-') expr
    |    INT
    |    '(' expr ')'
    ;
NEWLINE : [\r\n]+ ;
INT     : [0-9]+ ;


$ antlr4 Expr.g4
$ javac Expr*.java
$ grun Expr prog -gui
100+2*34
^D

注意:(That ^D means EOF on unix; it’s ^Z in Windows.) The -tree option prints the parse tree in LISP notation.

更多请参考:https://theantlrguy.atlassian.net/wiki/display/ANTLR4/Getting+Started+with+ANTLR+v4

分支预测

概念:什么是分支预测?

在电脑架构中,分支预测器(英语:Branch predictor)是一种数字电路,在分支指令执行结束之前猜测哪一路分支将会被运行,以提高处理器的指令流水线的性能。使用分支预测器的目的,在于改善指令管线化的流程。现代使用指令管线化处理器的性能能够提高,分支预测器对于现今的指令流水线微处理器获得高性能是非常关键的技术。

如果没有分支预测器,处理器将会等待分支指令通过了指令流水线的执行阶段,才把下一条指令送入流水线的第一个阶段—取指令阶段(fetch stage)。这种技术叫做流水线停顿(stream stalled)或者流水线冒泡(stream bubbling)或者分支延迟间隙。这是早期的RISC体系结构处理器采用的应对分支指令的流水线执行的办法。

分支预测器猜测两路分支中哪一路最可能发生,然后投机执行这一路的指令,来避免流水线停顿造成的时间浪费。如果后来发现分支预测错误,那么流水线中投机执行的那些中间结果全部放弃,重新取得正确的分支路线上的指令开始执行,这招致了程序执行的延迟。

在分支预测失败时浪费的时间是从取指令到执行完指令(但还没有写回结果)的流水线的级数。现代微处理器趋向采用非常长的流水线,因此分支预测失败可能会损失10-20个时钟周期。越长的流水线就需要越好的分支预测。

jdbc连接数据库一定要用Class.forName吗?

一来源

上周,在调试使用jdbc连接infobright的时候,遇到了一个奇怪的问题,当我使用Class.forName加载myslq jdbc的驱动的进行连接数据库的时候,连接缺报异常了。奇怪的是异常信息并不是mysql驱动抛出来的,而是另一个数据库驱动,达梦的驱动异常。

二好奇心

为什么会这样呢,我明明没有加载达梦的数据集驱动呀?连不上也就算了,还抛了个其他的异常,百思不得其解。于是,我debug到了DriverManager中的源码看了看,发现已经加载了各种不同的jdbc驱动有6,7种之多。就这样,顺带的,我凑了眼DriverManager的源码。

int源码赏析

概要

今天突发好奇,想不明白string是如何转换为int的,于是翻开jdk的源码,收货挺大的。

parseInt(String s)

首先我们看parseInt这个方法,看下图,它又调用了parseInt(String s, int radix),这个方法才是String转换成int的主要过程。这个方法比较有趣,我们来一一分析它的实现机制。

public static int parseInt(String s) throws NumberFormatException {
        //此处表示s默认为10进制
        return parseInt(s,10);
}

//radix为s的进制格式,不能大于36也不能小于2,即最小为2进制,最大为36进制,36进制的得来是10个数字加26个字母
//例如,parseInt("Kona", 27) returns 411787
public static int parseInt(String s, int radix)
            throws NumberFormatException
{
    /*
     * WARNING: This method may be invoked early during VM initialization
     * before IntegerCache is initialized. Care must be taken to not use
     * the valueOf method.
     */

    if (s == null) {
        throw new NumberFormatException("null");
    }

    if (radix < Character.MIN_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }

    if (radix > Character.MAX_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " greater than Character.MAX_RADIX");
    }

    int result = 0;
    boolean negative = false;
    int i = 0, len = s.length();
    //统一转成负号进行处理,不会出现溢出现象
    int limit = -Integer.MAX_VALUE;
    int multmin;
    int digit;

    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar < '0') { // Possible leading "+" or "-"
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+')
                throw NumberFormatException.forInputString(s);

            if (len == 1) // Cannot have lone "+" or "-"
                throw NumberFormatException.forInputString(s);
            i++;
        }
        multmin = limit / radix;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            //拿到第i个数字并且格式化成10进制
            digit = Character.digit(s.charAt(i++),radix);
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            //使用负号进行计算 , -20-3=-23
            result -= digit;
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    return negative ? result : -result;
}

Integer decode(String nm)

转换8进制,16进制,10进制到10进制

public static Integer decode(String nm) throws NumberFormatException {
    int radix = 10;
    int index = 0;
    boolean negative = false;
    Integer result;

    if (nm.length() == 0)
        throw new NumberFormatException("Zero length string");
    char firstChar = nm.charAt(0);
    // Handle sign, if present
    if (firstChar == '-') {
        negative = true;
        index++;
    } else if (firstChar == '+')
        index++;

    // Handle radix specifier, if present
    if (nm.startsWith("0x", index) || nm.startsWith("0X", index)) {
        index += 2;
        radix = 16;
    }
    else if (nm.startsWith("#", index)) {
        index ++;
        radix = 16;
    }
    else if (nm.startsWith("0", index) && nm.length() > 1 + index) {
        index ++;
        radix = 8;
    }

    if (nm.startsWith("-", index) || nm.startsWith("+", index))
        throw new NumberFormatException("Sign character in wrong position");

    try {
        result = Integer.valueOf(nm.substring(index), radix);
        result = negative ? Integer.valueOf(-result.intValue()) : result;
    } catch (NumberFormatException e) {
        // If number is Integer.MIN_VALUE, we'll end up here. The next line
        // handles this case, and causes any genuine format error to be
        // rethrown.
        String constant = negative ? ("-" + nm.substring(index))
                                   : nm.substring(index);
        result = Integer.valueOf(constant, radix);
    }
    return result;
}

String toString(int i, int radix)

public static String toString(int i, int radix) {

    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10;

    /* Use the faster version */
    if (radix == 10) {
        return toString(i);
    }

    char buf[] = new char[33];
    boolean negative = (i < 0);
    int charPos = 32;

    //如果非负数,转成负数
    if (!negative) {
        i = -i;
    }

    /**  12|2 ...0
    *     6|2 ...0
    *     3|2 ...1
    *     1|2 ...1      
    *    类似这种除法...
     **/
    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)];
        i = i / radix;
    }
    buf[charPos] = digits[-i];

    if (negative) {
        buf[--charPos] = '-';
    }

    return new String(buf, charPos, (33 - charPos));
}

Integer valueOf(String s)

valueOf有点特殊,他缓存了-128到127的值,所以数字在此范围内都是取的同一个对象。
与其类似的还有

  • Boolean:(全部缓存)
  • Byte:(全部缓存)
  • Character(<= 127缓存)
  • Short(-128 — 127缓存)
  • Integer(-128 — 127缓存)
  • Long(-128 — 127缓存)
  • Float(没有缓存)
  • Doulbe(没有缓存)
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

linux常用命令

目录和文件处理

目录符号

  • . : 当前目录。
  • .. : 当前的上一级目录。
  • ~ : 用户主目录,即/home/我的登录名。当然你直接输入cd,也有同样功效

mkdir

建立目录
用法:mkdir [OPTION] DIRECTORY…
例:mkdir test

spring aop

Advice通知

提示:为了理解advice,我们可以参考下面的例子:
http://www.mkyong.com/spring/spring-aop-examples-advice/

通知,制定在连接点做什么,在Sping中,他主要描述Spring围绕方法调用注入的额外的行为,Spring提供的通知类型有:
beforeadvice,AfterReturningAdvice,ThrowAdvice,MethodBeforeAdvice,这些都是Spring AOP定义的接口类,具体的动作实现需要用
户程序来完成。

public interface Advice {

}

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;

}

public interface AfterReturningAdvice extends AfterAdvice {
    void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

Pointcut连接点

提示:为了理解Pointcut和Advisor,我们可以参考下面的例子:
http://www.mkyong.com/spring/spring-aop-example-pointcut-advisor/

切点,其决定一个advice应该应用于哪个连接点,也就是需要插入额外处理的地方的集合,例如,被某个advice作为目标的一组方法。Spring pointcut通常意味着标示方法,可以选择一组方法调用作为pointcut,Spring提供了具体的切点来给用户使用,比如正则表达式切点 JdkRegexpMethodPointcut通过正则表达式对方法名进行匹配,其通过使用 AbstractJdkRegexpMethodPointcut中的对MethodMatcher接口的实现来完成pointcut功能.