看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: ()+ > | < FLOAT_LITERAL: ()+ "." ()* ()? ()? | "." ()+ ()? ()? | ()+ ()? | ()+ ()? > | < 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() )* } /** Variable declaration. */ void VarDeclaration() : { Token t; } { ( "boolean" { jjtThis.type = BOOL; } | "int" { jjtThis.type = INT; } | "float" { jjtThis.type = FLOAT; } | "char" { jjtThis.type = CHAR; } ) t = { 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 = { jjtThis.name = t.image; } } /** A literal. */ void Literal() #void : { Token t; } { ( t= { jjtThis.val = Integer.parseInt(t.image); } )#IntConstNode | BooleanLiteral() | ( t= { 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 : {} { ":" 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 = { jjtThis.name = t.image; } } /** A write statement. */ void WriteStatement() : { Token t; } { "write" t = { jjtThis.name = t.image; } } TOKEN : /* IDENTIFIERS */ { < IDENTIFIER: (|)* > | < #LETTER: [ "a"-"z", "A"-"Z" ] > | < #DIGIT: [ "0"-"9"] > | < #FLOAT_SUFFIX: ["f","F","d","D"] > | < #EXPONENT: ["e","E"] (["+","-"])? ()+> }
经过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 jj_expentries = new java.util.ArrayList(); 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 jj_expentries = new java.util.ArrayList(); 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循环,更多的运算符,更多的常用数学函数,打造出更好的一个解释器拿来玩儿.