CppUnit 笔记

2010年11月07日 19:39

 

最近在重写一个私人项目,为了提高项目质量,决定使用CppUnit做单元测试。 CppUnit已久仰大名,但由于之前主要做C的项目,使用的都是自己写的 ASSERT库,因此刚一上手时对CppUnit的一些概念的理解比较乱,不过在第N遍阅 读Cookbook后还算是理清了一些概念。

1 TestCase, Fixture and Suit

Cppunit通过上面三个概念(类)来组织测试用例。一般来说,一个Fixture包含 了N个TestCase,而一个Suit又包含了N个Fixture。用图表示就是下面这样: 

2 Helper Macros

在Helper Macros出现之前,把N个case加到一个suit里是一件非常机械的事, 它不仅要求为一个Fixture子类提供一个静态的suit()函数成员,它还意味着重 复写个类似于

suite.addTest( 
             new CppUnit::TestCaller<ComplexNumberTest>( "testEquality",
                                                         &ComplexNumberTest::testEquality )
             );

的代码,而这种代码一旦多了就容易造成笔误。因此 CppUnit提供了一些宏如 CPPUNIT_TEST_SUITECPPUNIT_TEST_EXCEPTION 等来帮助开发人员减少这 种情况的发生。 使用 CPPUNIT_TEST_SUITE 后,写成:

CPPUNIT_TEST( testEquality )

就行了。

3 TestFactoryRegistry

TestFactoryRegistry的存在是为了解决两个问题:

  • 忘记将某个fixture添加进suit
  • 有多少个suit就有多少个#include (一般一个suit放到一个文件里)

 

在WebKit的官网上有一篇使用指南:RefPtr and PassRefPtr Basics,从应用的角度讲解了RefPtr类族的由来,及使用方法。但很无厘头的是,这里面居然没有提到如何创建一个有引用计数功能的类!?(教你如何用砖头造房子却不教你如何造砖头)

在抱怨作者的粗心、懒惰、和恶搞精神后,我只能用一句俗话来安慰自己:还好有源代码,还好有源代码,⋯⋯WebKit我来了!!!

WebKit是个非常优秀的开源项目,应用范围广就不说了(safari、chrome、iphone、android、⋯⋯),代码质量也是高得一塌糊涂:清晰的模块划分、清晰的代码组织架构、晦涩(巧妙的同义词)的C++小技巧等优点完全值得有心提高内功的同志一读。

下面就是我在学习WebKit源代码里的RefPtr智能指针的一些心得。

Overview

WebKit的智能指针由类族 RefPtr 来实现,其核心由三个类组成:

  • RefCounted
  • RefPtr
  • PassRefPtr

其中RefCounted提供了引用计数器(一个int型成员),而RefPtr和PassRefPtr则提供了自动管理引用计数器的功能。根据RefPtr and PassRefPtr Basics的说法,最初并没有RefPtr和PassRefPtr,这两个类是2005年才加入的,在它们出现之前完全是靠找死的人工管理RefCounted的引用计数。RefCounted类本身是没有问题的,但它的使用方法相当繁琐,繁琐到开发人员发现很多内存泄露都是由于对方法ref()和deref()的调用不当而造成一半以上内存泄露的程度。为了简化RefCounted的使用方法,RefPtr诞生了,而为了更高效地传递参数,开发人员又创造了PassRefPtr。

RefCounted

RefCounted的源代码在这里:RefCounted.h

这个文件里定义了两个类:非模板类RefCountedBase和模板类RefCounted,从名字上就能看出来RefCounted继承于RefCountedBase。像维护引用计数器这么简单的活儿只使用一个类就绰绰有余了,这里之所以做成两个类是为了减少template hoisting(实例化模板导致的代码膨胀)。总而言之,最终的RefCounted完成了这样的功能:

  • 定义了成员变量:int m_refCount
  • 定义函数ref
    		// 
    void ref()
    {
        ++m_refCount;
    }
  • 定义函数deref
    		// 
    void deref()
    {
        if (derefBase())
            delete static_cast<T*>(this);
    }
    derefBase的定义是:
    		// 
    // Returns whether the pointer should be freed or not.
    bool derefBase()
    {
        if (m_refCount == 1) {
          return true;
        }
    
        --m_refCount;
        return false;
    }

上面的ref()和deref()就是RefCounted的核心功能了,不过有一点要注意的是RefCounted的析构函数是protected的,这样就不能直接定义RefCounted对象了,而是必须从RefCounted继承子类才能行。但是这里不同于一般的继承,这里玩了个小trick:

class Frame : public RefCounted<Frame> {
  // ...
}

和一般的继承不一样吧,之所以要写成这样的原因在于函数deref()能够删除正确的对象!在函数deref()里,如果计数器到了1,就必须把自己销毁(即delete this),但此时的RefCounted直接来一句delete this是不对的,因为如果有一个类Foo继承于RefCounted,那在RefCounted的成员函数里调用delete this只会触发RefCounted的析构函数,而不会触发Foo的析构函数。为了让RefCounted在delete this时能确定子类的类型,类Foo就得从RefCounted<Foo>继承,这样才能为在RefCounted中delete this提供便利:

delete static_cast<T*>(this);

在编译时T的类型会被确定为Foo,因此上面这句代码会触发Foo的析构函数,另外由于Foo继承于RefCounted,RefCounted的析构函数随后会调用(虽然啥也没干)。

--EOF--

最近使用 Eclipse 时碰到了一个奇怪的问题, 源代码完全没错, 但 Eclipse 总是报错:

Error generating final archive: Debug certificate expired on XXX

google 了一把, 在 stackoverflow 发现答案:

http://stackoverflow.com/questions/2194808/debug-certificate-expired-error-in-eclipse-android-plugins

问题的产生和 android sdk 有关, 只需要删除文件: ~/.android/debug.keystore , 再重启 eclipse 就行了.

 

 

可视化完全二叉树

2010年8月09日 06:12

 完全二叉树的特性:从根结点开始每一层都从左到右填充” 使得它可以用一维数组直接存储一棵树本身。这个数组虽然逻辑上已经是树型的了,但表现形式却还是一维的,看起来很费劲。因此我写了个脚本,把一个数组转换成png图像,就像下面这样:

 array  {1, 2, 3, 4, 5}
 pic 

直接将一个树显示为png文件是很麻烦的,因为不仅得需要知道使用生成png的相关api(svg就不用了),还得想办法画好各结点的间距、距离、连接线,这些都不是轻而易举能搞定的。不过有前人栽树,后人就好乘凉了,Graphviz 就是要乘凉的那棵树。

Graphviz 提供了一系列的工具,dot 就是其中之一。dot 的功能就能是:用专门的描述语言显示图形,比如上面的 {1,2,3,4,5} 树的 dot 源代码就是:

 

graph
{
1 -- 2
1 -- 3
2 -- 4
2 -- 5
}

 

就这么简单。剩下的工作就更简单了,只需要把数组显示成上面的源代码就行了。

转换代码如下:

 

  ;; emacs lisp
  ;;;;;;;;;;;;;;
  (defun complete-binary-tree-to-dot-script (array)
    "convert complete binary tree to dot script language"
    (when (and (sequencep array) (> (length array) 1))
      (let ((result "graph\n{\n")
            (i 0)
            (N (length array)))
        (while (< i N)
          (setq result (concat result (proc-left  array i)))
          (setq result (concat result (proc-right array i)))
          (setq i (+ 1 i)))
        (concat result "}\n")
        )))
  
  (defun proc-left (array p)
    (when (< (+ 1 (* 2 p)) (length array))
      (concat (number-to-string (elt array p)) " -- " (number-to-string (elt array (+ 1 (* 2 p)))) "\n")))
          
  (defun proc-right (array p)
    (when (< (+ 2 (* 2 p)) (length array))
      (concat (number-to-string (elt array p)) " -- " (number-to-string (elt array (+ 2 (* 2 p)))) "\n")))

 

求一个结点的左、右结点使用下面的公式就能得到:

leftnode=node*2+1

rightnode=node*2+2

 

要转换某个数组时,就使用:(complete-binary-tree-to-dot-script [1 2 3 4 5]) 来进行转换。将得到的结果保存到一个文件,然后再用 dot 编译成 png 就搞定了!

 

dot  -Tpng tree.dot > tree.png

 

 

有一个实例来辅助分析话会更方便,这里使用的实例是:编程找出文件中符合正则表达式 "alpha[alpha|digit]*" 和 "digit+" 的字符串。alpha 就是字母啦:[a-zA-Z];digit就是数字:[0-9]。

先画出DFA:

在上图中,状态只有三个(结束状态可以省略):开始状态、单词状态和数字状态。

接下来要用两种不同的方法来实现这个DFA,第一种是传统的 while&switch 大法,第二种是现在流行的设计模式中的状态模式。

为了方便比较,先抽象出一个名为Parser的接口,它提供了一个名为 parse 的方法,传统状态机状态机模式通过实现这个方法来完成对正则表达式的识别。传统状态机实现的类名叫ClassicalMachine,状态机模式的类名则叫PatternMachine。

Parser接口的定义: 

public interface Parser {
  void parse() throws Exception ;
}

ClassicalMachine 类的实现非常简单,它在 parse() 方法里就完成了干完了一切:

public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}

 注意上面有一堆 case xxx: 和 if(xxx),如果DFA中有更多的状态和动作的话,case xxx: 会更多。

接下来实现状态模式类。从DFA图可以看出需要实现的动作只有三个,这三个动作分别在碰到字母、数字和其它字符时触发,因此可以定义状态抽象类如下:

abstract class State {
    public abstract void isAlpha(char c);
    public abstract void isDigit(char c);
    public abstract void isOther(char c);
}

为了方便判断字符是数字还是字母,再加上一个函数dispatch(),于是State类最终是这样:

abstract class State {
    public abstract void isAlpha(char c);
    public abstract void isDigit(char c);
    public abstract void isOther(char c);
    public void dispatch(char c) {
        if (Character.isLetter(c)) {
            isAlpha(c);
        }
        else if (Character.isDigit(c)) {
            isDigit(c);
        }
        else {
            isOther(c);
        }
    }
}

 再看看上面的DFA图,发现状态只有三个,因此接下来从 State 派生出三个类对应这三个状态: 

class StartState extends State {
    public void isAlpha(char c) {
        setState( mKeywordState );
        mToken += c;
    }
    public void isDigit(char c) {
        setState( mNumberState );
        mToken += c;
    }
    public void isOther(char c) {
    }
}
	
class KeywordState extends State {
    public void isAlpha(char c) {
        mToken += c;
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Keyword: " + mToken);
        mToken = "";
    }
}
	
class NumberState extends State {
    public void isAlpha(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
}

 万事具备,只欠东风了,剩下的只是将字符流作为燃料,来驱动状态机了:

public void parse() throws Exception {
    while (true) {
        int ch = mFin.read();
        if (-1 == ch) break;
        char c = (char)ch;
        mState.dispatch( c );
			
    }
}

 


上面啰啰嗦嗦写了一堆代码,目的就是要对比传统状态机的实现方式和状态模式的区别。传统的实现方式是使用一大块 if&switch 代码,比如说lua的lex;当然也可以对这段铁板代码进行拆分,python的lex就是这么干的,但这样做仅仅只是将那些头痛的switch&if分散到了其它的函数而已,switch&if 和数量并没有减少。更多的条件判断语句会导致更复杂的逻辑,因此要让逻辑变得更简单, 得减少这些判断语句才行---状态模式办到了,它干掉了至少一半的判断语句。它是怎么做到的?

状态机的特点之一:状态是在运行时确定的,这刚好可以通过多态来实现。多态的特点:在运行时对对象进行动态绑定,绑定是由编译器或运行环境来完成的,无需手工参与,因此也可以说是现代越来越强大的编译器or运行环境干掉了switch&if。

附完整的代码:

ClassicalMachine.java:

package bsg;
import java.io.BufferedReader;
import java.io.IOException;


import bsg.Main.Parser;

import com.sun.tools.hat.internal.parser.Reader;


public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}



public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}


abstract class State {
    public abstract void isAlpha(char c);
    public abstract void isDigit(char c);
    public abstract void isOther(char c);
    public void dispatch(char c) {
        if (Character.isLetter(c)) {
            isAlpha(c);
        }
        else if (Character.isDigit(c)) {
            isDigit(c);
        }
        else {
            isOther(c);
        }
    }
}


class StartState extends State {
    public void isAlpha(char c) {
        setState( mKeywordState );
        mToken += c;
    }
    public void isDigit(char c) {
        setState( mNumberState );
        mToken += c;
    }
    public void isOther(char c) {
    }
}
	
class KeywordState extends State {
    public void isAlpha(char c) {
        mToken += c;
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Keyword: " + mToken);
        mToken = "";
    }
}
	
class NumberState extends State {
    public void isAlpha(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
}


public void parse() throws Exception {
    while (true) {
        int ch = mFin.read();
        if (-1 == ch) break;
        char c = (char)ch;
        mState.dispatch( c );
			
    }
}

 PatternMachine.java

package bsg;

import java.io.BufferedReader;

import bsg.Main.Parser;

public class PatternMachine implements Parser {
    private String mToken = "";
    private BufferedReader mFin;
    public PatternMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private StartState mStartState = new StartState();
    private KeywordState mKeywordState = new KeywordState();
    private NumberState mNumberState = new NumberState();
    //
    private State mState = mStartState;
    public State getState() {
        return mState;
    }
    public void setState(State state) {
        mState = state;
    }

    abstract class State {
        public abstract void isAlpha(char c);
        public abstract void isDigit(char c);
        public abstract void isOther(char c);
        public void dispatch(char c) {
            if (Character.isLetter(c)) {
                isAlpha(c);
            }
            else if (Character.isDigit(c)) {
                isDigit(c);
            }
            else {
                isOther(c);
            }
        }
    }
	
    class StartState extends State {
        public void isAlpha(char c) {
            setState( mKeywordState );
            mToken += c;
        }
        public void isDigit(char c) {
            setState( mNumberState );
            mToken += c;
        }
        public void isOther(char c) {
        }
    }
	
    class KeywordState extends State {
        public void isAlpha(char c) {
            mToken += c;
        }
        public void isDigit(char c) {
            mToken += c;
        }
        public void isOther(char c) {
            setState( mStartState );
            System.out.println("Keyword: " + mToken);
            mToken = "";
        }
    }
	
    class NumberState extends State {
        public void isAlpha(char c) {
            setState( mStartState );
            System.out.println("Number: " + mToken);
            mToken = "";
        }
        public void isDigit(char c) {
            mToken += c;
        }
        public void isOther(char c) {
            setState( mStartState );
            System.out.println("Number: " + mToken);
            mToken = "";
        }
    }
	

    @Override
    public void parse() throws Exception {
        while (true) {
            int ch = mFin.read();
            if (-1 == ch) break;
            char c = (char)ch;
            mState.dispatch( c );
			
        }
    }

}


package bsg;
import java.io.BufferedReader;
import java.io.IOException;


import bsg.Main.Parser;

import com.sun.tools.hat.internal.parser.Reader;


public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}



public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}


abstract class State {
    public abstract void isAlpha(char c);
    public abstract void isDigit(char c);
    public abstract void isOther(char c);
    public void dispatch(char c) {
        if (Character.isLetter(c)) {
            isAlpha(c);
        }
        else if (Character.isDigit(c)) {
            isDigit(c);
        }
        else {
            isOther(c);
        }
    }
}


class StartState extends State {
    public void isAlpha(char c) {
        setState( mKeywordState );
        mToken += c;
    }
    public void isDigit(char c) {
        setState( mNumberState );
        mToken += c;
    }
    public void isOther(char c) {
    }
}
	
class KeywordState extends State {
    public void isAlpha(char c) {
        mToken += c;
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Keyword: " + mToken);
        mToken = "";
    }
}
	
class NumberState extends State {
    public void isAlpha(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
}


public void parse() throws Exception {
    while (true) {
        int ch = mFin.read();
        if (-1 == ch) break;
        char c = (char)ch;
        mState.dispatch( c );
			
    }
}

Main.java 

package bsg;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;


public class Main {
    public interface Parser {
        void parse() throws Exception ;
    }
	
    public static void main(String[] args) {
        if (0 == args.length) return;

        BufferedReader fin = null;
        try {
            fin = new BufferedReader( new FileReader(args[1] ) );
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
		
        Parser parser = null;
        if (args[0].equals("cla")) {
            parser = new ClassicalMachine( fin );
        } else if (args[0].equals("pat")){
            parser = new PatternMachine( fin );
        }
		
        long beg = System.currentTimeMillis();
        try {
            parser.parse();
            fin.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis() - beg;
        System.out.println("" + end);
    }
}


package bsg;

import java.io.BufferedReader;

import bsg.Main.Parser;

public class PatternMachine implements Parser {
    private String mToken = "";
    private BufferedReader mFin;
    public PatternMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private StartState mStartState = new StartState();
    private KeywordState mKeywordState = new KeywordState();
    private NumberState mNumberState = new NumberState();
    //
    private State mState = mStartState;
    public State getState() {
        return mState;
    }
    public void setState(State state) {
        mState = state;
    }

    abstract class State {
        public abstract void isAlpha(char c);
        public abstract void isDigit(char c);
        public abstract void isOther(char c);
        public void dispatch(char c) {
            if (Character.isLetter(c)) {
                isAlpha(c);
            }
            else if (Character.isDigit(c)) {
                isDigit(c);
            }
            else {
                isOther(c);
            }
        }
    }
	
    class StartState extends State {
        public void isAlpha(char c) {
            setState( mKeywordState );
            mToken += c;
        }
        public void isDigit(char c) {
            setState( mNumberState );
            mToken += c;
        }
        public void isOther(char c) {
        }
    }
	
    class KeywordState extends State {
        public void isAlpha(char c) {
            mToken += c;
        }
        public void isDigit(char c) {
            mToken += c;
        }
        public void isOther(char c) {
            setState( mStartState );
            System.out.println("Keyword: " + mToken);
            mToken = "";
        }
    }
	
    class NumberState extends State {
        public void isAlpha(char c) {
            setState( mStartState );
            System.out.println("Number: " + mToken);
            mToken = "";
        }
        public void isDigit(char c) {
            mToken += c;
        }
        public void isOther(char c) {
            setState( mStartState );
            System.out.println("Number: " + mToken);
            mToken = "";
        }
    }
	

    @Override
    public void parse() throws Exception {
        while (true) {
            int ch = mFin.read();
            if (-1 == ch) break;
            char c = (char)ch;
            mState.dispatch( c );
			
        }
    }

}


package bsg;
import java.io.BufferedReader;
import java.io.IOException;


import bsg.Main.Parser;

import com.sun.tools.hat.internal.parser.Reader;


public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}



public class ClassicalMachine implements Parser {
    private BufferedReader mFin;
    private String mToken;
	
    public ClassicalMachine( BufferedReader fin ) {
        mFin = fin;
    }
	
    private enum STATUS {START, IS_KEYWORD, IS_NUMBER};
    STATUS mStatus = STATUS.START;

    @Override
    public void parse() throws Exception {
        mToken = "";
        while (true) {
            int ch = mFin.read();
            if (-1 == ch)	break;
            char c = (char)ch;

            switch (mStatus) {
            case START:
                if (Character.isLetter(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_KEYWORD;
                }
                else if (Character.isDigit(c)) {
                    mToken += c;
                    mStatus = STATUS.IS_NUMBER;
                }
                break;

            case IS_KEYWORD:
                if (Character.isLetterOrDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("keyword: " + mToken);
                    mToken = "";
                }
                break;

            case IS_NUMBER:
                if (Character.isDigit(c)) {
                    mToken += c;
                }
                else {
                    mStatus = STATUS.START;
                    System.out.println("number: " + mToken);
                    mToken = "";
                }
                break;
            }
        }
    }
	
}


abstract class State {
    public abstract void isAlpha(char c);
    public abstract void isDigit(char c);
    public abstract void isOther(char c);
    public void dispatch(char c) {
        if (Character.isLetter(c)) {
            isAlpha(c);
        }
        else if (Character.isDigit(c)) {
            isDigit(c);
        }
        else {
            isOther(c);
        }
    }
}


class StartState extends State {
    public void isAlpha(char c) {
        setState( mKeywordState );
        mToken += c;
    }
    public void isDigit(char c) {
        setState( mNumberState );
        mToken += c;
    }
    public void isOther(char c) {
    }
}
	
class KeywordState extends State {
    public void isAlpha(char c) {
        mToken += c;
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Keyword: " + mToken);
        mToken = "";
    }
}
	
class NumberState extends State {
    public void isAlpha(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
    public void isDigit(char c) {
        mToken += c;
    }
    public void isOther(char c) {
        setState( mStartState );
        System.out.println("Number: " + mToken);
        mToken = "";
    }
}


public void parse() throws Exception {
    while (true) {
        int ch = mFin.read();
        if (-1 == ch) break;
        char c = (char)ch;
        mState.dispatch( c );
			
    }
}

 

 

在osx上安装org-mode开发版

2010年7月31日 04:49

  1. 安装 emacs for osx,下载地址: http://emacsformacosx.com/
  2. clone org-mode devel code
    git clone git://repo.or.cz/org-mode.git
  3. 修改 Makefile:
    EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
    prefix=/Applications/Emacs.app
    lispdir=$(prefix)/Contents/Resources/lisp/org
    infodir=$(prefix)/Contents/Resources/info
  4. make && make install

 

Org-mode Version 7 Changes

2010年7月29日 18:28

1 不再支持 Emacs 21

7.xx 不再支持 Emacs 21, Emacs 21 用户可以使用 6.36c

2 不再支持 XEmacs

但为 XEmacs 提供了一个独立的开发版本。

3 Org-babel

从 7.x 开始 Babel 正式成为 Org-mode 内核的一部分(而不是作为 Contribute code)。这就意味着你一旦加载了 Org-mode,也就加载了 babel,它们现在是一体的。 这也是 7.xx 相对于 6.xx 最大的改动。

4 为 TODO sparse trees 提供了新的快捷键

C-c C-v 改为执行 Babel 的功能. 另外 TODO sparse trees 依然可以使用 C-c / tC-c / T

5 DocBook

为 DocBook 导出功能提供了更多的自定义变量.

手机话费的新用途

2010年7月27日 18:38

(消息来源: www.geek.com)

上周五 (ADB)Android Developers Blog 宣布将提供除信用卡外的另一种付费方式,但具体是什么,ADB 却没有说明,倒是有人通过分析新旧 DDA(Developer Distribution Agreement) 得到了一些信息。
DDA 最新版本做了如下修改:

  • In Section 13.1, “authorized carriers” have been added as an indemnified party.
  • Section 13.2 is new in its entirety, covering indemnity for payment processors for claims related to tax accrual.

因此可以猜到,google 准备向用户提供通过手机话费来购买应用程序的服务。 

目前只有 T-Mobile 和 google 有这方面的合作关系。
 

今天在邮件列表上又看到一封抱怨 plain lists 粗暴 folding 的邮件,主题为: "when do plain lists end?"

这个 bug 搞得我也是很不爽,试过N个办法来试图解决或避免,但到目前为止成功率为 0x0% 。

忘了描述这个 bug 了,这个 bug 表现为:在使用 orgmode 的 plain lists 时,在最后一个 item 按 TAB 总是会导致这个 item 后所有的内容(包括不属于这个item的段落)都会被 folding。举个例子就是

 

  get some plain lists
  - item 1
    blablabla..
  - item 2
    blana

  I am not part of item 2

 

如果在 - item 2 这行按 TAB 的话,“I am not part of item 2” 也会被 folding 掉。

这个bug也有一些年头了,之所以拖到现在还没解决,当然不是因为实际上这是一个功能缘故,而是因为这个 bug 比较棘手,并且在导出(比如html)后的文档里不会出现这方面的问题,因此才得以存活至今。

当然这个 bug 也不是一无是处。如果某天与某个 orgmode 用户在一起时突然冷场了,就可以试着用谈论这个 bug 来打破沉默的尴尬。