论坛首页 Java企业应用论坛

写一个自己的动态语言吧。 初学乍练的。

浏览 10098 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (8) :: 隐藏帖 (5)
作者 正文
   发表时间:2010-02-10  

接昨天

 

     if else  或者 switch 构成的 状态机 非常简陋。  状态越来越多的时候变得乱七八糟 。 所以要分解包装一下。

 

     状态机行为就是 :

 

                  ----输入---->switch----改变状态--->

 

      在游戏编程  Ai设计中经常用到 ,不过我们这里只主要是谈编译 ,所以输入就变成了 一个字符  输出为一个状态。 一直说“状态” 这个东西 那就把它搞成一个类吧

 

 

        class State{
        StateType type = StateType.BEGIN;

	public State nextState(char i){
		return null;
	}
}

 

nextState 就是主要部分了 输入为一个字符串 输出也就是返回值 为一个State。 看看如何用这个State来识别一个 整型数字 例如 1000 100 99 .....

 

建立一个IntState 然后重载 State:

 

 

 

  class IntState extends State{
      public State nextState(char c){
            if(c >= '0' && c <= '9'){
                  return this;
            }else{
                  return null;
            }
      }
}
 

 

 

这个比较简单 可以生成一个 IntSate  intState =  new IntState()  看看它如何工作

 

 

  String prostr  = "192321 2312 3243";
  String buff = "";
 for(int i=0 ; i<prostr.length() ; i++){
        char c = prostr.charAt(i);
        State st = intStat.nextSate(c);
         buff += c;
        if(st == null){
             print buff;
        }
}

 

 

其结果就是把字符串中的整型数字给 挑出来了。 这看起来很容易不是么? 等等如果它没有split 强大费这种力气干么? 实际上程序文件包含的字符串比这复杂的多,有int  float string 变量 关键字 各种符号.... ....................木有关系 统统没有关系 每一个建立一个state即可

 

如 floatState stringState  看看如何编写 ,这里只写出nextState 函数 它们都是继承自State类

 

 

			
     floatSate:
                      public State nextState(char i){
				if(isDigit(i)){
					return floatSt;
				}else if(i == '.' || isAlpha(i)){  // 浮点数状态下 输入“.”  和字符 返回deny状态 说明词法错误,这里也可以做成抛异常
					return deny;
				}else{
					return null;  //如遇到空格 或者其他的字符 则说明词法完成 顺利通过
				}
			}
     spaceState     识别空白的字符

                      public State nextState(char i){
				if(isSpace(i)){
					return spaceSt;
				}else{
					return null;
				}
			}

 

 常用的函数 

 

	public static boolean isAlpha(char i){
		return (i>='a' &&  i<='z') || (i>='A' && i<='Z');
		}
		
	public static boolean isDigit(char i){
		return (i>='0' &&  i<='9') ;
		}
		
	public static boolean isSpace(char i){
		return (i==' ' || i=='\r' || i=='\n' || i=='\t') ;
		}

 

 识别 单个的符号 更简单了 直接用State 生成一个不用继承,然后把它放入到一个字典里 在扫描程序字符串的时候可以快速查到。如下

 

 

map.put("(", new State(StateType.LPARENT));

 

 

这里还需要一个开始的状态 ,编译开始时 从一个字符开始扫描 。第一个字符有可能是 整型数字 也可能是字母 也可能 变量。 可以如下编写

 

 

public State nextState(char i){
				if(isAlpha(i) || i == '_'){    //如第一个字符遇见字母 ,那么接下来就返回 identState ,它用来识别变量和关键字这类的。
					return identSt;
				}else if(isDigit(i)){           //如是数字 看开头是0打头的 有可能是0x 也可是8位的 ,如果不是0 那就是整型的。当然整型半截可能遇见“.”
					if(i == '0'){              //在整型状态下遇见"." 就返回floatState 当作float识别。 这里用了子状态 。其实可以多生成些状态不要子
						intSt.subtype = 8160;  //状态
					}else{
						intSt.subtype = 10;
					}
					return intSt;
				}else if(i == '\"'){  // 引号出现说明后边是 字符串
					return strSt;
				}else if(isSpace(i)){
					return spaceSt;
				}else {              // 如果是其他符号 看看是不是单个的 {} []: ,. 之类的
					return smap.get(String.valueOf(i));
				}
			}

 

 

 

    一旦一个状态识别完 就会返回null 说明一个词义被确认 而且这个词是整型 还是浮点型 还是字符串 ... 都是最后的那个状态类型。 这时候可以根据类型 把字符串转化为java 的类型了。 

 

   其中遇到的一些 小问题 。如  第一个字符是 0 ,就有几种可能  十六进制0x7873  浮点数0.423  八进制0632,如何确定 用intSate  还是用floatState ?

这里我程序里是假定是整型先 如遇到“.” 状态机返回floatSate这样就跳到了浮点型识别, 在区分 八进制还是十六进制的时候 用了子状态 。其实这么做不好。最好引入中间状态 如叫做 zeroState   .在这个状态里就可以区分到底是用那个状态机去识别。  因为再往后一个字符就可以确定是什么类型 如果是‘x’就是十六位整型。 

 

   这种情况很常见,就是一个字符出现不能确定状态该怎么走 ,方法就是引入中间状态。  最后实现了 每个状态输入一个字符 就可以确定下一个状态,这也是为什么叫做有限确定状态机 DFA 。一个输入可能对应多个状态的叫做非确定状态机 NFA 。很恶心的书上把简单的概念都写得抽象的吓人,真不知道为啥?为了严谨? 写书给人看的 搞的跟给机器写书一样 。 隐隐约约记得书上像些咒语一样 写着如何把NFA 转为DFA .....  压根我看不明白。

 

   状态机的应用还很多 。其实正则就是状态机实现的 。有的是用NFA 有的是用DFA规则。 表现的区别 如匹配 (aaa|bbb|cdd)  。

 

字符串 : abcd

DFA 过程:  先看和 aaa 匹配么再和bbb匹配 在和ccc匹配

NFA过程: 先看abcd的第一个字符 和 aaa bbb ccc 那个匹配 。然后在看第二个字符b ...

有点类似一个深度优先 一个是广度优先。

 

  今天到这里 待续

  大家过年好,春节快乐。

 

  给出词法分析的代码

 

 

 

 

 

 

 

 

0 请登录后投票
   发表时间:2010-02-11  
现在的人越来越喜欢作秀了,且不说有啥气候
0 请登录后投票
   发表时间:2010-02-11  
自定义一些脚本语言很多项目是有好处的. 比如SQL parser及审计工作, 监控的规则编写.  我们用到了很多的自定义的语言, 或者parser脚本的工具.
0 请登录后投票
   发表时间:2010-02-11  
至少也用用正则吧...
0 请登录后投票
   发表时间:2010-02-11  

接昨天

 

到目前为止 已经有了一个词法分析的东东。 还不完善 不过已经可以把大部分程序里出现的有意义的字符识别出来了。例如:

 

 

		test = "int a = 5.234; float b = 3;<\n"
			+ "! <= += >= !=8377243.324324 () 3244\r\n"
			+ " 432 rewrew-  \"fdsfdfds\"\n"
			+ " 09\n"
		        + "//fdafdfd 843728 fdfdfd \n"
		        + "fdfdsfd ffff";

 

 被分解后输出为:

 

int		INTEGER_TYPE
a		IDENT
=		ASSIGN
5.234		FLOAT
;		SEMI
float		FLOAT_TYPE
b		IDENT
=		ASSIGN
3		INTEGER
;		SEMI
<		LT
!		NOT
<=		LE
+=		PLUS_A
>=		GE
!=		NE
8377243.324324		FLOAT
(		LPARENT
)		RPARENT
3244		INTEGER
432		INTEGER
rewrew		IDENT
-		MINUS
fdsfdfds		STR
------------------err-----------------------   //有了一点词法分析报错的 功能。 显示什么位置出现了错误
unexpect char "9" 
at line : 4 col :3
------------------errend--------------------
0		INTEGER
//fdafdfd 843728 fdfdfd 		SKIP
fdfdsfd		IDENT
ffff		IDENT

 

 

 

尽管上边的字符串不是合法的语句 ,但是词法分析不关系语句是否合法 ,只是把相应的有意义的字符串分解出来即可。 

 

 

 

语法分析比较难点  ,我也刚看不久。 表述不清楚的地方 大家查查资料吧。

 

首先 我们看看如何解析 数学表达式。 这是最简单的 也是最复杂的一种语法了。 简单的 如  2+4     4-2   .... 等等 

 

好现在可以写一个简单的程序处理这些了。

 

 

  express(){
      
      int left ,right  , result;
  
      Token tk = getNextToken();
       
       if(tk.type == INT){
             left = tk.value;             
       }
        tk = getNextToken();

       if(tk.type == ADD){
               tk = getNextToken();
              right = tk.value;
              result = left + right;
       }else if(tk.type == MINUS){
              tk = getNextToken();
              right = tk.value;
              result = left - right;
       }

}
 

 

很简单 出来了 计算 加减的表达式。  可是这个表达式比较弱 只能计算 两个数的加法减法。 改进一下。

 

 

 express(){
      
      int left ,right  , result;
  
      Token tk = getNextToken();
       
       if(tk.type == INT){
             left = tk.value;             
       }
        while(tk = getNextToken() != null){  //这里加入个循环 ,即让后边在出现 + - 的运算都进行下去

       if(tk.type == ADD){
               tk = getNextToken();
              right = tk.value;
              result = left + right;
       }else if(tk.type == MINUS){
              tk = getNextToken();
              right = tk.value;
              result = left - right;
       }
}

}
 

 

加入了循环处理 这就让  他可以处理 4+3-32+23-0  .... 的运算。

 

下一步加入 × /  运算。 这个比较麻烦的地方就是 。如何才能是乘除先运算  加减后运算?  

 

看这个 例子:     a + c*d

 

可以把 c*d 看作是一个整体 ,原来的表达式 ====》  a + b   ;  b ====> c*d

 

上边那个express() 函数 只能计算加减 。 可以计算  a +b  这部分。 但现在b 不是一个具体的数字了  ,而是一个 需要求解的未知值。

 

我们可以把b当作一个函数 ,这样表达式就变为了  a + b()  .在计算这个表达式之前 就得先计算b这个函数值 。 这样就体现了 × / 优先于 +- 计算了。 

 

糊涂了?  还是看代码吧 ,文字还是不如代码容易理解。

 

 

express(){
      
      int left ,right  , result;
  
      Token tk = getNextToken();
       
       if(tk.type == INT){
             left = tk.value;             
       }
        while(tk = getNextToken() != null){  //这里加入个循环 ,即让后边在出现 + - 的运算都进行下去

       if(tk.type == ADD){
              right = b();       //注意这里的变化
              result = left + right;
       }else if(tk.type == MINUS){
              right = b();      //注意这里的变化
              result = left - right;
       }
}

}
 

 

原来的right的值 直接取一个token就得到了 。这里变为了 调用一个叫做b函数。  现在我们假定了 后边跟着一个 c *d 运算 。那么b 函数这么写

 

 

    

int b(){
     int left ,right  , result;
   
      Token tk = getNextToken();
       
       if(tk.type == INT){
             left = tk.value;             
       }
        while(tk = getNextToken() != null){  

       if(tk.type == MULTIPLY){
              right = getNextToken().value;       
              result = left * right;
       }else if(tk.type == DIVIDE){
              right = getNextToken().value;       
              result = left /right;
       }
}
 

这个上边算加减的函数差不多  。到这里虽然可以计算 想 a + c *d 这类的表达式  可是现在也只能计算这样的表达式 。换一种写法就不行了。写成 c*d +a 就歇菜了。

 

想来想去 发现这么着是不行的。 继续顺着上边的思路走。 a + c*d ==> a + b ; b ==> c*d   你会发现  。a 也可能是一个表达式啊。  a 可能是 a ==> e + f 。那

 

e 呢? f呢 都可能是表达式, 感觉很混乱 ,还好 有人帮你整理了这中混乱, 于是就有了BNF 这种东西   .  先看看 BNF 是如何处理这中问题的。 

 

 

BNF 的思路和上边的思考过程 很像 不过更科学 。看看bnf 怎么描述 表达式的。

 

express === >   A + express | A - express | A

 

| 代表 或者的关系 。 A 可以理解为一个返回确切数字的函数 。 汉语意思可以说 表达式 是一个数字 + 或者 - 一个表达式  或者 就一个确切数字组成的。

 

这是个递归的说法 ,似乎看不到底 但是有一点很重要 它已经表达了有加 减。

 

现在开始定义 A 了 ,这里把A改名 叫做 term  因为这是通常都这么叫

 

express === >   term + express | term - express | term

 

把这个翻译成程序就是 

 

 

  int   express () {
        int left = term();
        Token tk = getNextToken();

        //express ==> term + express | term - express
        if(tk.type == PLUS || tk.type == MINUS){
              int right = express();
              return  left + - right;
        }else { putBack (tk )} //上边测试是不是×/ 不是的话吧这个token放回去 。这里很重要。 也很难说清楚流程 开代码 或者跟代码更容易明白
         // express == > term();  因为上边已经求出 left = term(); 所以直接返回left 
         return left

}
 

 

    现在定义 term ==> factor * term | factor / term | factor

 

简直跟express 一模一样的定义 ,这次定义了 乘除的运算。 可以看到 问题变得月来越小了

 

 

 

 

 int   term () {
        int left = factor();
        Token tk = getNextToken();

        //term ==> factor * term | factor / term
        if(tk.type == MULTIPLY || tk.type == DIVIDE){
              int right = factor();
              return  left  * / right;
        }else { putBack (tk )} //上边测试是不是×/ 不是的话吧这个token放回去 。这里很重要。 也很难说清楚流程 开代码 或者跟代码更容易明白
         // express == > term();  因为上边已经求出 left = term(); 所以直接返回left 
         return left

}

 

 

这次出现了 factor  。 factor 如何定义? 再往下分解 就是 数字了 factor ==> num | - num

 

int factor(){

    Token tk = getNextToken();
    if(tk.type == MINUS){
         return  - tk.value
    }
    return tk.value;
}

 

BNF主要是用了递归的方式 分解问题 。 

 

 

写了这么多 发现 还是看程序更容易理解  还是上程序吧 。。。 下边的程序计算一个 表达式  ,包括 括号 和 正负号 求模 功能。

 

 

 

0 请登录后投票
   发表时间:2010-02-12  
babykaokao 写道
我用java写过一个,很简单,用正则表达式把源码分割,然后就是根据语法规则和运算符的优先级进行运算就好了。


当运算量大的时候,用正则会有效率问题吧
0 请登录后投票
   发表时间:2010-02-12  
babykaokao 写道
我用java写过一个,很简单,用正则表达式把源码分割,然后就是根据语法规则和运算符的优先级进行运算就好了。

你是说你用java实现了一个词法分析器吧,词法分析貌似比较简单啊
0 请登录后投票
   发表时间:2010-02-13  
楼主讨论的东西还是有一定价值的,有兴趣可以看看我做的这个东东,然后大家可以交流一下,http://linliangyi2007.iteye.com/blog/337069
0 请登录后投票
   发表时间:2010-02-15  
linliangyi2007 写道
楼主讨论的东西还是有一定价值的,有兴趣可以看看我做的这个东东,然后大家可以交流一下,http://linliangyi2007.iteye.com/blog/337069

大牛出现  呵呵 。 前一阵子就参看过你的程序了
0 请登录后投票
   发表时间:2010-02-25  
我早就有自己写个动态语言的相法,语法规则像JSON那样,灵活又简洁,可编译原理学得不好,还只能写Lexer和Parser。有空的话和LZ切磋切磋,怎么样?QQ:307609641 email:g.johnsonlee@gmail.com.

下面是我写的解析JSON语法的代码:http://johnson-lee.iteye.com/blog/586686
望各位拍砖......
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics