Cool(Classroom Object-Oriented Language)
Note for Cool Language Manual.
由Alex Aiken 设计的用于课程实验的面向对象语言。
参考资料
- Stanford CS143 Cool Manual(2016, Alex Aiken)
- UIUC CS426 Cool Manual (2000, Alex Aiken)
1 语言特征
- 包含对象、静态定型、自动内存管理等现代编程语言特征
- Cool 程序由一组 classes 组成,class 封装数据类型的变量和过程,class 的实例是 objects (对象)
- Cool 是表达式语言,其许多语言构造都是表达式,表达式有一个值和一个类型
- Cool 是类型安全的:过程要确保被应用于正确类型的数据上
- Cool 程序文件的扩展名是 .cl
3 类
class <type> [ inherits <type> ] {
<feature_list>
};
- 每个类必须在一个源文件中定义,同一个文件可以包含多个类定义
- 类名是全局可见的,以大写字母开始
- 类不能重复定义
- feature 可以是属性或方法
- 类 A 的属性是一个表示类A的一个对象的部分状态的变量
- 类 A 的方法是可以操作类A的对象和变量的过程
- feature 名必须以小写字母开始
- 在类中,方法名不能被定义多次,属性名也不能被定义多次,但是允许一个方法和一个属性同名
- 类 C 继承 P 表示为
class C inherits P { ... };
,C是子类、P 是父类- C 除了自己的 features 外,含有P中定义的所有 features
- 如果父类和子类定义了相同的方法名,则子类的定义优先
- 父类和子类不允许定义相同的属性名
- 从类型安全的角度,有必要限制如何重定义方法
- 如果一个类没有父类,则它继承自类
Object
- Cool 采用的是单一继承(一个类只有一个父类),其类继承图是以 Object 为根的树
- 除了
Object
,Cool有四个基本的类:Int
,String
,Bool
和IO
4 类型
- 每个类名是类型
- 类型
SELF_TYPE
是self
变量的类型 - 类型声明的形式为
x:C
,其中x
是变量,C
是类型 - 每个变量必须先声明,所有属性也必须声明其类型
- 如果一个方法或变量期待 P 类型的值,则在这些地方也可以使用C类型的任何值,前提是: P 是 C 的祖先。
- 这种情况称为 C conforms to P 或者C ≤ P
- 定义4.1 conformance
- 类型检查
- 在编译时进行类型检查以保证程序在执行时不会有运行时的类型错误
- 利用程序员提供的针对标识符的类型声明,类型检查器为程序中的每个表达式推理其类型
- 表达式的静态类型是编译时由类型检查器推理得到,而其动态类型是运行时计算得到的
- 类型检查器得到的静态类型是可靠的 , 即
- 定义4.2:任何表达式的动态类型 ≤ 静态类型
5 属性
<id> : <type> [ <- <expr> ];
<- <expr>
部分是可选的初始化表达式, 在 新创建object 时执行。表达式的静态类型必须与属性的声明类型一致- 当创建一个类的 object 时, 所有继承的和本地的属性都必须初始化。初始化的顺序为: 按继承的次序,从最大的祖先类开始,同一类中按属性在源码中的先后次序依次初始化。
- 继承属性不能重定义
- 值
void
是所有类型的成员,用于作为未初始化的变量的缺省值(Int, Bool, String
类型的变量除外)。- 注意:Cool中并没有针对
void
的名字。 isvoid expr
用于检测一个值是否为void
- 注意:Cool中并没有针对
- 基本类型
Int, Bool, String
的变量的初始化比较特别
6 方法
<id>(<id> : <type>, ..., <id> : <type>) : <type> {<expr>};
- 形参标识符不能相同。方法的类型必须与声明的返回类型相同。形参隐藏同名属性。
- 重载被继承的方法时要保证类型安全, 要求:参数的个数、形参的类型和返回的类型必须与被继承的祖先类中的方法相同
7 表达式
- 常量
- 布尔常量
true
,false
属于类型Bool
- 整型常量是形如
0, 123, 007
的无符号数字串,属于类型Int
- 字符串常量是形如
"this is a string"
的由双引号括起的串,属于类型String
- 布尔常量
- 标识符
- 局部变量、形参、
self
和类属性都是表达式. - 不能给
self
赋值,不能在let
、case
语句中给self
绑定其他值;self
不能作为形参, 属性名不能为self
- 作用域规则
- 局部变量和形参有lexical scope
- 属性在声明它和继承它的类中可见,但是会被表达式中的局部声明所隐藏
self
隐式地被约束到每个类中
- 局部变量、形参、
赋值形式为
<id> <- <expr>
- 表达式的类型必须与标识符的声明类型一致
Dispatch (即方法调用)
<expr>.<id>(<expr>,...,<expr>)
- .f()的求值顺序是:
- 从左到右计算实参 to
- 计算 及其运行时类型类
C
(若为void,则产生运行时错误) - 然后调用类
C
中的方法f
,将 的值绑定到f
方法体中的self
,实参绑定到形参
- .f()的类型检查是:
- 假设 的静态类型为
A
,运行时类型为C
,类A
必须有方法f
,方法调用必须与f
的定义有相同的参数个数、第i
个实参的静态类型必须与第i
个形参的声明类型一致 - 如果
f
的返回类型为B
,则方法调用的静态类型是B
;否则,如果返回类型是SELF_TYPE
, 则方法调用的静态类型是A
- 假设 的静态类型为
<id>(<expr>,...,<expr>)
为self.<id>(<expr>,…,<expr>)
的简写<expr>@<type>.<id>(<expr>,…,<expr>)
可以访问被子类重定义所隐藏的父类的方法
- .f()的求值顺序是:
条件表达式
if <expr> then <expr> else <expr> fi
- 谓词部分必须是
Bool
类型 - 条件表达式的类型为 两个分支的静态类型的最小公共祖先类型
- 谓词部分必须是
循环表达式 while
loop pool - 谓词部分必须是
Bool
类型 - 循环表达式的类型为
Object
,循环体可以是任何静态类型
- 谓词部分必须是
块 (Blocks)
{<expr>; ... <expr>;}
- 从左到右依次计算
- 块表达式的静态类型是最后一个
<expr>
的类型
Let 表达式
let <id1> : <type1> [<- <expr1>], ..., <idn>:<typen> [<- <exprn> ] in <expr>
- 求值顺序:
- 先计算
<expr1>
,将结果赋给<id1>
- 然后计算
<expr2>
,将结果赋给<id2>
- ...
- 最后计算
<exprn>
,将结果赋给<idn>
如果<idk>
没有初始化表达式,则使用类型<typek>
的缺省初值
- 先计算
<id1>,...,<idk>
在<exprm>
(m>k
) 中可见<id1>,...,<idn>
在let
表达式的体中可见- 如果
let
中的标识符声明了多次,则后面的隐藏前面的
- 求值顺序:
Case
case <expr0> of <id1> : <type1> => <expr1>; ... <idn> : <typen> => <exprn>; esac
- 求值顺序
- 计算
<expr0>
和其动态类型C
- 各分支的最小类型
<typek>
满足C ≤ <typek>
- 将
<expr0>
的值绑定到<idk>
, 执行并返回<exprk>
. 不存在这样的分支则产生运行时错误
- 各分支的最小类型
- 计算
- 整个表达式的静态类型为所有分支的最小公共类型
x : Object => ...
表示 "default" 分支
- 求值顺序
New
new <type>
- 生成一个
<type>
类型的对象
- 生成一个
Isvoid
isvoid <expr>
- 如果
<expr>
是void
,则求值为true
,否则为false
- 如果
算术和比较运算
四种二元算术运算符:
+
、-
、*
、/
<expr1> <op> <expr2>
<expr1>
在<expr2>
前计算- 两个子表达式必须是
Int
,结果为Int
- 三种比较运算符:
<
、<=
、=
, 规则同上,除了结果为Bool
=
是特例,如果两个子表达式中存在一个静态类型为Int
,Bool
或String
, 另一个必须是同类型- 非基本类型的相等性检查是检查它们的指针是否相等
- 一元算术运算
~<expr>
是求整数补码.<expr>
为Int
, 结果为Int
- 一元逻辑运算
not <expr>
,<expr>
为Bool
, 结果为Bool
8 基本类型
Object
- 是继承图的根,它定义有下列方法:
abort() : Object (* 退出程序, 返回错误信息 *) type_name() : String (* 返回类名 *) copy() : SELF_TYPE (* 返回对象的浅拷贝 *)
IO
- 定义下列方法用于执行简单的输入和输出操作:
out_string(x : String) : SELF_TYPE (* 输出参数, 返回 self *) out_int(x : Int) : SELF_TYPE (* 输出参数, 返回 self *) in_string() : String (* 从 stdin 读入字符串, 换行结束, 不读入换行符 *) in_int() : Int (* 读入一个整数, 整数后换行前的其他符号忽略 *)
Int
- 提供整数,没有定义方法,默认初始化为 0
- 不能被继承或重定义
String
- 定义下列方法:
length() : Int (* 返回 self 的长度 *) concat(s : String) : String (* 返回连接 self 和 s 的字符串 *) substr(i : Int, l : Int) : String (* 返回从 i 开始,长度 l 的子串, index 从 0 开始 *)
- 默认初始化为 ""
- 不能被继承或重定义
Bool
- 提供
true
和false
两种取值, 默认初始化为false
- 不能被继承或重定义
- 提供
9 主类 (Main Class)
- 每个程序必须有一个类
Main
, 该类中必须有无形参的方法main
,该方法必须在类Main
中定义,不能被继承 - 程序通过对
(new Main).main()
求值而被执行
后面是关于Cool的形式化的描述
10 词法结构
- 整数, 标识符, 特殊记号
- Integer
[0-9]+
- Identifier
[a-zA-Z0-9_]+
self
SELF_TYPE
, 但不属于关键字
- Integer
- 字符串
"..."
, 字符串内\c
表示c
(写到这个,助教不是很想写下去了, 助教好想回宿舍睡觉, 怎么办呢)
助教 : 接下来直接看 manual 吧. 我……我有这个必要告诉你们一点,人生的经验。各种语言的规范必然都是用英文写的. 从现在开始就要锻炼.
学习外语,贵在坚持。学习和掌握一门外语不容易,需要付出艰苦努力。只要把握规律,坚持不懈,日积月累,就一定能不断有所收获。
江泽民
后面的内容依次是类型系统、操作语义, 它们是对Cool语言语义的形式描述. 感兴趣的下学期可以选冯新宇老师的程序设计语言基础课.