zoukankan      html  css  js  c++  java
  • 小型桌面计算器的实现(javacc)

    从开始学计算理论,就对形式语言,编译原理很感兴趣,所以大学对这门课学的也算是最好了。自己也实现过一些简单的词法分析器之类的东西,不过也都是学习目的的,质量一般 后来一直在Linux学习,对lex/yacc研究过一点时间,设计过一个小的脚本引擎,可以做一些比较复杂的数学运算,这个可以参考我的这篇博客<hoc>。工作以后,平台 变成了java,跟C渐渐的离得远了,也喜欢上java这个提供语言级别的面向对象的语言以前的一些东西用顺手了,一时习惯还改不过来,于是就开始找lex/yacc的替代品。

     

    研究过一段时间的antlr,觉得还行,特别是那个GUI做的非常好,但是跟lex/yacc在用法上还是有些差别的,最近才遇到了javacc,觉得跟lex/yacc的写法相当像,就试着 写了一个简单的桌面计算器(desktop-calculator),大家可以参考一下。 (从下载到javacc.zip到写出这个计算器,大约花了我一个下午的时间,所以你可以很快 的学会javacc,并在实际中应用。)用这类工具的好处是,只要你理解形式语言,那么就可以很方便的做出一个分析起来,而不用关心其实现细节(如词法分析等部分是十分 
    枯燥且无意义的)

     

    一来这是一个比较经典的例子,通过它的学习,可以迅速掌握一个编译器生成器,或者叫语言解析器生成器。二来最近项目中用到了一些类似的东西,正好趁此机会记下来 以便将来参考。

     

    四则运算的计算器的规则非常清晰,可以看一下这个巴克斯范式(BNF)

    Java代码  收藏代码
    1. expr ::= term ((+|-) term)*  
    2. term ::= factor ((*|/) factor)*  
    3. factor ::= number | expr   
    4. number ::= [0-9]+...   

     

      
    形式语言的好处就在于其清晰,准确和强大。如果你对正则表达式比较收悉,那么可以很容易掌握形式语言。(正则表达式其实就是一个精简而强大的形式语言实例) 
     
    首先,我们需要定义在文法描述中需要用到的所有记号(token),这是分析器能认识到的最小单元

    Java代码  收藏代码
    1. TOKEN:{  
    2.       <ADD : "+">  
    3.     | <SUB : "-">  
    4.     | <MUL : "*">  
    5.     | <DIV : "/">  
    6.     | <LPAREN : "(">  
    7.     | <RPAREN : ")">  
    8.     | <NUMBER :   
    9.           ["0"-"9"] (["0"-"9"])*   
    10.         | (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)?  
    11.         | "." (["0"-"9"])+ (<EXPONENT>)?  
    12.         | (["0"-"9"])+ <EXPONENT> >  
    13.     | <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >  
    14. }  

     javacc的记号描述很简单,就是普通的正则表达式,当然用引号引起来的简单单词也算是正则表达式,如记号ADD,就是"+",这样,当解析器遇到"+"时就返回一个ADD记号,方便在内部使用。 
     
    这里详细说一下NUMBER记号,数字怎么表示呢? 
    我们用自然语言可以描述成:以0到9中的任一个数字开头,后边可以有任意多位(包括0位)数字(此为整数),或者以至少一位数字,后边跟一个小数点,然后又有任意多位的数字,如果使用科学 计数法,则这个数字串后边还可以跟一个E,E后边又可以有正号(+)或者负号(-),也可以没有,然后,后边又是至少一位数字,或者…… 
     
    可以看到,自然语言的描述冗长且不容易理解,我们看看形式语言的描述:首先定义一些符号的意义,如

    "|" 表示或者
    0-9 表示0到9的一个数字
    [] 表示一个区间
    "?" 表示其前的区间中的元素重复一次或零次
    "+" 表示其前的区间中的元素重复至少一次
    "*" 表示其前的区间中的元素重复零次或多次
    () 表示一个分组,是一个整


     
    好了,有了这些定义,我们再看看如何用形式语言描述一个浮点数或者整数:

    Java代码  收藏代码
    1. number ::= [0-9]([0-9])* | [0-9]+ '.' ([0-9])+ exponent?  
    2.                 | '.' ([0-9])+ exponent?  
    3.                 | ([0-9])+ exponent?  
    4. exponent ::= [e,E]([+,-])?([0-9])+   

     

    可以看到,形式语言在描述这种抽象概念上比自然语言要好得多。一旦掌握了形式语言就可以很容易的理解各种复杂的规则。 
     
    词法分析器的一个功能就是剔除源码中的空白字符,比如,空格,制表符,回车换行等

    Java代码  收藏代码
    1. SKIP:{  
    2.     " " | "\t" | "\r" | "\n"  
    3. }   

     

    有了BNF,我们看看在javacc中,如何表现这些规则:

    Java代码  收藏代码
    1. void expr():{}  
    2. {  
    3.     term()((<ADD>|<SUB>)term())*  
    4. }  
    5.    
    6. void term():{}  
    7. {  
    8.     factor()((<MUL>|<DIV>)factor())*  
    9. }  
    10.    
    11. void factor():{}  
    12. {  
    13.     <NUMBER>|  
    14.     <LPAREN>expr()<RPAREN>  
    15. }   

     

      
    javacc基本忠实的体现了BNF的规则定义,当然,现在这个解析器的文法部分(词法分析和语法分析) 已经算是结束了,但是它还不能完成任何计算,因为它没有语义的定义部分,即当发现了 此语法现象后该做什么是没有定义的。 
     
    相信大家都已经注意到,每个非终结符后边都有两个大括号{},第一个目前为空。在javacc中,每个非终结符都最终会被翻译成一个方法,(至于怎么翻译的,你可以自己看看它生成的代 码,当年我曾痛苦在yacc生成的一堆yy_*中徜徉过一段时间,现在是实在看不下去了,javacc生成的代码中到处都是jj_*,唉,一个yy,一个jj,生成的代码是看不成了), 第一个空的大括号中即为将来你自己填写的一些关于这个方法的一些临时变量,包括返回值等信息。 
     
    好了,我们看看加入了语义解释后的代码: 

    Java代码  收藏代码
    1. double expr():  
    2. {  
    3.     double temp = 0;  
    4.     double first, second;  
    5. }  
    6. {  
    7.     //你可以在非终结符前插入变量,等号等,在其后可以插入普通的java代码  
    8.     //插入代码后看起来可能不够清晰,可以参看上边的形式定义  
    9.     first=term(){temp = first;}  
    10.     (<ADD>second=term(){temp = first + second;}|  
    11.      <SUB>second=term(){temp = first - second;})*  
    12.     {return temp;}//最后,应当返回某值  
    13. }  
    14.    
    15. double term():  
    16. {  
    17.     double temp = 0;  
    18.     double first, second;  
    19. }  
    20. {  
    21.     first=factor(){temp = first;}  
    22.     (<MUL>second=factor(){temp = first * second;}|  
    23.      <DIV>second=factor(){temp = first / second;})*  
    24.     {return temp;}  
    25. }  
    26.    
    27. double factor():  
    28. {  
    29.     double temp = 0;  
    30.     Token token;  
    31. }  
    32. {      
    33.     token=<NUMBER>{  
    34.         return Double.parseDouble(token.image);  
    35.     } | <LPAREN> temp=expr() <RPAREN>{  
    36.         return temp;  
    37.     }  
    38. }   
     


     
    好了,主体部分已经建立好了,我们再来看看声明信息等(形式语言的学习是重点,其余的 都比较简单易学,而且不同的cc都提供大同小异的功能)

    Java代码  收藏代码
    1. PARSER_BEGIN(CalcParser)  
    2. import java.io.StringReader;  
    3. import java.io.Reader;  
    4.    
    5. public class CalcParser{  
    6.     public CalcParser(String expr){  
    7.         this((Reader)(new StringReader(expr)));  
    8.     }  
    9.     public static void main(String[] args){  
    10.         try{  
    11.             CalcParser parser = new CalcParser(args[0]);  
    12.             System.out.println(parser.expr());  
    13.         }catch(Exception e){  
    14.             System.out.println("error : "+e.getMessage());  
    15.         }  
    16.     }  
    17. }  
    18. PARSER_END(CalcParser)   
     


     
    每个分析器需要一个名字,这个名字定义在PARSER_BEGIN(xxx)中,而且应保证与下面的类声明保持一致: 
    public class xxx{} 
     
    现在,我们可以用javacc提供的命令行程序来生成我们的计算器,需要注意的是,javacc生成的是java源码,且不再依赖于javacc,你可以将你的分析器源码放在任何地方使用。 
     
    $ javacc CalcParser.jj(你看看这文件名后缀)

    可以看到有类似的输出;

    写道
    Java Compiler Compiler Version 4.2 (Parser Generator)
    (type "javacc" with no arguments for help)
    Reading from file CalcParser.jj . . .
    File "TokenMgrError.java" does not exist. Will create one.
    File "ParseException.java" does not exist. Will create one.
    File "Token.java" does not exist. Will create one.
    File "SimpleCharStream.java" does not exist. Will create one.
    Parser generated successfully.
     


     
    你现在可以试着输入一些表达式让计算器进行计算了。(事例结果见后)


     
    如果想要加入一些别的功能,比如,计算表达式的正弦,余弦函数?很简单,我们可以使用 java.lang.Math中提供的一些数学函数。 
     
    对规则稍事修改,即可完成我们的需求。当然,这个计算器的还是比较简单的,比如,不能回溯(这个以后再说),不支持负数,不支持幂计算。但是,如果通过此文,你对形式语言有了比较 好的理解的话,这些问题都是很容易解决的。 
     
    下面这些代码是我再上边的这个四则运算计算器的基础上加入了少量规则而成的一个微型函数计算器,其表达式格式类似于JSP中的EL表达式,你可以对其进行扩展,从而使之更加有趣。

    Java代码  收藏代码
    1. PARSER_BEGIN(CalcParser)  
    2. import java.io.StringReader;  
    3. import java.io.Reader;  
    4.    
    5. public class CalcParser{  
    6.     public CalcParser(String expr){  
    7.         this((Reader)(new StringReader(expr)));  
    8.     }  
    9.     public static void main(String[] args){  
    10.         try{  
    11.             CalcParser parser = new CalcParser(args[0]);  
    12.             System.out.println(parser.elexpr());  
    13.         }catch(Exception e){  
    14.             System.out.println("error : "+e.getMessage());  
    15.         }  
    16.     }  
    17. }  
    18. PARSER_END(CalcParser)  
    19.    
    20. //声明到此结束  
    21.    
    22. SKIP:{  
    23.     " " | "\t" | "\r" | "\n"  
    24. }  
    25.    
    26. TOKEN:{  
    27.       <ADD : "+">  
    28.     | <SUB : "-">  
    29.     | <MUL : "*">  
    30.     | <DIV : "/">  
    31.     | <MOD : "%">  
    32.     | <LPAREN : "(">  
    33.     | <RPAREN : ")">  
    34.     | <NUMBER :    
    35.           ["0"-"9"] (["0"-"9"])*    
    36.         | (["0"-"9"])+ "." (["0"-"9"])* (<EXPONENT>)?  
    37.         | "." (["0"-"9"])+ (<EXPONENT>)?  
    38.         | (["0"-"9"])+ <EXPONENT> >  
    39.     | <#EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ >  
    40.     | <EXPRPREFIX: "${">  
    41.     | <EXPRSUFFIX: "}">  
    42.     | <SIN: "sin">  
    43.     | <COS: "cos">  
    44. }  
    45.    
    46. //记号部分声明到此结束,下面是语法声明,包括语义解释  
    47.    
    48. double elexpr():  
    49. double temp = 0; }  
    50. {  
    51.     <EXPRPREFIX>temp=expr()<EXPRSUFFIX>  
    52.     {return temp;}  
    53. }  
    54.    
    55. double expr():  
    56. {  
    57.     double temp = 0;  
    58.     double first, second;  
    59. }  
    60. {  
    61.     first=term(){temp = first;}  
    62.     (<ADD>second=term(){temp = first + second;}|  
    63.      <SUB>second=term(){temp = first - second;})*  
    64.     {return temp;}  
    65. }  
    66.    
    67. double term():  
    68. {  
    69.     double temp = 0;  
    70.     double first, second;  
    71. }  
    72. {  
    73.     first=factor(){temp = first;}  
    74.     (<MUL>second=factor(){temp = first * second;}|  
    75.      <DIV>second=factor(){temp = first / second;})*  
    76.     {return temp;}  
    77. }  
    78.    
    79. double factor():  
    80. {  
    81.     double temp = 0;  
    82.     Token token;  
    83. }  
    84. {      
    85.     token=<NUMBER>{  
    86.         return Double.parseDouble(token.image);  
    87.     } | <LPAREN> temp=expr() <RPAREN>{  
    88.         return temp;  
    89.     } |  
    90.     <SIN><LPAREN>temp=expr()<RPAREN>{  
    91.         return java.lang.Math.sin(temp);  
    92.     } |  
    93.     <COS><LPAREN>temp=expr()<RPAREN>{  
    94.         return java.lang.Math.sin(temp);  
    95.     }  
    96.     //如果有兴趣,可以加入更多的java.lang.Math中的数学函数,  
    97.     //当然,你也可以加入自己实现的一些方法,如返回前一个运算结果  
    98.     //记录历史信息等等。  
    99. }   

     

    演示计算过程,如:

    [juntao@juntao CalcParser]$ java CalcParser '${sin(1/2)*cos(1/4) + 12.3}'
    12.418611776418413
    [juntao@juntao CalcParser]$ java CalcParser '${(12+45)*(3-23)}'
    -1140.0
    [juntao@juntao CalcParser]$ java CalcParser '${3e-5}'
    3.0E-5
    [juntao@juntao CalcParser]$ java CalcParser '${sin(3/4)/2}'
    0.34081938001166706

     

     

    关于JJTree后边再说吧,最重要的还是上边提到的形式语言,它是一切的基础。


    这篇文章可以算是这篇小型桌面计算器的实现(javacc) 的续。

     

    可以这么说,使用javacc作分析器生成器,如果没有用到jjTree,那么就是对语义分析 的过程理解不够深入。如果用到了jjTree而且用好了,那么对编译原理,BNF等的理解才算是比较到位了。

    jjTree中最重要的概念是Node接口,所有的非终结符都可以规约 为一个节点。这个节点一般来讲是实现了Node接口的节点类
    其中主要有这样几个方法:

    Java代码  收藏代码
    1. ……  
    2.   /** This method tells the node to add its argument to the node's 
    3.     list of children.  */  
    4.   public void jjtAddChild(Node n, int i);  
    5.   
    6.   /** 返回孩子节点,下标从0开始,从左到右 */  
    7.   public Node jjtGetChild(int i);  
    8.   
    9.   /** 返回字节点个数 */  
    10.   int jjtGetNumChildren();  
    11.   
    12. ……  

     

    在本文所举的例子中,每个节点还要实现这样一个接口中声明的方法:

    Java代码  收藏代码
    1. void interpret()  

    这个方法中为每个节点具体的计算过程。

     

    jjTree处理的脚本(jjTree规则列表)以jjt结尾,这个文件通过jjtree工具可以生成.jj文件,然后用javacc编译.jj文件即可生成分析器生成器的java代码,然后于一些辅助解析的类进行编译,从而最终完成整个脚本引擎。

    jjTree有什么用? 
    这个是最核心的问题了,我们都知道jjTree的作用是为了将终结符 通过规则规约 成非终结符节点 。但是,规约的目的又是什么呢?其实,如果你的目标是简单的行解析器的话,根本不需要jjTree。但是,如果需要做一些比较有规模的脚本解析器,比如支持if,while等代码块的话,就需要解析器将这些临时的状态记录下来,那就必须用到jjTree了。

    加入了语法树 之后,解析器就需要做出一些修改了,比如需要加入全局的符号表,堆栈等数据结构,以方便规约出非终结符后再做动作时可以取出这些数据。当然,如果你的脚本引擎支持IO的话,这些全局的流描述符也应该和符号表,堆栈放在一起,比如一个单独的类中。

    涉及到使用jjTree的项目,即使是演示目的的,一般也比较大,所以,这篇文章中给出的都是一些片段,如果需要,我可以在blog中做一个小系列来说。

    比如,看一个例子:

    Java代码  收藏代码
    1. /** Conditional or expression. */  
    2. void ConditionalOrExpression() #void :  
    3. {}  
    4. {  
    5.   ConditionalAndExpression()  
    6.   ( "||" ConditionalAndExpression() #OrNode(2) )*  
    7. }  
     



    上边这段中 #void意思为当遇到ConditionalOrExpression规则时,不生成节点(不规约),而#OrNode(2)则表示如果发现有恰好2个ContionalAndExpression()规则,则规约到一个OrNode节点(需要在外部写一个ASTOrNode类)。

    而在ASTOrNode类中,会有下面的动作定义

    Java代码  收藏代码
    1. public void interpret()  
    2. {  
    3.    jjtGetChild(0).interpret();  
    4.   
    5.    if (((Boolean)stack[top]).booleanValue())  
    6.    {  
    7.       stack[top] = new Boolean(true);  
    8.       return;  
    9.    }  
    10.   
    11.    jjtGetChild(1).interpret();  
    12.    stack[--top] = new Boolean(((Boolean)stack[top]).booleanValue() ||  
    13.                               ((Boolean)stack[top + 1]).booleanValue());  
    14. }  
     


    在语法解析时,当规约出非终结符后,设置ASTOrNode父类的children的数组,然后在ASTOrNode先取出这个数组中的第一个Node进行
    递归解析,完成后取出第二个Node进行解析,最后,将这两个解析后的结果进行bool的或运算。整个过程很自然,计算过程放在外部的单独
    的类中进行。

    再看一个例子:

    Java代码  收藏代码
    1. /** A block. */  
    2. void Block() :  
    3. {}  
    4. {  
    5.   "{" ( Statement() )* "}"  
    6. }  
     


    用花括号括起来的一些代码表示一个块,怎么解析这个快呢?在ASTBlock中,有这样的运算过程:

    Java代码  收藏代码
    1. public void interpret()  
    2. {  
    3.    int i, k = jjtGetNumChildren();//取出代码块中的代码条数  
    4.   
    5.    for (i = 0; i < k; i++)  
    6.       jjtGetChild(i).interpret();//递归解析  
    7.   
    8. }  
     


    取出代码块中的代码条数,然后依次执行,如果遇到其他的Node,则递归调用这个Node上的interpret过程,从而执行整个代码块。

    希望这篇文章可以说明jjTree的运行机制,但是由于规模所限,有些地方可能还是不太清晰。欢迎留言,我会尽快在blog上写一个小的系列,谢谢!


    解释器(Interpreter)模式: 
    解释器模式是类的行为模式。给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器。客户端可以使用这个解释器 
    来解释这个语言中的句子。 

    一、解释器模式所涉及的角色 
    1、抽象表达式角色:声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称做解释操作。 
    2、终结符表达式角色:这是一个具体角色。 
    (1)实现了抽象表达式角色所要求的接口,主要是一个interpret()方法; 
    (2)文法中的每一个终结符都有一个具体终结表达式与之相对应。 
    3、非终结符表达式角色:这是一个具体角色。 
    (1)文法中的每一条规则 R=R1R2.....Rn 都需要一个具体的非终结符表达式类; 
    (2)对每一个 R1R2.....Rn 中的符号都持有一个静态类型为Expression的实例变量。 
    (3)实现解释操作,即 interpret()方法。解释操作以递归方式调用上面所提到的代表 R1R2.....Rn  中的各个符号的实 
         例变量 
    4、客户端角色:代表模式的客户端它有以下功能 
    (1)建造一个抽象语法树(AST或者Abstract Syntax Tree) 
    (2)调用解释操作interpret()。 
    5、环境角色:(在一般情况下,模式还需要一个环境角色)提供解释器之外的一些全局信息,比如变量的真实量值等。 

    (抽象语法树的每一个节点都代表一个语句,而在每一个节点上都可以执行解释方法。这个解释方法的执行就代表这个语句被解释。 
      由于每一个语句都代表对一个问题实例的解答。) 

    Java代码  收藏代码
    1. //抽象角色Expression  
    2.       /* 
    3.         这个抽象类代表终结类和非终结类的抽象化 
    4.         其中终结类和非终结类来自下面的文法 
    5.         Expression ::= 
    6.                 Expression AND Expression 
    7.                 | Expression OR Expression 
    8.                 | NOT Expression 
    9.                 | Variable 
    10.                 | Constant 
    11.         Variable ::= ....//可以打印出的非空白字符串 
    12.         Constant ::= "true" | "false" 
    13.       */  
    14.         
    15.       public abstract class Expression{  
    16.         //以环境类为准,本方法解释给定的任何一个表达式  
    17.         public abstract boolean interpret(Context ctx);  
    18.           
    19.         //检验两个表达式在结构上是否相同  
    20.         public abstract boolean equals(Object o);  
    21.           
    22.         //返回表达式的hash code  
    23.         public abstract int hashCode();  
    24.           
    25.         //将表达式转换成字符串  
    26.         public abstract String toString();  
    27.       }  
    28.         
    29.       public class Constant extends Expression{  
    30.         private boolean value;  
    31.           
    32.         public Constant(boolean value){  
    33.             this.value = value;  
    34.         }  
    35.         //解释操作  
    36.         public boolean interpret(Context ctx){  
    37.             return value;  
    38.         }  
    39.           
    40.         //检验两个表达式在结构上是否相同  
    41.         public boolean equals(Object o){  
    42.             if(o != null && o instanceof Constant){  
    43.                 return this.value == ((Constant)o).value;  
    44.             }  
    45.             return false;  
    46.         }  
    47.           
    48.         //返回表达式的hash code  
    49.         public int hashCode(){  
    50.             return (this.toString()).hashCode();  
    51.         }  
    52.           
    53.         //将表达式转换成字符串  
    54.         public String toString(){  
    55.             return new Boolean(value).toString();  
    56.         }  
    57.       }  
    58.         
    59.       public class Variable extends Expression{  
    60.         private String name;  
    61.         public Variable(String name){  
    62.             this.name = name;  
    63.         }  
    64.           
    65.         public boolean interpret(Context ctx){  
    66.             return ctx.lookup(this);  
    67.         }  
    68.           
    69.         public boolean equals(Object o){  
    70.             if(o != null && o instanceof Variable){  
    71.                 return this.name.equals(((Variable)o).name);  
    72.             }  
    73.             return false;  
    74.         }  
    75.           
    76.         public int hashCode(){  
    77.             return (this.toString()).hashCode();  
    78.         }  
    79.           
    80.         public String toString(){  
    81.             return name;  
    82.         }  
    83.       }  
    84.         
    85.       public class And extends Expression{  
    86.         private Expression left,right;  
    87.           
    88.         public And(Expression left,Expression right){  
    89.             this.left = left;  
    90.             this.right = right;  
    91.         }  
    92.           
    93.         public boolean interpret(Context ctx){  
    94.             return left.interpret(ctx) &&  
    95.                 right.interpret(ctx);  
    96.         }  
    97.           
    98.         public boolean equlas(Object o){  
    99.             if(o != null && o instanceof And){  
    100.                 return this.left.equals(((And)o).left) &&  
    101.                     this.right.equals(((And)o).right);  
    102.             }  
    103.             return false;  
    104.         }  
    105.           
    106.         public int hashCode(){  
    107.             return (this.toString()).hashCode();  
    108.         }  
    109.           
    110.         public String toString(){  
    111.             return "(" + left.toString() + "AND" + right.toString() + ")";  
    112.         }  
    113.       }  
    114.         
    115.       public class Or extends Expression{  
    116.         private Expression left , right;  
    117.           
    118.         public Or(Expression left,Expression right){  
    119.             this.left = left;  
    120.             this.right = right;  
    121.         }  
    122.           
    123.         public boolean interpret(Context ctx){  
    124.             return left.interpret(ctx) || right.interpret(ctx);  
    125.         }  
    126.           
    127.         public boolean equals(Object o){  
    128.             if(o != null && o instanceof Or){  
    129.                 return this.left.equals(((And)o).left) &&  
    130.                     this.right.equals(((And)o).right);  
    131.             }  
    132.             return false;  
    133.         }  
    134.           
    135.         public int hashCode(){  
    136.             return (this.toString()).hashCode();  
    137.         }  
    138.           
    139.         public String toString(){  
    140.             return "(" + left.toString() + "OR" + right.toString() + ")";  
    141.         }  
    142.       }  
    143.         
    144.        public class Not extends Expression{  
    145.         private Expression exp;  
    146.           
    147.         public Not(Expression exp){  
    148.             this.exp = exp;  
    149.         }  
    150.           
    151.         public boolean interpret(Context ctx){  
    152.             return !exp.interpret(ctx);  
    153.         }  
    154.           
    155.         public boolean equals(Object o){  
    156.             if(o != null && o instanceof Not){  
    157.                 return this.exp.equals(((Not)o).exp);  
    158.             }  
    159.             return false;  
    160.         }  
    161.           
    162.         public int hashCode(){  
    163.             return (this.toString()).hashCode();  
    164.         }  
    165.           
    166.         public String toString(){  
    167.             return "(NOT" + exp.toString() + ")";  
    168.         }  
    169.       }  
    170.         
    171.       import java.util.HashMap;  
    172.         
    173.       public class Context{  
    174.         private HashMap map = new HashMap();  
    175.         public void assign(Variable var,boolean value){  
    176.             map.put(var,new Boolean(value));  
    177.         }  
    178.           
    179.         public boolean lookup(Variable var) throws IllegalArgumentException{  
    180.             Boolean value = (Boolean)map.get(var);  
    181.             if(value == null){  
    182.                 throw new IllegalArgumentException();  
    183.             }  
    184.             return value.booleanValue();  
    185.         }  
    186.       }  
    187.         
    188.       //客户端  
    189.       public class Client{  
    190.         private static Context ctx;  
    191.         private static Expression exp;  
    192.           
    193.         public static void main(String args[]){  
    194.             ctx = new Context();  
    195.             Variable x = new Variable("x");  
    196.             Variable y = new Variable("y");  
    197.               
    198.             Constant c = new Constant(true);  
    199.             ctx.assign(x,false);  
    200.             ctx.assign(y,true);  
    201.               
    202.             exp = new Or(new And(c,x),new And(y,new Not(x)));  
    203.             System.out.println("x = " + x.interpret(ctx));  
    204.             System.out.println("y = " + y.interpret(ctx));  
    205.             System.out.println(exp.toString() + "=" + exp.interpret(ctx));  
    206.         }  
    207.       }  

    二、解释器模式适用于以下的情况: 
    (1)系统有一个简单的语言可供解释 
    (2)一些重复发生的问题可以用这种简单的语言表达 
    (3)效率不是主要的考虑。

  • 相关阅读:
    tee:结果输出到文件同时也作为往后的输入信息
    hexdump:查看文件头部信息,以十六制形式查看文件
    删除大文件方法
    rename:批量更改文件名
    求从1加到100的结果
    简书里面的面试题
    开源好网站
    ubuntu 14上安装mysql离线包
    单点登录原理与简单实现---转
    Revit API 判断一个构件在某个视图中的可见性
  • 原文地址:https://www.cnblogs.com/daichangya/p/12959589.html
Copyright © 2011-2022 走看看