脚本引擎的设计与实现(javacc) [一]

 

打算写一个系列,比较系统的介绍一下一个脚本引擎的设计和实现过程,本来打算使用lex/yacc来举例子,但是由于最近对java语言有了新的认识,故决定使用javacc这个工具来做,这个系列中就是以javacc中的一个比较复杂的例子来进行解说的。

这篇文章是本系列的第一篇,主要说几个概念的定义,有了这些定义,后边就容易理解了。

对一个语言的源文件进行解析,主要是做这样几件事:

  1. 词法分析
  2. 语法分析
  3. 语义分析

当然,每个步骤中都有自己的错误检查机制,这里暂且不表。下边分别对这几个过程进行解说:

词法分析

词法分析过程中,读入的是一个个的字符,返回的是记号(token).记号是语法分析所认识的一种抽象的概念。
语言中的关键字,变量名,数字,操作符,串等都是记号。

int x;
= 0;

上边这些代码,被词法分析器分析之后,应该生成这样的记号:

<int>,空格,<x>,分号,回车换行,<x>,<=>,<0>,分号

一般而言,在这个过程中,空白字符(空格,tab)都应该被跳过。

词法分析中会用到大量的正则表达式,因为正则表达式的表达能力是非常强大的。在此略微举几个例子,如对数字,变量名的定义,用正则表达式表示如下:

 

NUMBER ::= [0-9][0-9]*
VAR ::
= [a-zA-Z_][a-zA-Z0-9_]*

方括号"[]"表示一个区间,取这个区间中的任意一个字符,"*"表示零次或多次匹配,连字符"-"表示连接此连字符前后的一个区间,如0-9表示从0到9的任意一个字符。所以,NUMBER记号的正则表达式正好可以表示,以0-9中任意一个字符开头,后边跟任意多个0-9之间的数字(含0和9).而VAR的定义正如大部分语言要求的那样,以字母或者下划线开始,后边是任意多个字母或者数字或者下划线。如果需要限定VAR的长度为32位,则可以修改成这样:

 

VAR :: =  [a - zA - Z_][a - zA - Z0 - 9_] * { 31 }

语法分析

语法分析过程中,读入的是词法分析器返回的记号,返回的是规则(rule),这些规则是语义分析所需要的,在语法分析中,一般会使用BNF来表示规则,比如赋值语句是一个合法的语句,则在此处需要定义关于语法的BNF:

 

assignment ::= INT VAR EQ NUMBER END
INT ::
= "int"
VAR ::
= [a-zA-Z_][a-zA-Z0-9_]*
EQ 
= "="
NUMBER ::
= [0-9][0-9]*
END ::= ";"

就是说,如果读入了这样一个记号的序列:INT VAR EQ NUMBER END,则这个序列被识别成一个赋值的语句,但是,这个还是一个简单的规则,对于这个语句的意思还是不明确的,也就是说,虽然它可以认识所有的赋值语句,但是没有意义,意义的定义在语义分析部分。

 

BNF的表达能力也是非常强大的,比如一个四则混合语算的BNF可以定义如下:

 

expr :: =  term (( +|- ) term) *   
term ::
=  factor (( *|/ ) factor) *   
factor ::
=  number  |  expr   
number ::
=  [ 0 - 9 ] +

这个BNF可以支持乘除运算优先级高于加减法运算,括号优先级高于乘除法,且可以支持对四则混合运算的任意层次的扩展,比如:

 

( ( 12 + 3 ) / 6 ) + ( 1/5 + 155 ) 等的正确解析。

语义分析

在语义分析中,我们需要对在语法分析过程中得出的规则进行解释,比如赋值语句,我们需要将起翻译成:将NUMBER这个记号中的‘值’赋给名叫VAR的一个INT型变量。这个过程完成实际的语法解析,使得语言本身对外有意义。语义分析是脚本型的语言的最高级形式,即脚本解析到此为止将被转换成实际的语言内容并交付给宿主语言编译器进行编译并执行。又或者语义分析发现了一个语法分析过程中规约出来的四则运算表达式,那么它会根据语法中的规则对这个表达式进行求值,计算出最终结果来。

在这个系列中我们用到的一个语言叫做SPL(Stupid Programming Language),这是javacc中给它起的名字,我们不妨将其改名为SPL(Simple Programming Language),呵呵。

关于概念就解释到这里,如果你对正则表达式,BNF等概念不是很熟悉,可以先找找资料来学习。好了,第一篇先介绍这么多吧,后边再续。

 

你可能感兴趣的:(javac)