看Java有一阵子了,除了开发Android程序外,自己又开始动手写一些乱七八糟的东西,这次为自己带来了一个手机端C语言解释器。

 

话不多说上图:

 

 

JavaCC(Java Compiler Compiler..囧)为Java平台广泛应用于词法和语法分析的工具,类似于C语言的lex和yacc,可以翻译为纯Java代码,是用Java实现编译器和解释器的一个很好的工具.同时java还有另一个此法语法分析工具是Antlr.

JavaCC项目网站: https://javacc.dev.java.net/

JavaCC原Sample中的Interpreter项目主要目的为了解释JJTree的语法树生成器.其中包含了一个类C语言(被称为SPL: Stupid Programming Language)的语法文件,从而实现其解释器。其中实现了很多简单的语句,简单到只有int和bool类型,赋值语句和条件和while循环语句,read和write关键字用来输出。例子可以从JavaCC项目中得到.

我借其语法文件一用,加入了float类型和C语言的一样的注释方式。改写一部分代码并在JDK1.6上运行。后来加了写界面移植到J2ME,家变成现在的样子。其中遇到很多头痛的问题下面会进行说明。

项目地址: http://code.google.com/p/c2me/

 

 

下面开始简要说明开发过程:

 

一、JavaCC Eclipse插件:http://eclipse-javacc.sourceforge.net/

可以在eclipse上自动解析并翻译.jj或者.jjt语法文件为java本地解析代码。(其中jj文件为词法分析的文件,而jjt为语法树分析文件.两个内容几乎相同,但是jjt可以建立语法树,这样才能方便我们的解释器工作)

借用其官方图片来说明新JavaCC项目建立过程: 

 

建立JJTree项目以后

 

图示即为jjt语法文件.

 

 

经过我修改过的jtt文件如下(可在项目源码中找到)

options {
    //STATIC = true;
    MULTI = true;
    NODE_EXTENDS="MyNode";
    TRACK_TOKENS=true;
}

PARSER_BEGIN(SPLParser)

/** Stupid Programming Language parser. */
public class SPLParser {

  /**
   * Returns the root node of the AST.  
   * It only makes sense to call this after a successful parse. 
   * @return the root node
   */
  public Node rootNode() {
    return jjtree.rootNode();
  }  

}

PARSER_END(SPLParser)


SKIP : /* WHITE SPACE */
{
  " "
| "/t"
| "/n"
| "/r"
| "/f"
|  <"//" (~["/n","/r"])* ("/n" | "/r" | "/r/n")>
|  <"/*" (~["*"])* "*" ("*" | ~["*","/"] (~["*"])* "*")* "/">
}

TOKEN : /* Types */
{
   < INT: "int" >
 |
   < BOOL: "boolean" >
 |
   < FLOAT: "float" >
 | < CHAR : "char" >
}

TOKEN : /* LITERALS */
{
  < INTEGER_LITERAL: (<DIGIT>)+ >
| < FLOAT_LITERAL: (<DIGIT>)+ "." (<DIGIT>)* (<EXPONENT>)? (<FLOAT_SUFFIX>)? | "." (<DIGIT>)+ (<EXPONENT>)? (<FLOAT_SUFFIX>)? | (<DIGIT>)+ <EXPONENT> (<FLOAT_SUFFIX>)? | (<DIGIT>)+ (<EXPONENT>)? <FLOAT_SUFFIX> >
| < CHAR_LITERAL: "/'" (~["/'","//","/n","/r"] | "//" (["n","t","b","r","f","//","/'","/""] | ["0"-"7"] (["0"-"7"])? | ["0"-"3"] ["0"-"7"] ["0"-"7"])) "/'">
}

/*
 * Program structuring syntax follows.
 */

/** Compilation unit. */
void CompilationUnit() :
{
   //String name;
}
{
   (
       VarDeclaration() ";"
     |
       Statement()
   )*
   <EOF>
}

/** Variable declaration. */
void VarDeclaration() :
{ Token t; }
{
  (
    "boolean" { jjtThis.type = BOOL; }
  |
    "int" { jjtThis.type = INT; }
  |
    "float" { jjtThis.type = FLOAT; }
  |
    "char" { jjtThis.type = CHAR; }
  )
  t = <IDENTIFIER> 
  { jjtThis.name = t.image; }
}

/*
 * Expression syntax follows.
 */

/** Expression. */
void Expression() #void:
{}
{
  LOOKAHEAD( PrimaryExpression() "=" )
  Assignment()
|
  ConditionalOrExpression()
}

/** Assignment. */
void Assignment() #Assignment(2) :
{}
{
  PrimaryExpression() "=" Expression()
}

/** Conditional or expression. */
void ConditionalOrExpression() #void :
{}
{
  ConditionalAndExpression()
  ( "||" ConditionalAndExpression() #OrNode(2) )*
}

/** Conditional and expression. */
void ConditionalAndExpression() #void :
{}
{
  InclusiveOrExpression()
  ( "&&" InclusiveOrExpression() #AndNode(2) )*
}

/** Inclusive or expression. */
void InclusiveOrExpression() #void :
{}
{
  ExclusiveOrExpression()
  ( "|" ExclusiveOrExpression() #BitwiseOrNode(2) )*
}

/** Exclusive or expression. */
void ExclusiveOrExpression() #void :
{}
{
  AndExpression()
  ( "^" AndExpression() #BitwiseXorNode(2) )*
}

/** And expression. */
void AndExpression() #void :
{}
{
  EqualityExpression()
  ( "&" EqualityExpression() #BitwiseAndNode(2) )*
}

/** Equality expression. */
void EqualityExpression() #void :
{}
{
  RelationalExpression()
  (
     "==" RelationalExpression() #EQNode(2)
   |
     "!=" RelationalExpression() #NENode(2)
  )*
}

/** Relational expression. */
void RelationalExpression() #void :
{}
{
  AdditiveExpression()
  (
    "<" AdditiveExpression() #LTNode(2)
   |
    ">" AdditiveExpression() #GTNode(2)
   |
    "<=" AdditiveExpression() #LENode(2)
   |
    ">=" AdditiveExpression() #GENode(2)
  )*
}

/** Additive expression. */
void AdditiveExpression() #void :
{}
{
  MultiplicativeExpression()
  (
    "+" MultiplicativeExpression() #AddNode(2)
   |
    "-" MultiplicativeExpression() #SubtractNode(2)
  )*
}

/** Multiplicative expression. */
void MultiplicativeExpression() #void :
{}
{
  UnaryExpression()
  (
    "*" UnaryExpression() #MulNode(2)
   |
    "/" UnaryExpression() #DivNode(2)
   |
    "%" UnaryExpression() #ModNode(2)
  )*
}

/** Unary expression. */
void UnaryExpression() #void :
{}
{
  "~" UnaryExpression() #BitwiseComplNode(1)
|
  "!" UnaryExpression() #NotNode(1)
|
  PrimaryExpression()
}

/** Primary expression. */
void PrimaryExpression() #void :
{
   //String name;
}
{
  Literal()
|
  Id() 
|
  "(" Expression() ")"
}

/** An Id. */
void Id() :
{
   Token t;
}
{
   t = <IDENTIFIER>  { jjtThis.name = t.image; }
}

/** A literal. */
void Literal() #void :
{
   Token t;
}
{
 (
  t=<INTEGER_LITERAL>
    {
       jjtThis.val = Integer.parseInt(t.image);
    }
 )#IntConstNode
|
  BooleanLiteral()
|
  (
    t=<FLOAT_LITERAL>
    {
      jjtThis.val = Float.parseFloat(t.image);
    }
  )#FloatConstNode
| (
    t = < CHAR_LITERAL >
    {
      jjtThis.val = 
      t.image.
      replace("//n","/n").
      replace("//r","/r").
      replace("//t","/t").
      replace("//0","/0").
      charAt(1);
    }
  )#CharConstNode
}

/** A boolean literal. */
void BooleanLiteral() #void :
{}
{
  "true" #TrueNode
|
  "false" #FalseNode
}


/*
 * Statement syntax follows.
 */

/** A statement. */
void Statement() #void :
{}
{
  ";"
|
  LOOKAHEAD(2)
  LabeledStatement()
|
  Block()
|
  StatementExpression()
|
  IfStatement()
|
  WhileStatement()
|
  IOStatement()
}

/** A labeled statement. */
void LabeledStatement() #void :
{}
{
  <IDENTIFIER> ":" Statement()
}

/** A block. */
void Block() :
{}
{
  "{" ( Statement() )* "}"
}

/** A statement expression. */
void StatementExpression() :
/*
 * The last expansion of this production accepts more than the legal
 * SPL expansions for StatementExpression.
 */
{}
{
  Assignment() ";"
}

/** An if statement. */
void IfStatement() :
/*
 * The disambiguating algorithm of JavaCC automatically binds dangling
 * else's to the innermost if statement.  The LOOKAHEAD specification
 * is to tell JavaCC that we know what we are doing.
 */
{}
{
  "if" "(" Expression() ")" Statement() [ LOOKAHEAD(1) "else" Statement() ]
}

/** A while statement. */
void WhileStatement() :
{}
{
  "while" "(" Expression() ")" Statement()
}

/** An IO statement. */
void IOStatement() #void :
{
  //String name;
}
{
   ReadStatement()
 |
   WriteStatement()
}

/** A read statement. */
void ReadStatement() :
{ Token t; }
{
   "read" t = <IDENTIFIER>
   { jjtThis.name = t.image; }
}

/** A write statement. */
void WriteStatement() :
{ Token t; }
{
   "write" t = <IDENTIFIER>
   { jjtThis.name = t.image; }
}

TOKEN : /* IDENTIFIERS */
{
  < IDENTIFIER: <LETTER> (<LETTER>|<DIGIT>)* >
|
  < #LETTER: [ "a"-"z", "A"-"Z" ] >
|
  < #DIGIT: [ "0"-"9"] >
|
  < #FLOAT_SUFFIX: ["f","F","d","D"] >
|
  < #EXPONENT: ["e","E"] (["+","-"])? (<DIGIT>)+>
}

经过Eclipse和JavaCC插件编译后自动生成了jj和java文件.Eclipse会报告一些错误和警告,包括Package没有写,或者用到了Stack或者ArrayList容器但是没有声名所存储的模板类型,不用鸟他。运行就好了。

要说明生成的代码结构和各个文件所起到的作用不是很简单,请参考这些链接:

 

小型桌面计算器javacc的实现 http://mopishv0.blog.163.com/blog/static/54455932201081393726584/

还有一个链接是JavaEye的..悲剧的down掉了.. http://abruzzi.javaeye.com/

 

 

有了代码,我们run一下就可以得到一个命令行的解释器了。

输入像这样的类似C的代码就可以运行了

int n;
n = 0;
while(n < 10) { //没有for..
  n=n+1;   //没有自增运算
  write n;
}

 

二、移植到J2ME平台

J2ME是J2SE的一个子集,去除了一些容器和很多平台相关内容,加入了J2ME专有的界面处理API和移动设备API。在不是很老的API中还是有float类型和经过包装的Float类的(让人欣慰..),同时让人欣慰的是java.util中包含了List和Stack容器类型.这样使我们移植过程中复杂度大大降低了.但是遇到一些实际的实现还是修改一些代码,比如这样的一个List容器移植到J2ME就要换种写法

 

private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();

jj_expentries.add(jj_expentry);
jj_expentries.size()
jj_expentries.get(i)
jj_expentries.clear();

private ArrayList marks = new ArrayList();
//我奇怪为啥不用Stack...里面的操作无外乎就push pop size..xxx的



public Node popNode() {
    if (--sp < mk) {
      mk = marks.remove(marks.size()-1);
    }
    //这边也很囧
    return nodes.remove(nodes.size()-1);
}

要修改成

private java.util.List<int[]> jj_expentries = new java.util.ArrayList<int[]>();

jj_expentries.add(jj_expentry);
jj_expentries.size()
jj_expentries.elementAt(i)
jj_expentries.removeAllElements();

private Stack marks = new Stack();
//我改成了Stack,J2ME有..嘿嘿


public Node popNode() {
    if (--sp < mk) {
      mk = marks.pop();
    }
    return nodes.pop();
}

 

其中还有些问题,比如Stack等等容器最好是Object放进去,要修改响应数据,将其从源类型变为Object,在取出来的时候转换为所用类型,还有标准输出流OutputStream在J2ME里面没有了 只能替代成ByteArrayOutputStream并且转换到String再放到TextView中输出.其间历尽千辛万苦,终于修成正果。

最后加上了J2ME的界面,用到了TextView和buttons,加上事件,大功告成。

接下来就如项目中所说的TODO list一样了.准备加入for循环,更多的运算符,更多的常用数学函数,打造出更好的一个解释器拿来玩儿.