& Q) M. _' f; h: z
这一篇文章开始讲述如何实现一个高级语言的脚本引擎了。由于工程量较为庞大,因此将分开几篇文章讲。学习做脚本还是要从简单的东西做起的。上一篇文章介绍的命令脚本为实现高级语言的原理做了铺垫。首先,高级语言和低级语言脚本的架构是一致的。其次,为了具有较大的优化的空间,我们将把高级语言转换成低级语言,并配合一个低级语言的脚本引擎来实现高级语言的脚本引擎。当然,习惯上,在这种情况下我们把低级语言叫『指令』。+ N f# r M& F" r! y
( V% C2 v8 J3 S. v3 G: G r+ p在这个阶段,我们实现的这门语言是非惰性计算的、弱类型的、仅支持基本类型、数组和函数指针的语言。作为扩展,隐式类型转换和函数重载也将包含在这几篇文章的主题中。好了,开始介绍语法吧。# W- q" X8 W4 m, `9 N
2 k; t6 I c2 C- A) g" o为了免去分析C语言函数指针声明的一堆麻烦问题,在这里我借用了pascal的语法。我们将构造出一门非常类似pascal的语言出来。0 R$ b1 U8 [5 e; R0 D
, ]1 }) e8 i! S) M# r9 }' V" M- n7 t& ]( u 文件结构: $ ?0 s5 z. Y% |" G我们将实现的高级语言脚本是支持多文件的。脚本引擎总是需要外部函数的。为了方便的让宿主程序提供外部函数的声明,因此我们做成了多文件的脚本引擎。也就可以有类似C语言#include那样子的东西了。pascal有一个奇怪的注释规则:使用大括号注释。 ; P7 q8 |& f( ]; c2 S" b) M7 h k# K8 h. g
结构如下:9 ~! |; G( j- G$ _. o
unit 单元名;/ s7 F% [4 j6 g& b2 l$ l- x
* c: h: R' i3 \
uses 单元名1,单元名2,……;, c7 j x5 j. x3 d5 g# _0 H3 g, u
5 Y8 n$ |9 |' k( j) G+ @7 U2 btype # I# C( f0 F- X# n& s新类型名称=类型声明; : \* s! U$ z' S/ A$ O4 L…… r3 A" ]& P6 G$ H* j + d' M% A' W6 A# j7 R. Yvar6 s2 m2 u3 J) T+ d( x: ]+ y& W, r( ]# N
变量名组:类型;4 r9 l2 B$ C+ a
……7 X* |$ V& L$ O! a+ ?5 c% D( U& p
) E1 ~4 I( l8 O; L
interface r P9 ?/ Z. a; ]3 q
公开的函数声明;( J g) O# \. o$ R9 ]( k' l$ ^
$ S, q: U9 P g
implementation# Q+ x$ n/ Q# i5 q9 p6 q. s: R
公开和非公开的函数实现(非公开函数不需要声明) ( m& H9 S; [% X' H5 n) send. 1 d# A# W/ X5 o1 r5 J: ` 0 N0 E% J4 f- c W对于语言本身来说,type和uses最好应该属于interface和implementation的。不过我们为了方便,姑且就这么做吧。不然的话,既不能揭示更多的原理,又给自己添麻烦。 ' t; I. D7 b+ U/ k7 p7 Z4 h8 I: P0 h: w+ ?2 y' `1 I' c 类型声明: ; c7 W+ f$ g+ N; P- Y: |1 y类型声明有普通类型、数组类型和函数指针。 ' B- p. M& o! p# j, y f* `普通类型有boolean、integer、real、char和string。3 o- h. X3 ~! U, |) j3 G' R
数组类型的声明方法是array of 类型。 ) z7 u, G _4 I# ]% o* L0 T0 N! `( Q函数指针的声明方法跟函数声明一致,唯一的区别是函数指针不可出现函数名。譬如我们需要一个输入两个整数输出一个整数的函数指针,我们写:) O' @5 y2 r9 d- c% ^* H$ A
type MyPointer=function(a,b:integer):integer; 4 M! [, w0 l& h+ A4 q& m0 M- r ; y, W5 _) Y9 k- P. [. {函数声明: : S% M9 W5 u! C: l! ?pascal的函数根据有没有返回值的区别使用不同的语法。基本语法如下: 6 c6 O/ D# M% ?; z1 j2 Fprocedure 函数名(参数表)和function 函数名(参数表):返回类型2 J1 M/ _: b; s- r) x
参数表的语法:[var]参数名组1:类型; [var]参数名组2:类型;……[var]参数名组n:类型。其中参数名组可以为多个用逗号隔开的参数名,也可以仅为一个参数名。其中var代表引用参数。 0 N* C% y4 B" V" z4 I: b5 p* H, ]* X3 h8 F8 o& f 函数实现: 6 G4 E0 u, G" t- k q7 P# K- T, v函数实现的语法由函数声明、分号、可选的变量声明、语句、分号构成。其中变量声明由var开头,后面街上多个“变量名组:类型;”构成。 & s# ~3 |, I; @9 l: f8 \! F8 P X; e% r \! c 语句: # \; ~+ I. B! F一般语句:表达式、new 类型[长度]2 q. |4 {4 R3 p
赋值语句:变量名:=表达式: B3 G s4 O7 ~! G. s1 ]1 W* X
分支语句:if 布尔表达式 then 语句 [else 语句]- U: u; F0 @' n7 x& u
循环语句:4 A0 K, n5 }% n. s* u; I. g5 Q& L
for 变量:=值 to|downto 值 do 语句7 w4 P1 p1 Z$ X- P
while 布尔表达式 do 语句8 ?# N* D X ?8 T3 k
repeat 语句块 while 布尔表达式 6 u9 G1 c, D. T' J @复合语句:begin 语句块 end 8 g j* `9 I1 J命令语句:continue、break、exit 3 k* O/ j. u: {, F语句块为多个“语句;”连接而成。 $ E8 a) Q+ E, a2 Y$ z. f0 k0 s! W v3 X, [4 ?' { 表达式: 8 j% s; k$ ?* s; l表达式由变量、操作符、常数以及函数调用构成。支持的操作符有+、-、*、div、mod、/、and、or、xor、not。其中/的返回值一定是real,div用于两个整数的整除,mod用于求余数。在这里我们修改一下pascal的语法,我们默认字符串的下标从0开始,而不是1。, Z3 l9 R) q+ l* Q. `+ g5 G
数组和字符串可以用“表达式[下标]”来获得指向元素的引用。数组赋值的时候使用引用复制,字符串也使用引用复制。不过字符串修改的时候保证不影响到其他的副本,这个工作由虚拟机完成。) M4 ]% V. q3 v( `) Y8 ^7 Q
# f, e- x5 H% A9 F0 y$ s0 X既然有了这个简单的语法规定,我们可以试着来写一个程序。跟上一篇文章相同,我们写一个判断一个数字是否质数的函数: 6 [. v) o: w7 R7 i! zunit PrimeTest; ; K1 P' ]2 ^9 z2 ?7 Y! f* S6 r9 N4 R7 z( W7 e
uses IO;{writeln和read} 9 C# f$ j7 m' u( ]9 ?8 n, ? / }3 \4 t D) e# M9 W9 b5 Binterface/ c0 n8 o" J) r+ f
function IsPrime(Num:integer):boolean;' | [# y. M6 ]( v6 w& W
4 }3 R/ j" C& S$ Jimplementation % j3 Y: @$ U8 r3 N 6 F% |# e/ X& nfunction IsPrime(Num:integer):boolean;( j" G3 }( e" S- w3 O$ r% f
var i:integer;4 I3 d$ I* B& U( r1 m
begin - H" a9 S! Z3 @2 @result:=true; {这是delphi设置返回值的方法,此处借用。exit用于退出函数,result变量仅仅用于设置返回值}6 N0 _- D4 G, \& S+ ~+ `" g. ]0 S
if Num<2 then ( I# X$ c1 N% C; R1 ]result:=false; 1 z$ ^( }& H% a" p- ?# w! Jelse if Num>2 then" ~) x' F- _# d: {" x' D' s6 r! r
for i:=2 to Num-1 do: m7 h! B( _# w; X' T
if Num mod i=0 then : s: ?% q0 @5 x' M0 Y9 e6 ~( T1 F9 o/ O2 mresult:=false; " {8 E& v7 C1 V) D+ B, |# rend;% K& y: P2 B. `* o, ?
/ H% C' |$ m& E, nend.9 s: d' }3 K1 a$ w' ~' E+ N8 a, ^