[附录A crowbar语言的设计]

本章将要说明在本书中crowbar的最终版本(book_ver.0.4)的设计。但是,本章中的内容既不是定稿版的文档,也不是很严谨。因为如果想要严谨地描述一门语言的设计需要相当的篇幅。

A.1 程序结构

构成程序的要素

crowbar的程序由以下要素构成。

1. 顶层结构

顶层结构(toplevel)由语句(statement)组成。crowbar的程序由处理器指定的源文件的顶层结构开始执行。

2. 函数定义 

函数定义.(function. definition)是定义可以(可能)从其他位置调用的函数。在顶层结构的语句按顺序执行的时候会忽略函数定义。因为函数允许回溯引用,所以其定义的位置既可以在调用的位置之前,也可以在调用的位置之后。

A.2 文字语法规则

A.2.1 源文件的编码

crowbar的源文件使用与处理器相同的字符编码。当前确定的是在UNIX环境中为EUC和UTF-8,在Windows环境中为GB2312。除了写注释和定义字符串常量之外的情况,都不能使用非ASCII字符。

A.2.2 关键字

下面这些单词作为crowbar的关键字,不能作为变量名、函数名使用。

break catch closure continue else elseif false final finally for

foreach function global if null return throw true try while

A.2.3 空白字符的处理

空格(ASCII码的0x20)、制表符和换行符在源文件中除了区分不同的标识符外没有其他含义。

A.2.4 注释

在crowbar中从 #开始到本行结束都会被作为注释。

A.2.5 标识符

被当做变量名、函数名、类名等使用的标识符,需要遵从以下规则。

第一个字符必须是英文字母(A~Z,a~z)或者是下划线。

从第二个字符开始可以是英文字母、下划线或数字(0~9)。

crowbar的标识符中不允许使用中文等ASCII字符集中不包含的字符。

A.2.6 常量

1. 真假值常量

真假值常量只有 true(真)和 false(假)。

2. 整数常量

整数常量由 0或者“ 1~9后面跟着0个或以上的 0~9”组成。

目前为止还不支持八进制和十六进制的表示方式。

3. 实数常量

实数常量由“1个以上 0~9的组合、点、1个以上 0~9的组合”组成。

.5或者 1.都不是实数常量。

4. 字符串常量

字符串常量是由双引号括起来的字符串。在字符串常量中 \n 表示换行,\t表示制表符, \表示 \。

5. null常量

null常量用来表示引用类型没有指向任何值。

6. 数组常量

在花括号 {}中将元素用逗号分开,并初始化元素就创建了数组常量。数组常量的详细内容请参考A.3.3节。

在最后一个元素的后面有没有逗号都可以。

7. 正则表达式常量

由 %%r和任意一个将正则表达式括起来的字符组成正则表达式常量。

【例】

%%r"[a-z]*"

%%r!hoge(.)a\1! ←用括号括起来的部分可以在\1的位置被回溯引用

A.2.7 运算符

以下这些字符(也许只是一部分)会被当做 运算符  (operator)解释。

, && || = == != > >= < <= + - * / %

+= -= *= /=%= ++ — ! . () []

A.2.8 分隔符

以下的字符会被解释为 分隔符 (punctuator)。

() {} ; : ,

A.3 数据类型

A.3.1 类型一览

在crowbar中存在以下 类型 (type)。

逻辑类型 : true或者 false。

整数类型 :能够表达的范围与处理器编译的C语言环境中的 int类型一致。

实数类型 :能够表达的范围与处理器编译的C语言环境中的 double类型一致。

字符串类型 :字符串类型的详细内容请参考A.3.2节。

数组类型 :数组类型的详细内容请参考A.3.3节。

对象类型 :对象类型的详细内容请参考A.3.4节。

函数类型 :函数类型的详细内容请参考A.3.5节。

原生指针类型 :原生指针类型的详细内容请参考A.3.6节。

A.3.2 字符串类型

crowbar的字符串类型属于引用类型。但是因为字符串本身不能改变(immutable),所以使用者没有必要意识到它是一个引用类型。

crowbar的字符串类型的内部表现形式为,处理器编译的C语言环境中的宽字符串。

字符串具有以下的模拟方法(fake method),通过方法调用的形式取得字符串的相应信息。

figure_0344_0089

A.3.3 数组类型

crowbar是非静态类型的语言,因此数组中可以保存所有的类型,不仅如此,各种数据类型可以同时被装在一个数组里面。

crowbar的数组类型可以通过数组常量和 new_array()函数创建。

创建以整数、字符串、实数、数组为元素的数组

a1 = {1, "abc", 10.0, {1, 2, 3}};

创建有10个元素的数组(元素的初始值为null)

a2 = new_array(10);

取得数组元素和为元素赋值都需要使用 []运算符。数组下标从0开始。

取得数组的元素

print("a[3].." + a[3]);

为数组元素赋值

a[5] = 10;

crowbar的数组是引用类型。

数组具有以下的模拟方法,通过方法调用的形式操作数组。

figure_0344_0090

A.3.4 对象类型

对象类型是由多个 成员 组成的类型。数组通过下标指定元素,对象则是通过成员名称指定。

对象通过 new_object()函数创建。创建的对象通过对指定的成员名称赋值的形式,为对象添加成员。

创建空的对象

o = new_object();

添加对象的成员

o.x = 10;

o.y = 20;

引用对象的成员

print("o.x.." + o.x + ", o.y.." + o.y + "\n");

对象类型是引用类型。

A.3.5 函数类型

不仅可以通过函数名赋值给其他变量,还可以通过 ()进行调用。

function a() {

print("hello!\n");

}

c = a; #将函数a代入c

c(); # ←输出"hello!"

可以使用关键字 closure在表达式中定义一个无名的函数。这种方式被称为闭包(closure)。

c = closure() {

print("hello!\n");

};

c(); ← 输出"hello!"

从闭包内部可以引用到创建闭包所在位置的局部变量。即使在闭包调用的时候,创建闭包的函数已经结束的情况下也是一样。

A.3.6 原生指针类型

原生指针类型是保存在原生函数内部使用的值(如文件指针, FILE*)的类型。在原生函数外的原生指针类型只能够进行复制和等值比较。

现在的原生指针类型只在文件指针和正则表达式中使用。

原生指针类型可以由原生函数定义终结器(finalizer)。终结器是在GC要释放原生指针类型所指向的对象时执行的函数。但是,因为终结器的执行时机不能被应用程序预测,所以有关重要资源的释放不应该依赖终结器。

A.3.7 迭代器

迭代器(iterator)是表现循环的类型。

迭代器是一个设置了一些闭包方法(method)的对象,并不是处理器中定义的特殊类型。因此,在用户程序中也可以定义迭代器。

迭代器是具有以下方法的对象。

first():返回迭代器中的第一个元素。

next():将迭代器游标向后移动一个。

is_done():迭代器移动到超出最后一个元素的位置时返回 true,否则返回 false。

current_item():返回迭代器中当前的元素。

crowbar的数组可以通过 iterator()方法得到数组的迭代器。另外,在使用 foreach语法的时候,就是基于迭代器循环的。

A.3.8 异常

在crowbar中,异常是被设置了一些闭包作为方法的对象。可以使用原生函数 new_exception()来创建异常。

使用者在创建“异常”的时候,需要使用内建函数 create_exception_class()。这个函数以“父类”为参数创建一个新的异常类。通过调用这个类的 create()方法,创建属于这个类的实例。

在构建脚本中对ArithmeticException定义的描述

ArithmeticException = create_exception_class(RuntimeException);

通过create()方法生成实例。

e = ArithmeticException.create();

异常实例中的 child_of() 方法用于检查异常的类型。在上面的例子中, ArithmeticException是RuntimeException的子类,e是ArithmeticException的实例,因此如果调用 e.child_of(RuntimeException)的话,传入上面两个类都会返回 true。

A.4 表达式

所谓 表达式 (expression),就是通过运算符将常量或者标识符关联起来。

A.4.1 类型转换

在crowbar中使用双目运算符(+、-、*、/、%)和比较运算符(==、!=、>、>=、<、<=)时,如果两边的类型不同的话,crowbar会基于下面的规则对类型进行扩展。

  1. 不论左边还是右边,只要其中一边是整数,另外一边是实数的时候,整数会转换为实数。

  2. 在左边是字符串右边是逻辑类型、整数类型或者实数类型的时候,右边会转换为字符串。

上述类型转换,在算术赋值运算符(+=、 -=、 *=、 /=、 %=)的情况下同样适用。

A.4.2 运算符一览

crowbar的运算符一览如下所示(优先顺序由高至低)。

figure_0347_0091

(续)

figure_0348_0092

在crowbar中,只存在后置的自增和自减运算符。并且,其动作与C等语言中的前置运算符效果相同(不会等到最后,而是立刻自增表达式的值)。

A.5 语句

A.5.1 表达式语句

所谓表达式语句(expression statement),就是在表达式后面加上分号。

调用print()函数的表达式,后面加上分号

print("hello,world\n");

自增表达式后面加上分号

i++;

无副作用的表达式也可以成为表达式语句,但没有任何意义

5;

A.5.2 if语句

if语句是根据条件进行判断并进行分支处理的语句。

if (条件表达式1) {

条件表达式1为真时执行的处理。

} elseif (条件表达式2) {

条件表达式1不为真,且条件表达式2为真时执行的处理。

} else {

所有条件表达式都不为真时执行的处理。

}

elsif 子句和 else 子句都是可以省略的。另外,可以编写任意多个 elsif子句。

与C等语言不同的是,即使只包含一行语句,也不能省略花括号({})。

A.5.3 while语句

while语句是进行循环操作的语句。

标识符:

while (条件表达式) {

在条件表达式为真的情况下,此处的代码会反复执行。

}

“标识符:”的部分被称为标签(label)。标签可以省略。

与 if语句相同,即使只包含一行语句,也不能省略花括号({})。

A.5.4 for语句

for语句是进行循环操作的语句。

标识符:

for(第1表达式; 第2表达式; 第3表达式) {

在第2表达式为真的情况下,此处的代码会反复执行。

}

“标识符:”的部分被称为标签(label)。标签可以省略。

让我们先看一段for语句的代码:

创建一个数组

a = new_array(10);

循环取得数组的每个元素

for(i = 0; i < a.size(); i++){

输出每个元素

print("a[" + i + "].." + a[i]);

}

基于上面这段代码,其中的第1表达式、第2表达式、第3表达式也有其他的叫法。

第1表达式通常的作用是对判断循环是否需要继续的变量进行初始化,因此也叫作初始化表达式。

第2表达式通常的作用是根据初始化表达式中创建的变量,以此判断是否需要继续循环,因此也叫作判断表达式。

第3表达式通常的作用是重新设置循环的判断条件(一般都是++或者—),因此也叫作增量表达式。

上述 for 语句中,除了在 continue 时会执行第3 表达式之外,其他与下面的 while语句效果相同。

第1表达式;

while (第2表达式) {

语句

第3表达式;

}

第1表达式、第2表达式、第3表达式都是可以省略的。在省略了第2表达式时意味着永远返回真。

A.5.5 foreach语句

foreach语句是利用迭代器进行循环操作的语句。

标识符:

foreach (变量 : 集合) {

语句

}

“标识符:”的部分被称为标签(label)。标签可以省略。上述 foreach语句等同于下面的 for语句。

for (ite = 集合.iterator(); !ite.is_done(); ite.next()) {

变量 = ite.current_item();

语句

}

但是,实际上在 foreach 语句中引用不到迭代器,在上述例子中变量 ite并不存在。

A.5.6 return语句

return语句是从函数中跳出的语句。

return 表达式;

在省略表达式的时候,该函数返回 null。

A.5.7 break语句

break语句是用于跳出循环的语句。

break 标识符;

标识符可以省略。在省略的情况下,跳出最内侧的循环。

在指定了标识符的情况下,跳出持有同样标识符的循环。

A.5.8 continue语句

continue语句用于跳转到循环的末尾。

continue 标识符;

标识符可以省略。在省略的情况下,以最内侧的循环为对象。

在指定了标识符的情况下,以持有同样标识符的循环为对象。

在以 for 为对象进行 continue 的时候, continue 后会紧接着计算for语句的第3表达式。

A.5.9 try语句

try语句是用于执行异常处理的语句。

try{

语句

} catch (变量名) {

语句

} finally {

语句

}

在 try子句中如果发生了异常就会执行 catch子句。发生的异常会被设置到 catch子句声明的变量中。 catch子句和 finally子句只要存在其中的任何一个,另外一个就可以被省略。

无论异常是否发生,无论是否存在 catch子句, finally子句都一定会执行。在 try子句或者 catch子句中,如果执行了 break、 continue、 return来中断处理,那么也会执行 finally 子句。如果使用 break、continue、return.来中断 finally 子句的话,相比 finally 的执行结果, try 子句或者 catch 子句中语句的执行结果会更优先。例如, try 子句内执行了 return 5;,在返回前执行了 finally子句中的 return 3;,在这种情况下,最终被作为返回值返回的是 5。

A.5.10 throw语句

throw语句是用于抛出异常的语句。

throw e;

e在通常情况下会使用通过 new_exception()创建的异常对象,但是整数类型等其他所有类型都可以被 throw。

在 try 子句内发生的异常,(如果有的话)会在 catch 子句中被捕捉。当 catch 子句不存在或者异常发生在 try 语句之外的时候,会终止当前正在执行的函数并返回到调用者(或者叫上一层)的处理中。如果调用者也没有 catch这个异常的话,则会沿着调用层级回溯,直到顶层结构。如果顶层结构中也没有捕捉这个异常,就会输出 栈轨迹  (stack.trace)并终止处理。

crowbar中的栈轨迹在调用 new_exception()的时候就被创建了。

A.5.11 global语句

global语句是为了在函数内引用全局变量而使用的语句。在crowbar中,如果不使用 global语句进行声明的话,在函数内就访问不到全局变量。

引用了STDIN,STDOUT两个全局变量

global STDIN,STDOUT;

A.6 函数

A.6.1 函数定义

函数使用以下形式定义。

function 函数名 (参数) {

语句

}

请参考下面的示例。

接收两个参数并返回它们的和的函数

function sum (a, b) {

return a + b;

}

A.6.2 局部变量

在函数内声明的变量就是局部变量(local variable)。

局部变量的作用域只在当前函数内。与C语言不同的是,函数内的程序块并不会形成作用域 [1]

注 释

[1]. 也就是说,局部变量的作用域是当前函数,而不是当前程序块。——译者注