My favorites | Sign in
Project Logo
                
Search
for
Updated Mar 24, 2007 by liuxinyu95
Labels: Featured, Phase-Implementation
ChineseSupportCodeReading  

#阅读Squeak3.8的中文支持Smalltalk代码

简介

Squeak国际化的工作由大岛芳树负责,他帮助实现了Squeak的中文支持,本文介绍这部分代码。希望读者通过这部分代码了解squeak中i18n的原理,并吸引更多的爱好者,帮助进行其他版本squeak的中文化工作。

代码内容

Squeak采用了映像(image)的概念。不同于一般软件利用所在计算机的文件系统,Squeak自身是一个完整的系统。所有的内容都在映像内,映像运行于虚拟机上,映像内包含调度器,垃圾回收器gc,内存管理,编译器,文件系统等等。Squeak中文支持安装包名称为zh-cn2.zar(可从本网站下载),其本质是一个zip压缩包(可以通过unzip了解其内部),它包含以下内容:

  • sqLandZHCN2.cs 文件,记录了针对xxx的改动
  • langeditorJun20.cs 文件,记录了针对语言编辑器(language editor)的改动
  • uSimplifiedChineseFont.out 文件,为中文字体文件
  • install 目录
    • preamble 文件,可以理解为安装脚本,该文件使用Smalltalk代码,将zh-cn2.zar安装到squeak映像中。

如何浏览Squeak中的Smalltalk代码?

在后继的内容中,有时候会阅读Squeak中的相关Smalltalk代码。这些代码全部是公开的,(因为Squeak本身就是一个开源软件)。但是由于Squeak不像其他普通软件有着明确的代码和编译好的二进制可执行文件的区别,所以可能读者会感到困惑:“如何看到这些Squeak中的Smalltalk代码?”。

作者建议,读者在阅读此文章时,手头打开一个Squeak,随时用于阅读其中的代码。中文Squeak可以从本网站下载,地址为:http://chinesesqueak.googlecode.com/files/squeak3.8chn_win_installer.exe 如果读者打算下载英文Squeak,可以访问这里:www.squeak.org

下载运行Squeak后,请在窗口中的空白位置(称为'World')点击鼠标,选择打开('open'),浏览器('Class browser'),可以在浏览器的左上角中的列表中点击右键('right click'),选择‘find class’,然后输入类名即可浏览该类中的所有方法和成员,以及它们的Smalltalk源代码。

安装的启动

了解安装的启动,需要一些Smalltalk语言的背景知识。对于从来没有接触过Smalltalk的读者,如果希望在短时间内了解一下Smalltalk,可以阅读这里的材料:

Squeak中存在一个SARInstaller专门用于将.sar文件安装到系统中。SARInstaller的注释中解释道:

“I am an object that handles the loading of SAR (Squeak ARchive) files.
A SAR file is a Zip file that follows certain simple conventions:
* it may have a member named "install/preamble".”

翻译成中文就是: '我是用来处理读取SAR(Squeak存档)文件的物体。一个SAR文件是一个遵循某种简单约定的Zip文件:它可能包含一个名叫"install/preamble"的内容。'

SARInstaller类有一个方法名叫‘fileInFrom:’,它接受一个参数stream。当用户把中文包zh-cn2.sar利用资源管理器拖拽到Squeak中(这里说的是Windows的情况),SARInstaller>>>fileInFrom:stream将被调用,用于处理如何安装sar包。

这个方法的实现如下:

fileInFrom: stream
	"The zip has been saved already by the download.
	Read the zip into my instvar, then file in the correct members"

	| preamble postscript |

	[
		stream position: 0.
		zip _ ZipArchive new readFrom: stream.

		preamble _ zip memberNamed: 'install/preamble'.
		preamble ifNotNil: [
			preamble contentStream text setConverterForCode fileInFor: self announcing: 'Preamble'.
			self class currentChangeSet preambleString: preamble contents.
		].

		" 以下省略 ..."
		
	] ensure: [ stream close ].

在Smalltalk中用引号括起来的部分是注释,所以该方法最开始的部分是关于这个方法的解释,这是Squeak/Smalltalk中的一个良好习惯。该注释表明,zip文件(sar文件)在执行到本方法前已经通过download安全地保存好了, 本方法下面的代码将从SARInstaller的成员变量‘zip’中读取内容到一些临时变量中,然后再利用文件导入功能执行进一步的安装。

紧接着注释,该段代码定义了两个临时变量,一个叫preabmle,一个叫postscript,读者可以暂时忽略后者。

临时变量后面的代码可以理解为如下的形式:

[ ... ] ensure: [ stream close]

用方括弧括起来的代码叫做语句块('block closure'),由于在Smalltalk中,“万物皆物体(Every thing is an object)”,所以语句块也是物体(Object),因此可以对着语句块调用它的方法,ensure:就是语句块拥有的一个方法,他接受另外一个语句块作为参数。通常表述为:

BlockA ensure: BlockB

它的作用是:不管语句块BlockA执行后会怎么样,一定保证在BlockA执行结束后,会调用BlockB。所以fileInFrom:stream中,不管结果如何(sar文件安装成功或者失败),都会确保文件流(stream)会被关闭。而关闭流就是通过这样一句:stream close,即对着stream物体(对象object)调用它的close方法。在Smalltalk中,人们更习惯这样说:对着stream物体,发送close消息。

下面分析BlockA中的详细内容。首先是最初的两句:

stream position: 0.
zip := ZipArchive new readFrom: stream.

第1句的作用是,将stream的位置设定为最开头(0位置),这样将来读写都从最开头开始(类似于其他语言中的reset)。具体做法是,对着stream物体调用position:方法,并传入一个参数0。

第2句是一个赋值语句。在Smalltalk中,通常人们习惯用:=号或者左箭头表示赋值,而不是使用等号=,由于在普通的计算机键盘上,无法输入左箭头符号,所以人们使用下划线_来进行输入,下划线在Squeak环境中会被自动显示为左箭头。

赋值语句的左侧是成员变量zip,zip是定义在类SARInstaller中的成员变量。SARInstaller类是这样声明的:

Model subclass: #SARInstaller
	instanceVariableNames: 'zip directory fileName installed'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'System-Support'

也就是说SARInstaller类是Model的子类(继承自Model,或者说是Model类的派生类)。它有4个成员变量(Smalltalk的成员变量全部是私有的)分别是:zip、directory、fileName和installed。

因此zip := ZipArchive new readFrom: stream. 一句,首先创建一个ZipArchive(zip文档)类的实例,然后接着对着这个实例调用readFrom:方法,并将stream作为参数传递给该方法。这样一个Zip文档物体,就从流中创建出来了。最后再把这个Zip文档(Object)赋值给zip成员变量。

zip成员赋值完毕后,就开始着手创建preamble,(注,英文单词preamble中文的意思是‘导言’)这一句如下: preamble := zip memberNamed: 'install/preamble'.

首先对着zip调用memberNamed:方法,并传给一个字符串参数'install/preamble',Smalltalk中的字符串都是用单引号括起来的。memberNamed:方法在ZipArchive的父类Archive类中定义,这里略去详细细节。该方法会根据传入的文件名字符串,在zip包内找到指定文件,比如一个zip包内包含了a.cs, b.cs ,c.cs 三个文件,则zip memberNamed: 'b.cs'就会返回第二个文件b.cs的内容。因此最终导言变量preamble中,就存入了zh-cn2.sar中的文件'install/preamble',也就是存入了安装脚本的内容。

导言变量preamble创建好后,就开始处理导言中的安装脚本,相关的代码如下:

preamble ifNotNil: [
	preamble contentStream text setConverterForCode fileInFor: self announcing: 'Preamble'.
	self class currentChangeSet preambleString: preamble contents.
].

程序首先判断导言变量是否为空,方法是对着导言物体(object)调用它的ifNotNil:方法。与其他语言不同,Smalltalk中没有专门的if-then之类的关键字。所有的if类判断,都是通过向物体发送!ifXxx方法来进行的。例如objectA ifTrue: [...]objectB ifFalse: [...]等等。针对preamble调用ifNotNil:后,若preamble为空,则说明前面的zip memberNamed: ...失败了,通常这是由于zip包不全,没有指定的文件。或是由于zip包损坏了等其他因素。这时,就不会执行ifNotNil:后面接受的参数(该参数是一个用中括号括起来的语句块)。否则,说明导言不空,于是执行后继的语句块。

后继语句块由两句话组成,第一句是:

preamble contentStream text setConverterForCode fileInFor: self announcing: 'Preamble'.

它首先对着导言物体preamble发送contentStream消息(也就是调用preamble的contentStream方法)。由于前面preabmle变量是通过zip memberNamed: ...创建的。所以preabmle实际上是一个ZipArchiveMember物体(读者可以自行浏览ZipArchive的父类Archive的mbmerNamed:方法了解其细节)。ZipArchiveMember类定义了contentStream方法,该方法会将zip包中的某个文件的内容,通过流解压缩出来并返回。

获得了zip包中某个文件的解压缩后的内容,接着就对着它调用text方法,将其转换为文本形式。(请注意Smalltalk中的语句是左结合的。),于是就得到了zh-cn2.sar压缩包中preamble文件内的文本内容。

如果,读者用任何文本编辑器浏览preamble文件的内容,就会发现该文件内部都是一些Smalltalk代码。所以接下来对着文本调用setConverterForCode方法。该方法会根据文件的实际格式,决定是使用何种格式进行转换。

转换完毕后,对对着转换结果调用一个二元方法:fileInFor: client announcing: announcement这个二元方法接受两个参数,一个是client,一个是annoucement。

注意这里Smalltalk二元函数的独特格式,这种格式有一个特殊的优点,是目前其他语言中很难做到的。这种优点可以叫做:“强protocol”。例如,一个代表平面上点的类Point。它接受三个参数,分别是横座标x和纵座标y,以及一个颜色c。在其他语言中,Point的初始化,很可能是这样

p = new Point(1,2, 0);

而在Smalltalk中会是这样:

p := Point new X:1 Y:2 Color:0

看到new Point(1, 2, 0),使用者很难猜到第三个参数到底是颜色,还是座标z,而看到Smalltalk的代码,读者一下子就知道第三个参数是颜色,而不是座标。这种强protocol使得Smalltalk代码很难产生歧义。

二元方法fileInFor: announcing:会从Smalltalk代码文本中读取一个一个的Smalltalk表达式,然后将这些表达式送入编译器进行编译。同时屏幕上会显示一个进度条。进度条窗口的标题就是annoucement的内容。因此,!SARInstaller就会一边编译安装导言preamble,一边显示进度,进度条窗口上的标题显示为'Preamble'。

整个这一句执行完毕后,!SARInstaller接下来执行如下语句:

self class currentChangeSet preambleString: preamble contents.

这一句首先通过self引用自己,self可以理解为一个关键字,在任何物体(object)内,使用self都会引用该物体自身。对着self调用class方法后,就获得了该物体所属于的类。这里要澄清一个重要的概念,这个概念在C++和Java等其他语言中都几乎没有。这一概念来自与这样的Smalltalk“公理”:

所有一切都是物体(everything is object)

所以,得出的一个推论是,所有的类也是物体(Class is also object)。但是在C++或Java中,类是类,对象(Object)是对象,类在这些语言中不是一个Object。例如不能对着一个类调用方法(发送消息),也不能动态创建或删除一个类(虽然可以动态创建或删除这个类的对象(object))

class MyClass{
	public int foo() {...}
};

//...

MyClass obj=new MyClass;
obj.foo(); //ok, obj is an object, call its member.
MyClass.foo(); //error! MyClass isn't an object (in C++/Java world)

Smalltalk中,类本身也是物体,既然是物体,就可以对着它发送消息。例如:

obj1 = MyClass new.
obj2 = obj1 class new.

第一句对着MyClass这个物体(一个代表类的object)发送new消息,结果是MyClass这个物体会创建出一个新的物体,这个新物体属于MyClass类,这个物体由obj1代表。 第二句的先对着obj1物体发送class消息,class返回一个物体,该物体代表obj1所属的类Myclass。接着针对这个物体(也就是一个MyClass)发送new消息。于是这个MyClass就创建出另一个新物体,把它赋值给obj2,该物体属于MyClass

所以new方法是属于MyClass类的,而不是属于obj1的。这个和C++以及Java不同。在C++和Java中,唯一有些类似的就是static方法。现在用Java类比一下(C++只能实现上面Smalltalk中的第一句,第二句则难以实现,Java靠反射可以实现第二句,但可能有异常):

class MyClass{
	public static MyClass New(){ return new MyClass; }
};

//...

MyClass obj1 = MyClass.New();
MyClass obj2 = obj1.class().invoke("New", [], []); //may throw exception.

Java和C++中,由于类不是物体(object),所以会有static成员的概念,使用static的成员变量和成员方法,才能达到Smalltalk中访问类(这个物体)的功能。也正因为在Smalltalk中,类不是什么特殊的东西,而被平等的视为物体,所以根本不需要static关键字的和相关的概念。这也体现了Smalltalk的简洁和统一。

因此,执行到self class后,就获得了"!SARInstaller class"物体。(可以在Squeak的!System browser中,点击SARInstaller后,点击左上方第二个列表框下方的class按钮,就能浏览!SARInstaller class物体的所有内容)对着这个物体执行currentChangeSet就获得了当前的"变化集"。

什么是“变化集(change set)”呢?普通软件开发,都会有版本控制的概念,常见的版本控制工具有CVS和SVN等等。Squeak内部自动继承了自己的变化管理工具,其中的一个概念就是Change set,从Squeak启动后,用户做的任何动作(创建自己的物体,画画,听音乐,安装其他软件等等)都被视为变化,这些变化的总体集合称为变化集,英文叫Change set。用户可以通过变化分类器浏览自己Squeak中的所有变化,并且决定是否接受某些变化,他可以方便的将系统恢复到任何变化之前。

任何Squeak开发人员都可以通过变化集来发布自己的作品和补丁。其他Squeak用户只要接受这个变化并将其安装到自己的Squeak中,就可以共享别人的智慧和劳动成果。变化集以文件的形式保存后,就形成了.cs文件。后缀cs就是Change set的缩写。前面介绍的中文支持包,就包含两个变化集cs文件。

执行完self class currentChangeSet后,接着就向当前变化集发送preambleString:消息。ChangeSet的preambleString:方法接受一个字符串参数。这里传递进去的参数是:preamble contents。由于preamble是一个ZipArchiveMember物体,而不是一个字符串。所以不能直接传入它,但是对折preamble物体发送contents方法后。就会调用zip解压缩算法,将相关的内容解成字符串并返回。

所以这句话执行完毕后,当前变化集的内容就是导言文件内部的Smalltalk代码了。

至此!SARInstaller通过对SAR包中preamble文件的读取,完成了所有安装前的准备工作,它下面将开始根据安装脚本preamble中的Smalltalk代码,正是安装中文支持功能。

安装脚本preamble文件

该文件内容最简单,就是一个安装脚本。

| stream |

self fileInMemberNamed: 'sqLandZHCN2.cs'.
self fileInMemberNamed: 'langeditorJun20.cs'.

(Smalltalk includesKey: #NaturalLanguageTranslator) ifTrue: [
	NaturalLanguageTranslator newLocaleID: (LocaleID isoString: 'zh-cn').
].

StrikeFontSet installExternalFontOn: (self memberNamed: 'uSimplifiedChineseFont.out') 
    contentStream binary encoding: SimplifiedChineseEnvironment leadingChar 
    encodingName: #SimplifiedChinese textStyleName: #DefaultMultiStyle.

Sign in to add a comment
Hosted by Google Code