2011年6月30日星期四

面向Scheme用户的Common Lisp语言的小贴士【程序语言介绍】

* Scheme
Scheme的主要参考资源是SICP和R5RS,优点是语言设计的相当精巧,便于学习和理解程序的原理。

* Common Lisp
Common Lisp是Lisp最主要的方言之一,是为了标准化众多分支而产生的,在AI领域特别是符号计算方面有所应用。和Lisp的另一方言Scheme相比,更适合写实际可用的程序,而不限于演示的目的。
本文假定读者已经了解了Scheme语言的基本使用,将侧重于CL语言自身中有差异的部分,以减少在使用CL因为困惑而不自在。
Lisp的共同点是在于List,这里尽量把Scm和CL当作不同的语言来看待,避开去具体比较之间的相似和相异之处。
这里假设已经了解了Lisp系语言的基本使用和编程范式,将仅从语言特性角度来看反而能都看到CL的一些特别之处。

* 参考文档
** Common Lisp the Language, 2nd Edition
语言描述文档,对语言特性和提供的库都有详细的说明
** Common Lisp HyperSpec
参考手册,用于查阅,形式类似于按照关键词组织的卡片表格样式
** ANSI Common Lisp
偏向于语言特性的教学,很适合本文的话题,所以这里将按照这本书的结构来写

* 正文
** 缩进对阅读代码来说,相当重要。如下内容如有歧义请以文档为准
** 列表list是Lisp中重要的数据结构,同时Lisp语言的表达式也以list形式书写,表达式可以求值
** 断言predicate命名以p结尾
** t表示true而nil表示false,t是所有类型的supertype,nil是所有类型的subtype
** 为方便表示不同的操作符,下面将Function称为函数,将Special Form称为语句,将Macro称为宏,有时候在用法上之间的差异会模糊
** 用defun定义全局函数,用defparameter定义全局变量并计算初始值(如果仅仅是定义的话,也可用defvar),用defconstant定义全局常量
** 用let/let*绑定局部变量,用flet/labels绑定局部函数
** 宏destructuring-bind可以接受模式pattern形式的绑定
** 语句setf用来改变全局或局部符号或表达式所表示的空间所绑定的值,当符号未绑定时自动绑定全局符号
** 语句setf可以同时接受多组符号和值的绑定,这里的符号可以是符号类型,也可是表达式来指定符号的空间或者表示对对象的修改
** 函数参数从左到右执行,函数定义时允许递归调用
** 格式化函数format的第一个参数表示输出的方式,格式字符串~A表示在该处输出值,~%表示换行
** 格式化函数中的格式字符串有一些复杂但有效的用法
** 语句quote可以在包内获取或创建符号,或者用来创建列表,可缩写为'
** 语句function可以将符号或lambda表达式对应的函数,可缩写为#'
** 以lambda开头的不是函数或者语句,而是一种单独形式表达式,类似于列表
** 函数apply和funcall用来将函数作用到参数上,第一个参数可以是函数也可以是符号,但不建议是lambda表达式
** 判断值的类型可以用类型相关的断言,或者用函数typep进行比较,typecase进行分支,deftype定义类型,coerce进行类型转换
** 判断值的内容是否相同用eql,判断列表及每个元素相同用equal,判断值所在的地址是否相同用eq
** 列表list通过函数list生成或者通过make-list来创建,内部由点对pair构成
** 用copy-list和copy-alist和copy-tree来复制列表和关联列表和嵌套列表
** 函数mapcar用来通过某函数将列表的每个元素映射并新创建一个列表
** 列表可以充当集合Set使用,或者是序列Sequence,或者是堆Stack,或者是关联列表Assoc-list,在CL中提供有相关的函数
** 多维数组由函数make-array创建或者用#na生成,由函数aref引用下标,并可由setf来改变数组内的值
** 向量是一种一维数组,用vector或者#()来生成,通过svref来引用。数组的函数如sort和aref同样可用
** 字符串也是一种一维数组,可以用equal判断相等(区分大小写),同时也有专门用于字符串的函数如函数char来获取下标对应的字符以及不区分大小写的比较(以“string-”为前缀)
** 列表可用nth获取下标,不过由于是递归调用cdr/rest所以性能不佳。和向量及字符串一起,都可以使用序列相关的函数,如elt获取下标对应的元素
** 序列相关的函数常见的关键词是:key :test :from-end :start :end
** 形如(defstruct point x y)定义了两个成员的结构Structure,由(make-point :x 0 :y 0))创建实例,并得到访问函数point-x和point-y,提供有setf形式用于修改,断言名为point-p。
** 字段可有初始值,结构也由:cone-name定义对应函数的前缀,有:print-function定义打印方式
** 哈希表Hash Table由make-hash-table创建(其:test默认为eql),gethash访问(其第二个返回值表示是否存在)
** 控制流程中语句progn依次执行语句并返回最后一个值,语句block的第一个参数表示标签并可用return-from返回语句块的值(标签为nil时可用return,也可在一些语句内使用,函数名可作为标签使用),tagbody可在内部任意位置位置使用并由通过go来跳转
** 分支cond和case的最后一个分支可以是t或者otherwise表示默认的情况
** 循环和迭代过程可以使用do或do*或dolist或dotimes或mapc(不返回值的mapcar)或loop
** 语句可以返回列表也可以由values返回多个值,通常只有第一个值其作用,可由multiple-value-bind或multiple-value-call或multiple-value-list使用多返回值
** 非局部跳转(跨越函数调用)可以用throw(参数为标签加返回值)和catch和unwind-protect ,而错误error会触发error handler
** 函数symbol-function用来获取符号对应的全局函数,也可以用setf的形式使用,它不可以用来访问局部函数
** 用documentation可以获得defun中参数后的描述用字符串,函数之外的类型也可以定义这样的文档
** 参数列表Parameter List指函数定义和调用时的&rest &optional &key,可以有默认值
** 函数定义中可以指定某变量使用动态作用域,形如(declare (special x)),可以在修改全局变量时以免造成副作用的扩散
** 函数compile用来编译符号所对应的函数,有些操作符会在编译期执行的,会影响到性能和一些值的绑定。
** 递归可以用数学归纳法来考虑
** 标准输入输出流Stream所存放的全局变量是*standard-input*和*standard-output*
** 由make-pathname创建路径,函数open(:direction表示读写方式)由路径打开流,函数close关闭流
** 宏with-open-file所定义的语句有一个隐含的unwind-protect的close,其第一个参数的首元素是该语句中绑定到流的变量名
** 函数read-*默认使用以*standard-input*作为字符输入,函数read-from-string从参数的字符串作为输入流,在读取时read-macro会被展开
** 函数prin1可以和read配对使用(会将转义字符显示出来),通常的输出使用princ,函数pprint对应*print-pretty*非nil
** 函数format中的格式化字符串在~后可跟,分割的参数,参数可空,右侧,可省略
** 符号Symbol不区分大小写(对于包含空格等字符的符号名,可以用双"|"包围),会intern到包package中,不同的包里的同名符号是不同的。
** 以冒号为前缀的关键词keyword的值是其本身,与包的作用域无关。有时需要用gensym得到一个唯一的符号。
** 函数intern用于从字符串在包内查找或创建符号,每个符号有它的属性列表
** 包由宏defpackage创建,可以指定:use :nicknames :export,使用in-package切换到某包内
** 通过“包名:符号名”的形式来使用别的包里导出export的符号
** 当符号有绑定全局变量时,可以用symbol-value获得该变量的值(不同的类型可以绑定到同一个符号)。局部变量只在编译或解释存在,不可以混用
** 类型number分为integer float ratio complex,存在自动和手动的类型转换
** 用=可以比较两个数是否相等(“/=表示参数各不相等”),而eql不仅要相等好需要类型一致
** 整数分为fixnum(最大正值为常量most-positive-fixnum)和bignum,会自动转换。浮点数有四种,之间储存和计算的精度不同
** eval以列表作为参数,执行使用全局作用域
** 定义宏defmacro返回一个列表,语法类似于defun,用macroexpand-1可以手动展开,常配合Backquote书写
** 宏将在在使用它的程序执行前替换原表达式,需要区分编译时和运行时执行的部分,以及和调用的上下文的影响(建议配合let使用)
** 用于setf的同名宏通过define-modify-macro定义,setf中的表达式参数作为该宏的第一个参数
** 定义类defclass,其参数为类名,超类列表,槽列表。函数make-instance通过符号创建类,函数slot-value访问实例的槽
** 槽slot可以定义:accessor :initarg :initform,定义“:allcation :class”则槽属于类而非实例,槽还可以定义:documentation :type
** 子类会继承超类superclass的槽,类可以有多个超类,按照从到右深度优先
** 同名的槽,:allocation :initform :documentation取做特殊化的类(子类),:initargs :accessors :readers :writers取并集,:type取交集,同名依据符号相同来判断
** standard-object是standard-class的实例,是t的subtype,是其他所有class的superclass
** 宏defmethod用于定义方法,可以给参数定义class或者type或者eql条件
** 方法必须有相同数量的参数,可选参数也需要个数一致,&rest或&key需要同时存在或不存在。其中只有必须的参数可以特例化,如果类型一致会覆盖先前定义的特例方法
** 由方法定义的函数称为generic function,可以对不同的类定义不同的方法,类型依照参数从左到右的顺序匹配定义了特例且最特例的类型,这有消息转递模型所差异
** 可以在方法的基础上定义辅助auxiliary方法:before(按照特殊往一般的顺序) :after(按照一般往特殊的顺序) :around(只执行最特殊的,可以(if (next-method-p) (call-next-method)))
** 宏defgeneric用于将所有的方法看作一个整体,例如可用:method-combination通过某操作符混合所有的方法
** CLOS可以配合PACKAGE机制来只导出需要暴露的函数
** 列表类型(也包括其他的容器)中会存在节点数据的共享,并可能构成环(打印需要*print-circle*指定为t,使用#n=和#n#来读入)。特别要注意副作用会影响到所有引用它的数据,例如member就会引用原数据,而list会创建一个新的列表(quote就不会)
** 操作列表的参数以形式(setf list1 (** list1))来使用,这里的操作符有的会创建新的列表,有的则是破坏性的destructive
** 破坏性的操作会改变对象的数据,但是不保证会做出如何的修改,而是确保返回值是正确的,这样可以比完全重新创建列表有性能上的好处
** 有无破坏性的函数可能会对应提供,如mapcar和mapcan,remove和delete,append和nconc,subst和nsubst,通常以命名以n为前缀(表示non-consing)
** 可以用declare定义函数的编译参数,以及用declaim定义全局的,包括optimize inline type,用the定义表达式的类型,数组时可定义:element-type,已知类型可以加快对方法的查找。性能的因素可以在不影响语句时再完善
** 可以定义类型,如(vector fixnum 20),(simple-array fixnum (4 4)),(integer 1 100),(simple-array fixnum (* *)),(or vector (and list (not (satisfies circular?))))
** 容易可以定义大于:fill-pointer的空间,以便添加元素用。创建pool可以重复使用以创建的对象,减少cons和gc的开销
** 当流是:element-type 'unsigned-byte时可以read-byte/write-byte
** 定义read-macro通过set-macro-character以及set-dispatch-macro-character来通过流返回列表
** 用户默认包是common-lisp-user,可以用make-package创建用in-package进入,用export导出use-package使用
** 宏loop用来书写迭代过程,可以仿照已有的例子来使用
** 函数error ecase check-type assert会抛出错误,可用ignore-errors转化为nil
** 语句or和and是语句,本身不是函数,也不像C++中那样可以overload为函数
** 可以使用trace以及交互模式中的指令对代码进行调试,交互回话可以保存为映像
** 通过函数load载入文件,通过eval-when在设置在装载或编译时执行语句
** 可以根据所使用的类型,有必要知道一下CL已经自带的一些函数或宏或方法

* Wiki
http://www.cliki.net

* 实现
我暂且用的是gcl和clisp,因为比较容易上手,话说自己还没什么CL的使用使用经验。
IDE可以用SLIME,LispIDE或者带tag和高亮和括号匹配的文本编辑器也行,Cusp不知还在维护否。
有一些开源的符号计算软件使用CL实现的,可以作为学习的资源和参考。

* 其他方言或相关语言
** Emacs Lisp
** Visual Lisp
** Clojure
** Dylan

* 接下来...
** 面向Scheme用户的ML语言的小贴士
** 面向Scheme用户的Haskell语言的小贴士

----
用org-mode列了一个提纲,展开缺乏,看着有点别扭。
有些细节可能会有理解误差,这里仅仅是导读的目的了。
此文纯介绍性质,不建议随意地在实际目的中使用。

----
关于另一个很重要的特性,就是程序边运行边修改,自己还没有这方面的经验和习惯,但是CL的一些语言设计确实有为这种用法考虑。
如果是有经验实现可以选择SBCL+SLIME,本文只是一个浅尝辄止的态度的产物,虽然出发点是想说说Scm和CL的差异的说。
恩...有些地方简略地有些不明不白了...

----
貌似说reduce要优于不定长参数。
loop宏和尾递归是两种风格不同的东西,前者算是更进一步的抽象吧。

2011年6月13日星期一

《现代操作系统》一书中的每章节的小结部分,放在这里的算是此书中比较浅显的概括

把每章节的SUMMARY翻译了一下,书是ISBN0-13-031358-0,有976页,这不是该书当前的最新版本。



MODERN OPERATING SYSTEMS
SECOND EDITION
by Andrew S. Tanenbaum

* 0 前言


* 1 介绍
操作系统可以从两个视角来看待:资源管理器和扩产的机器。从资源管理器的角度说,操作系统的工作是有效的管理系统的不同的部分。从扩充的机器的角度说,操作系统的工作是给用户提供一个比真实机器方便使用的虚拟机器。
操作系统有一个很长的历史,从它们代替一般的操作的时候开始,到现代的多程序的系统。重要的包括早期的批处理系统、多程序系统、以及个人计算机系统。
由于操作系统紧紧地和硬件交互,一些计算机硬件的知识对于理解操作系统的有用的。计算机由处理器、储存器以及I/O设备组成。这些部分由总线连接。
所有的操作系统都依赖的概念是进程、内存管理、I/O管理、文件系统和安全。它们都会在随后的章节里处理。
任何操作系统的核心是一系列可以处理的系统调用。它们表明了操作系统实际能做的事情。利于UNX系统,我们接触了一组系统调用。第一组系统调用和进程的创建和终止有关。第二组用来读取和写入文件。第三组用于目录管理。第四组包括其他杂项调用。
操作系统结构可以有多种方式。最常见的为单片机系统、层状层次、虚拟机系统、集成内核或者使用客户机服务器模型。

* 2 进程和线程
为了隐藏中断的作用,操作系统提供了由一系列进程并行运行组成的概念模型。进程可以被动态的创建和终止。每个进程有它自己的地址空间。
对于某些应用让一个进程里有多个线程控制是很有用的。这些线程相互独立地调度并个人有自己的堆栈,但是进程里的所有进程享用共同的地址空间。线程可以实现在用户空间或者内核当中。
进程和使用进程间的通讯机制相互通信,例如信号、监视、或者消息。这些机制用来确保不会有两个进程同时在它们会引起混乱的关键区域。进程可以正在运行、可运行或者阻塞状态,并且可以在自己或者另一个进程运行一个通讯机制的中断的时候改变状态。线程间的通讯也是类似的。
进程间的通讯机制可以解决一些问题、比如生产消费问题、餐饮的哲学家问题、读取器与写入器的问题、以及睡觉的理发师的问题。即使使用了这些机制,仍然需要避免错误和死锁问题。
许多调度算法为人所知。它们中一些原先是用于批处理系统的,例如最短任务优先。另一些则在批处理系统中和交互系统中通用。包括循环调度、优先级调度、多级列队、保障调度、彩票调度、以及公平共享调度。一些系统在调度机制和调度方针之间有明确的划分,这样就可以允许用户来控制调度算法。

* 3 死锁
死锁是任何操作系统中都潜在的问题。它在发生在一组进程中各被授权独立地访问一些资源,并每个进程也需要已经属于该组中其他进程的资源。这些进程被阻塞并都永远不能再运行。
死锁可以通过跟踪什么状态是安全的以及什么状态是不安全的来避免。安全的状态是才能在的一序列时间可以保证所有的进程可以结束。不安全的状态是没有这样的保证。Banker算法可以通过拒接将系统的置于不安全状态的请求来避免死锁。
死锁可以通过建立一种让它永远不会产生的设计来阻止。例如,通过仅允许进程只能同时持有一个资源,死锁的循环等待条件就被打破了。死锁也可以通过给所有的资源编号、并让进程请求资源严格按照递增的顺序。饥饿可以通过先来先服务的分配方针。

* 4 内存管理
在这章中我们探讨了内存的管理。我们看到最简单的系统不交换或分页。一旦程序被载入内存中,它将持续到结束。一些操作系统仅允许某时刻仅有一个进程在内存中,而另一些支持多程序。
下一步是交换。通过交换的应用,系统可以处理比内存空间更多的程序。没有空间的进程交换出到磁盘。内存和磁盘上的自由空间通过位图或者列表的形式跟踪。
现代的计算机通常有某种形式的虚拟内存。在最简单的形式中,每个进程的机制空间划分为称为页面大小一致的块,可以放置到内存中任意可获得的页面帧里面。有许多页面替换算法:两种较好的算法是Aging和WSClock。
页面系统的模型可以看作程序中抽象的页面引用字符串合计使用同样的字符串的不同算法。这些模型可以用来做出一些对分页行为的预测。
为了使分页系统工作良好,选择算法是不够的;还需要注意到一些问题如工作集的决定、内存分配的方针、以及页面的大小。
分段有助于在在执行中处理改变大小的数据结构并简化了连接和共享。它也促成了不同分段的不同保护方式。有时分段和分页是组合在一起提供了二维的虚拟内存模型。MULTICS和Intel Pentium系统支持分段和分页。

* 5 输入/输出
输入/输出是一个常被忽视,但是重要的话题。任何系统中相当大的部分是和I/O相连的。I/O可以通过三种方式来完成。第一种,是可编程了I/O,其中主CPU输入或输出每个字节或字并保持紧张的循环等待直到它能得到或发送下一个数据。第二种是中断驱动的I/O,其中CPU开始对字符或单词的传输后离开去做别的事情,直到中断到达表示I/O完成的信号。第三种是通过DMA,它是一个单独管理用户传输数据块的芯片,只有在整块数据传输完成的时候给出中断。
I/O可以以四个层次来结构:中断服务程序,设备驱动程序、设备无关的I/O软件、以及用户空间中的I/O库和后台程序。设备驱动处理运行设备的细节以及为余下的系统提供一致的接口。设备无关的I/O软件进行缓冲和错误报告之类的事情。
磁盘有多种类型,包括磁性的磁盘、阵列、和各种光盘。磁头的调度算法通常可以用来提升磁盘性能,不过这关系到所呈现的几何形态的复杂度。通过连接两个磁盘,可以构建特定作用属性的稳固储存。
时钟用于跟踪真实事件,限制进程可以运行时间长度、处理看门狗定时器、以及做报告。
面向字符的终端有一些特殊字符可用户输入和一些转义字符用于输出。输入可以在原始模式或者烹饪模式,这取决于程序想对输入有多少控制。输出中的转义序列控制光标的移动,并用户允许插入和删除屏幕上的文字。
许多个人计算机使用GUI来输出。它们基于WMP范式:窗口、图标、菜单、以及指点设别。基于GUI的设备一盘是事件驱动的,通过键盘鼠标以及其他送往程序的事件被尽快地处理。
网络终端有若干形式。其中一种最流行的是运行复杂地用于构建图形界面的X系统。X窗口的另一中方式是使用低层次的接口通过网络传输简单的原始像素。SLIM终端的实验显示这项技术也可以很好的工作。
最后,电源管理是便携计算机的一个主要的问题,因为电池的寿命是有限的。各种技术可被操作系统采用来降低电源的损耗。程序可以通过牺牲一些性能来帮助延长电池的使用时间。

* 6 文件系统
从外面来看,文件系统是文件和目录的集合,加上对它们的操作。文件可以被读取和写入,目录可以创建和销毁,并且文件可以在目录间移动。大部分现代的文件系统支持层次的目录系统,其中目录可以有子目录,并它们循环地继续有子目录。
当从内部来看,文件看起来有很大的不同。文件的设计者必须考虑储存是如何分配,以及系统是如何跟踪哪个块是属于哪个文件。可以的方式包括连续的文件、链接的列表、文件分配表、以及i-node。不同哦你高的系统有不同的目录结构。文件属性可以放在目录中或者其他地方(例如i-node中)。磁盘空间可以使用自由列表或者位图来管理。文件系统的可靠性通过制造增量的转储来增强以及通过让程序可以出错的文件系统。文件系统的性能是重要的并可以用若干方式来增强,包括转存、预读、以及仔细的方式文件块使之间临近。日志结构文件系统也通过大的单元进行写入来增强性能。
文件系统的例子包括ISO 9660、CP/M、MS-DOS、Windows 98、以及UNIX。它们在许多方面有区别,包括如何跟踪哪个块属于哪个文件、目录的结构、以及自由磁盘空间的管理方式。

* 7 多媒体操作系统
多媒体是新起的计算机的使用。由于多媒体文件的大尺寸和它们严格的实时回放需求,为文本设计的操作系统对多媒体不够理想。多媒体文件有多重的平行的轨道组成,通常是一轨视频和至少一轨音频以及有时也有字幕轨。它们必须在播放时同步。
音频的通过定期的采样来录制,通常是44,100次/秒(CD音质)。压制可用于音频信号定义统一的10x的压制速率。视频的压制同时使用帧内压缩(JPEG)和帧间压缩(MEPG),后者用P帧表示与前一帧不同的地方。B帧则居于前一帧或者下一帧。
多媒体需要实时调度已满足期限。两种算法常被采用。第一种是固定速率调度,它使用静态的优先级算法依照周期给进程赋予固定的优先级。第二种是期限最近优先,它是总选择最接近期限的进程的动态算法。EDF较复杂,但是能实现100%的利用率。RMS那样的则不能。
多媒体文件系统通常使用Push模型而不是Pull模型。一旦数据流开始,从磁盘上读取数据就不再要用户请求。这种途径和传统的操作系统有根本的不同,由于对满足实时请求的需要。
文件可以连续的储存或者不是。在后一种情况下,单元是可以可变程度的(一个块是一帧)或者固定长度的(一块有多帧)。这些途径有不同的取舍。
文件在磁盘上的放置影响到性能。当有多个文件的时候,有时organ-pipe算法可以使用。分割文件到多个磁盘上不论横向或纵向,是常见的。块和文件缓存策略也广泛地用来提升性能。

* 8 多处理器系统
计算机系统可以使用多个CPU来更加快速和可靠。三种多CPU系统的组成方式是多处理器、多计算机、以及分布式系统。其中每一种都有自己的特性和问题。
多处理器由两个或更多享用功能的RAM的CPU组成。CPU可以用总线、交叉开关、或者多级交换网络来互联。多种操作系统的配置是可行的,包括给每个CPU单独的操作系统、有一个主操作系统其他作为从属、或者使用对称的对处理器让一份操作系的拷贝可以让任何CPU运行。在后面一种情况,需要用锁来保持同步。当锁不能获得的时候,CPU可以旋转或者做上下文切换。多种调度是可以的,包括时间分时、空间共享、以及成群调度。
多计算机也有两个或者更多的CPU,但是这些CPU有它们自己的私有内存。它们不分享公用的内存,所以所有的通信使用消息的转递,在一些情况,网络接板有它自己的CPU,在这种情况下主CPU和接板的CPU必须自己组织以避免竞争条件。多计算机中用户层次的通讯通常使用远程过程调用,但是分布共享内存也可以使用。处理器局部的平衡是这里的一个问题,并有用于此的多种算法包括发送者启动的算法,接受方启动的算法、以及投标算法。
分布系统是松散耦合的系统,它的每个节点都是有着完整的外围设备和自己的操作系统的独立的计算机。通常这样的系统分布在较广的地理区域。中间件通常构建在操作系统之上来给应用程序提供一致的交互层。各种的中间件包括基于文档的、基于文件的、基于对象的、以及基于协调的中间件。一些例子如World Wide Web、AFS、CORBA、Globe、Linda、以及Jini。

* 9 安全
操作系统可以在许多方面都到威胁,从内部的攻击到从外界进入的病毒。许多攻击是黑客尝试闯入特定的系统,通常仅仅是猜测密码。这些攻击通常只是使用常用密码的字典并惊人的成功。密码安全可以通过使用Salt、一次性密码、以及挑战响应方案。智能卡和生物指示也可以使用。视网膜扫面已经实用了。
许多不同的对操作系统的攻击是一致的,包括特洛伊木马、登录欺骗、逻辑炸弹、陷阱门、以及缓冲溢出攻击。通用的攻击包括请求内存并对其窥探,使用非法的系统调用来看看发生什么,甚至尝试欺骗内部来透露他们不应该透露的信息。
病毒是许多用于增长的严重问题。病毒存在多种形式,包括内存常驻病毒、引导区感染、以及宏病毒。使用病毒扫面器在查找病毒特征是有用的,不过真正好的病毒能加密它们的大部分代码并在每次复制的时候修改余下的代码,使得检测非常困难。一些杀毒软件不仅仅通过查找病毒的特征码来工作,而是通过发现特定可疑的行为。通过安全的计算机使用习惯来避免病毒要优于尝试处理攻击后的后果。简单的说,不要载入执行来历不明的和可信度有疑问的程序。
移动代码是如今必须要处理的问题。把它放在沙箱内,解释运行、以及仅运行可信赖的供应商签名的代码是可行的途径。
系统可以用一个领域(如用户)垂直或者对象水平的矩阵来保护。矩阵可以切割为行,采用基于能力的系统,或者为列,采用基于ACL的系统。
安全的系统是可以被设计的,但是必须从开始就定位一个目标。可能最重要的设计规则是有一个最小的可信赖的计算机基础不能被任何资源的访问绕开。多层安全可以基于Bell-La Padula模型,为保密而设计,或者Biba模型,用于维护系统的完整性。橙皮书描述了可信系统需要满足的要求。最终,即使系统可证明是安全的,仍必须注意到隐蔽的渠道,它能够轻易的通过创建模型外的通信渠道来推翻系统。

* 10 案例1:UNIX和LINUX
UNIX由作为小型机的分时系统开始的,但是现在使用涵盖从笔记本计算机到超级计算机。存在有三个接口:Shell、C库、以及系统调用本身。Shell允许用于键入执行的命令。可以是简单的命令、管道、或者更复杂的结构。输入和输出可以重定向。C库涵盖了系统调用并也有增强的调用,例如printf用于格式化输出往文件。实际的系统调用接口是精简的,大约100个调用,每个调用仅仅做它需要的事情。
UNIX中的关键概念包括进程、内存模型、I/O、以及文件系统。进程可以分出子进程,产生树状的结构。UNIX中的进程管理使用两个关键的数据结构,进程表和用户结构。前者总在内存中,但是后者可以被交换或者分页出去。进程的创建通过复制进程表以及内存镜像来完成。调度使用基于优先级的算法并请相互进程。
内存模型每个进程由三个片段组成,text、data、stack。内存管理曾是用交换来完成,不过现在多数UNIX系统中通过分页来完成。核心地图跟踪每个页面的状态,页面守护进程使用使用时钟算法来保持大约有足有的自由页面。
I/O设备使用特殊的文件来访问,各自有一个主设备号和一个次设备号。块设备I/O使用缓冲缓存来减少访问磁盘的次数。LRU算法可用来管理缓存。字符I/O可以通过直接或者烹制模式。线路规程或流用于给字符I/O添加功能。
文件系统是由文件和目录组成的层次结构。所有的磁盘被载入有单独的根开始的单独的目录树。单独的文件可以链接如一个目录从文件系统的其他地方。要使用文件,必须先打开,产生一个用于读取或者写入的文件描述符。在内部,文件系统使用了三个主要的表:文件描述符表、打开的文件描述符表、以及i-node表。其中i-node表最为重要,包含了关于文件所有的管理信息以及它的块的位置。
保护是基于根据所用者用户组和其他来控制读写执行操作。对于目录,可执行标记解释为搜索的权限。

* 11 案例2:WINDOWS2000
Windows2000的结构是HAL、内核、执行文件、以及一层薄的用于捕获传入的系统调用的系统服务层。此外,有各种设备驱动,包括文件系统和GDI。HAL从上层中隐藏了一定的差异。内核试图给可执行文件隐藏余下的差异使得执行文件是机器独立的。
执行程序是基于内存对象。用户进程创建它们并取回句柄在随后操纵它们。执行组件也能创建对象。对象管理器维护了什么对象可以插入用于以后查找的命名空间。
Windows支持进程、作业、线程和Fiber(用户空间)。进程有虚拟地址空间和资源的容器。线程是执行的单位并由操作系统调度。作业是一组进程用于分配资源的额度。调度通过通过优先级算法让最高优先级的线程随后执行。
Windows2000支持按需分页的虚拟内存。分页算法是基于工作集的概念。系统维护若干自由页面的列表,这样每当页面错误发生,一般有自由页面可用。自由页面列表使用复杂的规则来调整,试图扔掉长时间里没有使用的页面。
I/O通过遵循Windows设备模型的设备驱动完成。每个驱动由初始设备对象开始,其中包含了系统用于添加设备和执行I/O的调用的过程的地址。驱动可以层叠作为过滤器。
NTFS文件系统是基于主文件表,它为每个文件或目录有一项记录。每个文件有多项属性,它可以在MFT记录中不常驻位于磁盘上。NTFS支持压缩和加密,以及其他的功能。
安全是基于访问控制列表。每个进程有访问控制标记来表明它是谁以及如果有的话,它有什么特别的特权。每个对象有一个和它相关的安全描述符。安全描述符指向一个自由访问控制解表,其中有允许或拒绝个人或组访问的访问控制项。
最后,Windows2000为文件系统维护单独系统范围的缓存。这是一个虚拟缓存而不是物理的。磁盘块的请求首先往缓存。如果它们不能满足,则适当的文件系统被调用以获取所需的块。

* 12 操作系统设计
设计一个操作系统有决定它要做什么开始。接口应该是简单、完整、并且高效的。它应该有清晰的用户接口范式、执行范式和数据范式。
系统应该合理地结构,使用一种已知的技术,例如分层或客户服务模式。内部的组件应该相互正交并清晰的从机制中划分出政策。一些问题应该有多思考,例如静态动态的数据结构、命名、绑定时间、以及实现模型的顺序。
性能是重要的,但是优化需要仔细选择而不会破坏系统的结构。空间时间的权衡、缓存、暗示、地区利用、以及优化常见情况是常常值得做的。
由几个人编写的系统不同于300人生产出的大系统。在后一种情况下,团队的结构和项目的管理在项目的成功与失败中扮演了关键的角色。
最终,操作系统不得不继续改变以遵循新的趋势迎接新的挑战。其中包括64位地址空间、大量的连接、桌面多处理器、多媒体、手持的无线计算机、以及种类繁多的嵌入系统。接下来的年份将会是操作系统设计的激动人心的时刻。

* 13 阅读清单和参考书目




*
下面是闲话时间,当前自己对操作系统的了解仅停留在浅浅的层面。关于操作系统方面还缺乏经验和心得,于是这里来说说Java和Scheme语言中多线程的原理和实现吧。这里的侧重是关于用户模式内线程,利用协程以及时间中断来实现多任务的切换。此外要注意到一些系统调用会阻塞整个进程,这也是需要处理的问题。当然,这部分闲话的内容是可以正好来运用一下操作系统方面的知识。

2011年6月10日星期五

Academy of Interactive Arts & Sciences【介绍文/翻译/施工】

Academy of Interactive Arts & Sciences From Wikipedia
嘛...暂时还没开工...
不过其实也没什么好翻译的,只是想找个条目来写写个人的想法。
里面好多东西不熟悉,也没办法有什么感想。
说自己熟悉的话,还是对NES和GBA要过一些。
另外那里还没有涉及到1996年之前的东西。
这样的话,就先坑在这里了。
要是说自己口味的话,喜欢参与感和空间感强的。
有时候对手柄和格子和树状结构有意外的好感。

2011年6月2日星期四

又一个坑了的视觉小说,\n顺便说说Java与Scheme的混合/嵌入使用

拿Slick2d和Kawa包了一组给Scheme用的API出来,做了一个简单而拙劣的VisualNovel演示。话说第三方库有源代码和JavaDoc外加严格的类型系统和Eclipse的辅助,使用起来还是挺方便的。
下面的东西是个试验用的半成品,不过后来发觉又依赖JRE又依赖平台二进制包感觉挺恐怖的。想分发或者跨平台的话暂且都有问题,也就没动力继续去完善了这个自命名Slickawa的包了。
于是代码什么的在很久很久以前就不继续了,现在拿出来发觉可以用来写篇日志,相当于随手写写遇到的想法吧。

话说Slick的提供的库的API让自己挺欣赏的,至少是打消了再去包装它一层的看法(比如SDL就一定要先包一个主循环出来类出来供派生才觉得舒服)。不过要注意的是Slick是在OpenGL上的一层包装,如果觉得它提供了某个抽象合适的话,就拿来用,或者直接看它的代码。如果不合适的话,大可不必考虑某些类的存在,按照自己的需要直接从更底层的方法往上层建立。达到目的即可,而且目的肯定不是重复一个和Slick一样的抽象层(比如移植其他平台用)吧。
不过SDL1.2和OpenGL用起来还是有个很明显的区别(就像PyGame和PyGlet),它们负责图像混合的设备不同。前者主要靠CPU,后者主要靠GPU,如果不这样顺着它们的原理用的话,性能会大有问题(所以C或者Shader的使用最好是无限制)。如果要在多媒体程序里做一些视觉效果的话,真的还是很容易出现性能瓶颈的。

给Scheme的API分为四组,sk中是基本的过程,skex中是可以用Scm本身来实现的,sklab是试验用方法,sklib是依赖底层的方法。这分别对应四个Java的Class,要暴露的方法实现为静态的,这样在Kawa中就可以直接使用了。
算是语言间类型的自动绑定吧,其实呢Kawa是把Scheme代码编译为Java的字节码,这样在性能上和互操作上都很方便。不过想在Kawa完全使用Java库还是有些不便,所以还是提供为简单的一个个过程较方便。对象仍旧是对象,方法变为以对象为参数的过程。
现在让Scheme代码和Java代码结合的地方可以有两种,一种是用Kawa提供的机制直接在Scm中使用Java的包和对象,一种是在Java中用Kawa中提供的类型映射类创建实例交给自己管理的Scheme运行环境。不过这两种方式都不要指望Kawa能够是Scheme和Java部分能够完全的分离,必然会有一个中间层在Scheme中操作了Java的数组,或者是在Java中操作了Scm的列表。
如果需要添加什么功能的话,要么单独的写为一个包,要么就在那个中间层里放心大胆的写。不必在意是写为纯Java的还是纯Scheme的为好,被中间层所选用的语言所限,不然在使用所依赖的库的时候会遇到麻烦。如果担心会导致所暴露的API会紊乱的话,就放到不同的命名空间里去。也不必在意是否暴露的足够的API,只要要中间层有足够的活性(有废止的方法标记一下就可以了,反正只涉及静态方法和单方法的界面,单一元素的修改仅会带来很有限的影响。),混合语言所带来的将不是负担而是效率了。中间层即使Java和Scheme语言同时用也可,目的都是让纯Scm部分的代码可以用某个函数,所以还是倾向于在Java中写这个中间层同时Scheme也用。至于Java6的那个对脚本语言的框架,造成通常得首先包装为对象传给那个框架,然后在用脚本语言提供适合自身类型的包装。用来两次包装之后,想实现一些功能的话,放在哪个部件里都觉得不是很方便。
相比另一个Scheme的Java实现SISC,本来自己是觉得它更小巧一些,所以一开始的更偏爱一些。后来觉得Kawa做得更成熟一些,于是就从比较省事的角度选择的Kawa。之前在用tinyscheme做过一次绑定,得自己添加垃圾收集用的类型,再添加Unicode编码的支持,再针对类型的转换把提供的API函数一个个写一遍。而且用到的函数得去tinyscheme的头文件里找,然后再去长长的单个源代码里看看是怎么回事后才用,再传递指针的时候很容易就把类型信息丢掉。结果就是TinyScheme的代码也混杂在宿主程序里面一起修改了。Java的好处是可以把单独要改的类拿出来改一下然后用(只要不涉及包的可见性以及开源的许可协议的限定的话),原本引入的jar文件可以丝毫不变的放在那里。不过话说似乎好多Scm实现都是为单独使用设计的,想手工包装到C/C++里面都有点手疼,而更倾向于用FFI在Scm代码中用C库(于是对第三方库就可能是先用C包一层,然后再Scm针对类型转换包一层了。这样总比在C中连类似什么也考虑要手疼的好一些,虽然这里来说说感想也看不出多少可再用的价值)。
有个chibi-scheme时间不如TinyScheme,但是设计得更加完善一些。

Java和Scheme都有Steele的参与,这或许是让两者看起来很协调的一方面吧(只是或许而已)。两者都有各自的变量命名习惯,都喜欢使用长的n的单词组合。虽然一个用大小写来分割单词,一个用横线来分割单词,但是这两者对应起来还是相当直观。都是很少用语法糖,都是写起来手疼,都是值是有类型的对象的语言。所以两者混合用起来还是蛮协调一致的感觉。
Scheme的运行环境有两个地方,一个是让Scm代码自己成为一个Java类,一个是人工的管理一个Runtime对象。如果仅仅是把Scheme作为数据来看待的话,似乎后者会比较方便。Scm的一个优势是自身可以当作一种数据格式来使用,Kawa中提供了解析供的方法。而让Scm代码去实现某个interface然后传回Java也是可以的。此外还可以main中直接eval,提供一些API后控制权给脚本,让Scheme当胶水使用。
如果仅仅是想REPL地试验一下某个Java代码片段,有个BeanShell还是蛮方便用的。

关于下面的实现,还有些额外的说明。这里就列举一下,语句会不大通顺。首先呢,当类型不重要的时候,用Scm中的List来传递值了,这样比从对象里查询要方便一些(如果List是Lazy的话就更方便了,语言的执行就是 Graph Reduction)。其次来说,这里Java的主循环三种事件要调用Scheme提供的函数。Java部分只要只管要时去调用,而场景的切换放在Scm里面做了。至于场景切换要怎么修改提供给Java的过程,以及场景切换中还是否要做一些额外的事情,Java部分是不用管的。所以这里为了方便的在Scm中实现多个场景,做了一个简单的OO。注意逻辑和渲染和必须分割开来的,中间可以用全局(相对两者而言)来传递信息。还有呢,涉及RGBA混合的过程无论写在Scheme中还是写在Java中都是会性能不够的,正确做法是写在OpenGL里面,可惜自己还不会。动画效果这里参考了Qt的状态和变换框架,这里仅仅提供了一个用来进行y=kx线性变化的类。其实利用y=f(kx1)+g(kx2)的组合还是能做出很多效果的,比如以线性变化为时间轴映射到多个变量上,或者串联多个线性变换,这些都是可以发挥想象里的地方。然后作用坐标、透明度、颜色以及其他可以数值表示的地方,看起来还是挺生动的(这方面Flash是榜样,不过Flash性能限制)。

写的时候参照了KRKR2/KAG3和ONScripter和RenPy的部分代码,以及rpgchina.net和gamediy.net上的教程,以及Slick和PyGame和ClanLib的API:
;;;开始
(sk:make "VisualNovelDemo")
(sk:path ".;visualnovel")
(sk:display-mode 800 600 #f)
(sk:fps 60)
(sk:debug #t)
;;;OO
(define (send obj msg . arg)
  (cond ((obj msg)
         =>(lambda (x) (apply x arg)))))
(define (dispatch prototype method-alist)
  (lambda (mth)
    (cond
      ((assoc mth method-alist) => (lambda (x) (cadr x)))
      ((procedure? prototype) (prototype mth) )
      (else #f))))
;;;全局变量
(define *env* (interaction-environment))
(define *background* #f)
;(define *script* (sk:load-data "script.scm"))
(define *script* (skex:load-line-data "script.scm"))
(define *scripts-now* #f)
;;;全局过程
;(define *music* #f)
(define (state-clear)
  (set! *scene*
        (dispatch '()
                  (list
                   (list 'frame (lambda (delta) '()))
                   (list 'button (lambda (b) '()))
                   (list 'render (lambda (g) '()))
                   )))
  )
(define (dialog-jump! label)
  ;(sk:display "try")(sk:display label)
  (let ((s (member (list 'label label) *script*)))
    (sk:display s)
    (when s
      (state-clear)
      (set! *scripts-now* s)
      (script-next!)
      ;(sk:display (state))
      )))
;;;场景
(define *scene*
  (dispatch '()
            (list
             (list 'frame (lambda (delta) '()))
             (list 'button (lambda (b) '()))
             (list 'render (lambda (g) '()))
             )))
;;;对话场景
(define (scene-dialog text)
  (define dialog-font (sk:load-font "Simhei" 32))
  (define *dialog-char-count* (skex:make-linear 0 (sk:string-length text) 1))
  (define (dialog-end?)
    (skex:linear-end? *dialog-char-count*))
  (define (dialog-end!)
    (skex:linear-end! *dialog-char-count*))
  (define (on-frame delta)
    (skex:linear-next! *dialog-char-count* (* 0.03 delta)))
  (define (on-render g)
    (sk:draw-color 30 428 (sk:color 0 0 0 128) (- 800 60) 128 )
    (skex:draw-text
     34 432
     (sk:substring text 0 (skex:linear-now *dialog-char-count*))
     (- 800 64) dialog-font (sk:color 0 0 0 128))
    (skex:draw-text
     32 430
     (sk:substring text 0 (skex:linear-now *dialog-char-count*))
     (- 800 64) dialog-font (sk:color 255 255 255 255))
    (when (dialog-end?)
      (sk:draw-color 740 530 (sk:color 128 255 128 255) 8 8)
      )
    )
  
  (define (on-button b)
    (if (or (equal? b '(pressed A)) (and (pair? b)(eq? (car b) 'touch)))
        (if (dialog-end?)
            (script-next!)
            (dialog-end!))))
  (dispatch
   '()
   (list
    (list 'frame on-frame )
    (list 'button on-button )
    (list 'render on-render )
    ))
  )
;;;scene-effect 背景切换效果
;;;背景切换场景
;;;(bg "1.png" 0)
(define (scene-bg-change arg)
  (define *bg-future* (sk:load-image (cadr arg)))
  (define *bg-changing* (skex:make-linear 0 1 (/ 1 (caddr arg))))
  (define on-frame
    (lambda (delta)
      (skex:linear-next! *bg-changing* delta)
      (when (skex:linear-end? *bg-changing*)
        (set! *background* *bg-future*)
        (set! *bg-changing* #f)
        (script-next!))))
  (define on-button
    (lambda (b) '()))
  (define (on-render g)
    (sk:draw-image 0 0 *bg-future* (* 255 (skex:linear-now *bg-changing*))))
  (dispatch '()
            (list
             (list 'frame on-frame )
             (list 'button on-button )
             (list 'render on-render ))))
;;;scene-effect 背景遮片效果
;;;effect-mask
;;;(effect mask "1.png" "rule.png" 100)
;;;name = 'mask
;;;arg = '(mask "1.png" "rule.png" 100)
(define (scene-bg-effect arg)
  ;;to extend this lambda class
  (define (make-effect bg-future effect-render-with-p% time-last)
    (dispatch '()
              (list
               (list 'frame (lambda (delta) '()))
               (list 'button (lambda (b) '()))
               (list 'render (lambda (g) '()))
               )))
  (case (cadr arg)
    ((mask) (scene-bg-mask-change (cdr arg)))
    ((bg) (scene-bg-change (cdr arg)))
    (else (error "effect"))))
;;;'(mask "1.png" "rule.png" 100)
(define (scene-bg-mask-change arg)
  (define *bg-future* (sk:load-image (cadr arg)))
  (define *bg-mask* (sk:load-image (caddr arg)))
  (define *time-line* (skex:make-linear 0 1 (/ 1 (cadddr arg))))
  (define on-frame
    (lambda (delta)
      (skex:linear-next! *time-line* delta)
      (when (skex:linear-end? *time-line*)
        (set! *background* *bg-future*)
        (set! *time-line* #f)
        (script-next!))))
  (define on-button
    (lambda (b) '()))
  (define (on-render g)
    (sklib:draw-image-with-mask 0 0 *bg-future* *bg-mask* (* 255 (skex:linear-now *time-line*))))
  (dispatch '()
            (list
             (list 'frame on-frame )
             (list 'button on-button )
             (list 'render on-render ))))
;;;用户界面场景
(define (scene-ui arg)
  ;(ui ((text 116 500 "开始" fnt clr) start) ((text 516 500 "退出" fnt clr) end))
  (define *ui* '())
  (define (ui-draw x)
    (map (lambda (i)
           (apply
            (case (caar i)
              ((text) sk:draw-string)
              ((image) sk:draw-image)
              (else (error i)))
            (map (lambda (j) (eval j *env*)) (cdar i))))
         x))
  (define (ui-area x)
    (map
     (lambda (i)
       (list
        (let ((arg (map (lambda (j) (eval j *env*)) (cdar i))))
          (let ((x (car arg))
                (y (cadr arg))
                (s (case (caar i)
                     ((text) (sk:text-size (caddr arg) (cadddr arg)))
                     ((image) sk:draw-image)
                     (else (error i)))))
            (list x y (car s) (cadr s))))
        (cadr i)))
     x))
  (define *button-area* #f)
  (set!
   *scene*
   (dispatch
    '()
    (list
     (list
      'frame
      (lambda (delta) '()))
     (list
      'button
      (lambda (b)
        ;(sk:display (ui-area *ui*))
        (when (and (pair? b)(eq? (car b) 'touch))
          ;(touched (1 2))
          (let ((x (caadr b))(y (cadadr b)))
            (map (lambda (i)
                   (when (skex:contained? x y (car i))
                     ;(sk:display (cadr i))
                     (when (symbol? (cadr i))
                       (dialog-jump! (cadr i)))))
                 (ui-area *ui*))))
        (when (and (pair? b)(eq? (car b) 'cursor))
          ;(touched (1 2))
          (let ((x (caadr b))(y (cadadr b)))
            (set! *button-area* #f)
            (map (lambda (i)
                   (when (skex:contained? x y (car i))
                     (set! *button-area* (or *button-area* (car i))))
                   (when *button-area*
                     (set!
                      *button-area*
                      (apply
                       (lambda (a b c d) (list a b (+ 2 c) (+ 2 d)))
                       *button-area*))))
                 (ui-area *ui*))))))
     (list
      'render
      (lambda (g)
        (when *button-area*
          (apply sk:fill (append *button-area* (list (sk:color 0 0 0 128)))))
        (ui-draw *ui*)))
     )))
  (sk:display (cdr arg))
  (set! *ui* (cdr arg))
  *scene*)
;;;解析并执行脚本
(define (script-next!)
  (if (not (null? (cdr *scripts-now*)))
      (begin
        (set! *scripts-now* (cdr *scripts-now*))
        (state-clear)
        (cond ((string? (car *scripts-now*))
               ;;显示对话
               ;;"Hello World"
               (set! *scene* (scene-dialog (car *scripts-now*))))
              ((pair? (car *scripts-now*))
               (case (caar *scripts-now*)
                 ((bg)
                  ;;切换背景
                  ;;(bg "05_1_2.jpg" 100)
                  (set! *scene* (scene-bg-change (car *scripts-now*))))
                 ((effect)
                  ;;切换效果
                  ;;(effect mask "sdf.jpg" "rule/12.png" 2000)
                  ;;(effect bg "rule/12.png" 2000)
                  ;;(effect bg:mask "rule/12.png" 2000)
                  (set! *scene* (scene-bg-effect (car *scripts-now*))))
                 ((ui)
                  ;;显示界面
                  ;;(ui ((text 116 500 "开始" fnt clr) start) ((text 516 500 "退出" fnt clr) end))
                  (set! *scene*(scene-ui (car *scripts-now*))))
                 ((music)
                  ;;播放音乐
                  ;;(music "11.ogg" 100)
                  ;(sk:play-music (sk:load-music (cadr (car *scripts-now*))) #t)
(sk:play-music (cadr (car *scripts-now*)) #t)
                  (script-next!))
                 ((eval)
                  ;;(eval (set! fnt (sk:load-font "Simhei" 48)))
                  (eval (cadr (car *scripts-now*)) *env*)
                  (script-next!))
                 ((label)
                  (script-next!))
                 ((jump)
                  (dialog-jump! (cadr (car *scripts-now*))))
                 (else (error "未知指令"))))
              ((null? (car *scripts-now*)) (script-next!))
              (else (error "未知指令"))))
      (sk:exit)
      ))

(sk:on-init
 (lambda (e)
   ;;;载入剧本
   (set! *scripts-now* (cons '() *script*))
   ;(set! bgm (sk:load-music "music/bgm03.ogg"))
   (script-next!)))
(sk:on-frame
 (lambda (delta)
   (send *scene* 'frame delta)))
(sk:on-button
 (lambda (b)
   (send *scene* 'button b)))
(sk:on-render
 (lambda (g)
   (when *background*
     (sk:draw-image 0 0 *background* 255))
   (send *scene* 'render g)))
(sk:start)

所用的脚本格式中@开始的行是命令,读取是首尾加括号,不然则加引号。读取后是一个sexp的列表,这样可以在Scheme中直接操作。
话说IEEE标准里没有收eval语句,因为有点细节上的欠缺。虽然R5RS里有,个人还是觉得这里也不必要去用完整的eval过程,不过自己改写出来一个eval还是个重复劳动。
脚本就是纯文本,话说一开始的写法是每个字符串要加引号。虽然有正则这种东西,但是想重新分段什么的还是不方便。与其每次保存是要处理,不如直接扩充Reader部分。
上面的代码在重构的时候消灭了很多全局变量,顺便也抽象出来一些较直观的概念。这些修改不影响跑测试代码,但是这体现Scm代码的进化方式。
以上的OO是基于原型的消息传递,还缺少一个set-this消息,继承时会用到。不过添加这样一个消息的话,对已有代码影响很小,而且暂时没用到,所以还没有添加。

Java部分的代码或许以后传吧,没多少内容
不过还是暂且来简单的写一些说明吧——
首先呢,是最常用到的若干包和类(以及类的方法):
kawa.standard.Scheme 对应 Scheme的一个运行环境;
gnu.lists.LList 对应 Scheme中的类型,列表;
gnu.mapping.Symbol 对应 Scheme中的类型,符号;
至于整型就是Integer,字符串就是String
想把sexp字符转为Scheme的嵌套Pair的h话:
LispReader reader = new LispReader(new InPort(new StringReader(sexp)));
return ReaderParens.readList(reader, 0, 1, -1);
至于Number的话可能是gnu.math.Numeric也可能是java.lang.Number,
如要用instanceof判断之后再获取值,比如调用.floatValue();。
此外,现在绘制是一堆draw-*的过程,另一种实现方式是:
提供 <? extends Drawable & Posable ...>然后统一的用draw来调用。
这样就转变为考虑怎样在场景里放置物品,而让绘制操作可以自动进行。
这部分暂时还没用到,只是在Java中申明了Interface,还是放到Scm中需要的时候写这部分好了。
话说一般用对象的使用,以及界面的实现好了,类的派生什么的还是少用为好。
比如这里Java给Scheme的API就不包括类的派生,不过Silck2D的习惯本身也没有精灵类。
有时候嘛,精灵类还是需要的,用来管理场景里哪些东西要绘制,若仅仅是OO的话,还是放在Scm中为好。
在Scheme中可以用lambda来做参数传递时转化为实现了所需要的只有一个方法的界面,
此外Kawa提供的一些机制还是可以用虽然目前不够完善所以用着不舒服。
kawa中导入Java的类是使用(define-namespace sk ),
然后可以直接使用静态成员方法,创建和使用对象也有提供了方法的。
想开一个交互的调试窗口的话,可以执行如下的代码(可以绑定到按键上):
ReplDocument doc = new ReplDocument(scm, scm.getEnvironment(), true);
GuiConsole con = new GuiConsole(doc);
con.setVisible(true);
其中scm是Kawa提供的Scheme类的实例,也就是我们的scm代码编译后运行的环境。
像Gambit-C一类编译类的Scm实现,直接和C混合着用虽然看起来不够美观。
但总比让FFI的部分复杂要好,毕竟C在内存管理和类型转换上比Java的鸿沟大。
另一个编译器Chicken是可以即当编译器也可嵌入当解释器用,不过Chicken的内存管理方式比较异类。
值得庆幸的是,两者都可以定义变量在GC时执行的过程,这样可以省写一些Free(不要和Close混淆哦,open/close是成对等价于with使用的)。
是嵌入使用,还是有FFI调用外部类,不同的实现有不同的方式的。此外,用C库的话,还有字符串编码问题要自行解决一下。
关于语言标准,IEEE出于简化考虑比R5RS少了eval和卫生宏,不过R5RS还没有module和record-type这两个很有用的组织代码的手段。
再如果仅仅是拿sexp当数据格式来使用的话,那就和使用什么实现没有关系了,手工解决即可。



剧本部分是拿以前的日志重写的,现在有点反胃了。
所有@开始的行已删除,现在算是一个纯文本了。
说实话,废话比以前多了,而且人物感觉更加薄弱。
在遥远的被阻隔的异世界
记忆之海
埋藏着记忆力淡去的痕迹
虽然显得遥远而迷茫
可是……

《遥远的记忆之海》

【Chapter 0】
这里是死后的世界,而年轻的我是记忆之海上的渡船人。
每天都会有亡灵抵达这里,来在这片汹涌的波涛下探寻往日的回忆。
虽然只是一些被遗忘的影像,这恐怕是对被阻隔在异世界的亡灵们有效的抚慰。
我将用这片小舟载着他们,驶向掩埋于他们意识的幻境之中,去探寻这份被遗忘的记忆。
不知道自己是如何开始这项职业的,也不知道是否在遥远的未来会有某个终结点。
大概我便会这么一直以这样平淡而幸福的方式生活下去吧——每天都可以期待不同的故事。
而自己就做好一个船夫好了,这点好奇心尚不会为自己的记忆增加多余的烦恼。

【Chapter 1】
【我】 坐稳呦~要出发了~
我在船尾向着船头走上来的一位老奶奶说道。
然后熟练而悠闲地划动起身旁的桨,让船缓缓却高速地飞向某个预定的水域。
海水在炫目却冰凉的天空下闪着光芒,一眼望去只能看见浅浅的海面。
目标抵达后,我让船平稳地停下而随意地顺着波浪起伏,然后向着船上老人站立的那一头说到——
【我】 你的记忆便保存在这里,在这片浑浊的水面之下。不过还有若干事项要知道——
【我】 在波浪起来的时候,要努力地往海底望,凭自己的坚定的意志去努力地望。
【我】 让自己的身躯仿佛为回忆包围、洗涤、沉浸,直到某一刻海水骤然变得清晰。
【我】 这样你便能看见深深的海底,看见那片保存着你的记忆的地方。
【我】 那是只属于你的地方,只有凭自己的力量才能看见,而别人的帮助对此无能为力。
而老人听见了我的话,点了点头,然后将视线远远的望着海平面的尽头。
她用手触摸着船的边沿,却一直没有敢低下头去将视线触及身旁水面的光芒。
虽然犹豫了一会,老人随后还是放弃了努力,也没有去尝试,转身向内在船头坐下。
【我】 就真的这样放弃了吗,这可是一个难得的机会,不需要那个世界的记忆了吗。
每个人只有一次机会来这里探寻活着时候的记忆,老人的行为多少让我不解。
【老人】 确实呢……
老人轻轻地应了声,然后坦然地点了点头,颤动着的嘴唇似乎有话要说。
我也暂且安下心来,在这水天包裹的空间里躺在船的一角,听着老人传来的言语:
【老人】 随着人生漫长的时光,我已经是一个年迈的人了。记忆力在一天天流逝,身体也一天天薄弱。
【老人】 变得不再有力气去触摸身边的世界中的幸福,而对周围事物的意识也越渐模糊。
【老人】 活着,自己却越来越难以感受到自己是存在着的。于是生命的最终一刻,便在完全不知觉中抵达了。
【老人】 过完了一生,纵然有坎坷或遗憾,可终究还是幸福的一生,可以让我安心于死后了。
【老人】 可是衰老的记忆力依旧是留下的残缺,对于自己最后的时光,竟然不能在心里珍藏着这份幸福。
【老人】 所以我便来到了这片记忆之海,想弥补这份残缺,想为自己寻找一份安慰。
【老人】 至少不论生后或生后,我想让自己明白,自己确实是幸福的生活着的。
【老人】 不过啊,果然我还是有这份幸福感便足够了呢。
【老人】 这种感受纵然已经被遗忘,也肯定在那个世界里曾经存在过吧。
我打量着对面的老者,觉得她是在害怕由记忆去触及内心的真实,不禁叹气感到遗憾。
老人在船头沉默地坐着,渐渐平静了略带激动的言语,我随后起身撑起船驶上回程之路。
伴着海平面上柔和而巨大的夕阳,船的划痕消失在身后的那片先前抵达过的水域,
这也同时告别了那里那些对于老人将永远被掩埋于海底的对往日的记忆与情感。
这过去的,便是一个普通的工作日,随着一天天的过去,我不久也就会把这天淡忘的。
因为这毕竟只是我不断往复着的类似的日子中的一天,一个没有源头也没有终点的记忆所背负的时光。
接下来一天的客户是一位中年男子,身体健壮没有疾病的样子,看上去是个生活中积极的人。
他是意外而死去的吧,真是遗憾呢,我简单地这样判断着。然后撑起船,又一起踏上一个新的旅途。

【Chapter 2】
船平稳地向预定的某地行去,今天的海面的波浪依旧缓和,我在船头悠闲地把着方向。
而他在船的另一头显出好奇,往四周那片行船偶然路过的水天之痕,不断地张望。
【我】 这途中附近不知是埋藏着谁的记忆,在无尽的记忆之海上,恐怕连我也可能不再会抵达这里。
我静静地坐在船的末端,以船夫的身份这样解释道,然后视线依旧向着遥远的目光不能触及的何处。
【中年】 好了,我是有点兴奋,毕竟是第一次来到记忆之海。如果这样做对航船有影响的话,我会注意的。
我能够感受到他一旁的视线,那似乎是一种寻求着言语上的联系的期待,这恐怕是出于他待人的热情吧。
【我】 我想知道,你为什么要用这唯一的机会,到这里探寻自己的记忆呢?你还没有到一个健忘的年纪。
【中年】 我来寻找记忆,并不是因为它们被我遗忘,而是因为它们所承载的时光已经离我而远去。
【中年】 我是想寻找一丝安慰——即使将来记忆变得淡去,记忆也曾经凭借着自我的存在而存在过。
【中年】 这也是那个生者的世界所留给我的痕迹,告诉自己往日的努力的意义所在,没有遗憾也是一种遗憾吧。
【中年】 那些记忆纵然不能在物质上于现在现在的我建立起沟通,但是分明就是我在这里可以依旧延续下的一种财富。
【中年】 虽然记忆显得飘渺但却是中真实,这也就是我来到记忆之海缘由,想在这里挖掘到的一份心灵中的宝藏。
船速已经变缓,这是即将到达预定的海域了,我向他做着关于探寻记忆的说明,引着他往船外沿的地方走去。
然后留下他一人等待海风的气息掀起记忆之海翻滚的浪花,祝福他的思绪将能够触及到脚下海水的彼端世界。
风与浪下船头的挺拔地站立着,在海天间映出一个厚实的身影,轮廓仿佛是一扇凭空为世界展开的门。
他的声音在海风之间,在倾诉着言语,在一层层的展开着他的过去——
【中年】 这是我的出生,这是我的父母,这是我的学校,
【中年】 我的成年,我的工作,我的老婆,我的女儿,我的事业,我的生活……
男人在回忆着他活着的经历,眼前海水的蔚蓝色里翻滚着幸福的味道,一丝腥味,又一丝甘甜。
在随后回程的航线上,他一直在向我分享着他在这里获得的满足——
【中年】 美好的期待一直在延续着,直到有一天,那天的一场意外的事故,让我只能告别那一切。
【中年】 回忆是以痛苦终结的,我想把这最后的时刻忘掉,可是又害怕失去了想守护的东西。
【中年】 我便这样犹豫着在这个死后的世界,后来想法开始有些坦然——
【中年】 记忆的存在并不是因为它们被记住,而是它们被经历过。
【中年】 记忆的痕迹即使随着时光淡去而似乎不曾有过,它们也不会消失依然存在。
【中年】 不经意间看到自己身体上留下的微小残缺,知道这便是一种以不可弥补的痕迹来保存着记忆。
【中年】 我想我脑海中的记忆也是一样的,是因为我曾今为着他们的存在而做出过什么。
【中年】 这便足够了,它们就是我在记忆之海下埋藏的一份财富,将永远为海天守候。
【中年】 所以,记忆里的东西无法改变,也不需要改变了,在现在这个世界里依然可以有新的记忆。
他只是说着他想说出来的事情,似乎不在意我是怎样漫不经心地听着,这样能否记住他说出的话。
在傍晚的时候,打量着我们周围海面似乎永恒不变的夕阳景色,我想——
他说出了现在的感受,这样便是让这种感受以与人共同的记忆的形式确认着存在,让自己能够保留下去。
不过即使这样,着对于我,仍只是不知过去与未来的时光里的一天,一个在永恒的时间轴上的一个渺小痕迹。
而接下来一天的客人是位年轻的学生,年龄似乎与我接近而略小,我依然只是职业地将他引上船。
今天的天空似乎有些异样的阴沉,不过不会影响到航行,于是这又将开始一个新的旅程。

【Chapter 3】
我控着方向,让船往预定的地点驶去,在有些阴沉的天空下划过淡淡的线条。
他和来到记忆之海的大多数人一样,在船的前头打量周围,以未知抚慰着未知,显得有些无措。
回忆的味道就是这样的吧,我也顺着他的视线向远方望去,心中划过这样的想法。
其实的船行不需要过多的体力操作,在今天格外低的天空之下,航行显得有些沉闷。
不过如果天空晴朗,这就是个同往日一样的航行了,充满生活的平淡的温馨,平淡便足够了。
我沉浸在自己的思绪中,随后忽然感到船身的一个摇晃,我看见船对面的那个身影站起身子。
他向我走来,我赶忙先稳定下船的航行,然后放松下身子。此时听见他说话的声音——
【青年】 您比我大吧,我可以叫你哥哥吗?
【我】 我已经是不在意自己的年龄了。
【青年】 那我就叫你哥哥吧!
【我】 这随便你。
【青年】 嗯,哥哥好!
【我】 ……
他在我一旁坐下,没有把刚才打招呼的热情继续下去,而是将视线转向远方,有些顾虑的样子。
【我】 来说你为什么要来到这里,是想寻找到怎样的记忆吧。
虽然他的举动让我感到有些奇怪,我还是试图以平静的语气挑起对话。
【青年】 不知道。
他把头转向我,带着年轻人的稚气。
【青年】其实我确实不知道,我没有想寻找的东西。
即使这样也不必要有多余的犹豫吧。
【我】 这里有记忆的宝藏哦,会有让人感到幸福的东西,不要为让自己可以努力的机会而犹豫。
【青年】可是那些记忆是我主动抛开的,它们让我感到难受,我却矛盾地又为一种缺失感来到这里。
【我】 好了,既然来到这里了,即使意外,也会找到些东西吧。
【我】 它们都曾今属于你,今后也依然是。想着抛弃,就可能会让自己失去更多的东西。
他看着我的眼睛,像在寻找一种希望。而航行也在这不知觉中驶近了目的地。
【我】 去吧,相信自己心中小小的愿望,在海浪之间寻找能够寻找到的东西。
【青年】 恩,谢谢哥哥。
他答应了,然后走向船的那头。站立在船的边沿上,在阴沉的海天之间,显得波澜壮阔。
而我一人在船的这头放松下身子,视线漫无目的地望向天空,意识到居然对着客人说出自己的想法。
我这是说给谁听的呢。一个陌生人?一个在这死后世界里再也不会相遇的人?还是说……我自己?
我在怀疑着自己举动的意义,是否就代表着自己已经做出或者将会做出一些多余的事情呢。
可是此刻我异样的感觉到,我的思绪与情感在被他联系着,从未有过的被一个人所联系着。
想着在此刻能为别人的困难添上自己的帮助,这同时也是在敞开心扉地能够去接受别人的帮助。
看到船头转过头来望着我,在脸上露出一个微笑,而我看着也在自己脸上回应着一个微笑。
两个人的微笑,未曾在这里奢望过的微笑,我意识到这是一个第一次在这死后世界里自己希望永远记住的东西。
忽然平静的一切被打破了,一个浪打在了船身上,他刚刚侧转的身子没有站稳,摇晃着向记忆之海里跌落下去。
在这一瞬间里,海风也变得巨大了,天空更加阴沉下去,而且似乎要发展往一个糟糕的方向。
此刻顾不上太多,我想我得救他。赶忙跑向他所在的船头,往波浪翻滚的海水里跳了下去。
我在下沉,身边被黑暗包围着。那是一片无尽的黑暗,我感到瞬间失去了重力,努力划着水却寻找不到方向。
周围也异样的安静,这是一个海面下无声的世界,时间的流淌似乎漫长,甚至凝结。
渐渐地我的心也在水中平静下来,我能感受到自己的呼吸,呼吸在这个海底并无异样。
恐惧之后这我才意识到,这是在一个死后的世界,一个已经不会再死去而离开的世界,
一个欣慰却又孤独的世界,一个没有失去也没有保留的世界,一个让记忆永恒封存的世界。
在这个黑暗无声的世界里,我顺着水流漂浮着,让意识随着身体漂浮着。
在记忆的海洋中以心灵去触摸着曾今,以及那些孕育着现在和未来的往昔时刻。
我能体会到在深海之下闪烁的幽幽光芒,像一个指引者在前方对我轻轻的召唤着。
我凭借着意志向光芒游去,让光点在眼前接近在身边扩散,觉得这样才能够拯救到什么。
这样才能够实现自己对他人的一个守候,一个哪怕在这死后世界已经显得多余的举动。
一种是哪怕只是出于内心原始的善良的淳朴的冲动,也要去带着落海的少年顺利的回来。
光芒弥散于海底,如同思绪飘荡在无尽的水中接受着温柔而永恒的抚慰,包裹在我的周围。
渐渐地,这些光点构成的屏障上清晰出一个影像,在向我讲述着什么。
在向我呈现着一个“生”所在的世界,一个在海底无尽的黑暗里心所体会的“光”所组成的世界。

【Chapter 4】
【青年】 真是一个空旷的操场啊。
耳边响起了青年的言语,轻轻地感叹着,然后声音又远去了。我想这是少年的记忆传递给我的信息吧。
在他的视角上,我以第一人称在看到了他的双手,和他眼前的整片操场。
接下来将在这个记忆构建的世界里,以一天天的经历,去体会着他的过去。
可是这只是视觉,他的意识以及他的存在感,却无法通过薄弱的纽带感觉到。
身体开始活动起来,由机械变得自然地走动着,向着教学楼那里去寻找人的踪影。
我的意识仅仅是随着身体漂浮着,在这个世界里不能左右什么,也不会有想去左右什么的愿望。
在阳光下曝露的身体,接触着这里的空气与水分,仿佛在由陌生不断变得亲近着。
与空旷的操场相比,教学楼因为人的踪迹,而显出格外不同的氛围。
身体继续往教室里走去,人的气息的存在让它移动着的脚步学会了自信。
同时身体也是在渴望着阳光之外的什么,让自己来到这人群中去寻找着。
身体在经历着成长,可是这样似乎仍缺少着什么,它所见到的人群都在它面前散开了。
当身体带着疑惑去走进下一个教室,带着或许会有改观的期望,依旧得到的是一个失望的结果。
【青年】 果然人们是讨厌我的,这个世界里已经不需要我的存在了。
此刻身体举动正是表达着他的意志,这让我清晰的感受到意识的存在,但是明白这种体验的阻隔。
之后,身体便失去了他本身的驱动,松软下来。重量压在了我的身上,而我只是依然麻木地站在教室中央。
刚才跑去教室外的人群开始渐渐返回,他们仿佛无视了我的存在,又回到了他们的日常。
我就这样站在教室里,近距离的带着身体逝去的愿望。去接触人群的气息,去沉浸于海洋的梦境之中。
可是,梦境最终将迎来黎明。
【同学】 刚才真是一个讨厌的人啊,就这么像无视我们的跑近教室来。”
是“讨厌”吗?一句声音之后,周围的声音也纷纷附和起来,成为这个狭小的教室里共通的话题。
我能感受到其中的恐惧,这正是我能从身体中所感受到的。
让我出于内心的害怕,从这个空间里,从这个人群中向外跑去,跑向一个与此隔绝的世界中。
我在遥远的地方大口的喘着气。驻足后望,那个教室,那片学校已经变成渺小的身影。
这已经足够远了,这已经是喊声不能再被传递到的距离了。我的心开始变得空荡荡的。
这是一种安心吗?我看着自己缓慢起落的脚步,在坚硬的水泥地面没有留下任何的痕迹。
我再去望那片远去的世界,一个希望着阻隔开的,一个永远只保留着不起眼的存在的世界。
我漂浮在这个梦境的世界,和一个已经属于我的身体,因为躲避着什么而肆意游荡着。
只到有一天,也是偶然而特别的一天,我似乎是感到孤单,感到厌倦了。
也似乎是出于好奇,决定再去一次那所学校,再次重拾起曾今想寻找的什么。
校门在眼前清晰了,操场在眼前清晰了,教学楼在眼前清晰了,曾今去过的那所教室在眼前清晰了。
自己觉得心中的愿望,去寻找一个未知到重要的东西的心愿,也这路途上变得清晰了。
这或许就是在凭借着海的梦境的经历,来记忆里所未填的空缺吧。
在那间教室的前,我敲了敲门走了进去,心里已经做好了接受各种意外的准备。
这不是躲闪着旁人视线,而是在寻找他人的帮助,寻找着一种重拾遗失的启示。
他站在教室门前,鼓起勇气地努力睁开眼,去接受着人群的目光。
身体在人群中肆意的走动着,人们并没有做出异样的反应。
身体想在人群中挑起话题,人们的意志中已经淡忘了他的痕迹了。
这我才发现,他所做出决定已经无法改变,关于那个让自己消失于此的决定。
他已经不再属于这个世界了,这一个他曾想抛开却一直未割舍的世界。
那我又将飘向何方呢,向着一个新的永恒的世界吗?
在意识在这个身体已经在远离我而去的时候,我又似乎不愿回到那片记忆之海上。
因为在我海的梦境中,在这死后的世界里,我是第一次意识到了我所希望寻找的东西。
我的意识回到的我的身体上,我闭着眼漂浮在黑暗的无尽的记忆之海的深处。
随着水流飘向我一个不知为何方的地方,一个永远将背负着记忆的地方。
在漫长的飘荡中,终于,我决定,睁开我的眼睛,去看看周围的世界。
去看看身边的世界,哪怕看到的依旧只是黑暗,去依旧能在世界里发现着什么。

【Chapter 5】
这是一片天空柔和的色彩,西斜的阳光透过玻璃照射在我面前的床铺上。
双手是棉被的触觉,鼻尖是医药水的味道,身边有机器在运转的声响。
这是一个空荡荡的病房,我孤单的一个人躺在中央。身体不能动,只能望着粉白的屋顶。
在这狭小的容器里,我感到内心的一阵寂寞与痛苦,因为孤单而在对外界恐惧而害怕着。
害怕着我的记忆里所寄托的想象,会成为自己的一种负担,会让自己面对着未知却只会守护着丝丝迷茫。
我闭上眼,想象自己正被水包围着,想象就在海底,在那片无尽的黑暗中。
希望再次抵达那个世界每天航行在记忆之海上,一直地去陪同着别人寻找着别人在寻找的东西。
于是在这样的孤单中,我的意识又变得模糊开始轻浮,再向一个遥远的地方飘去了。
直到忽然间响起清脆的敲门声,这让我回过神来,可是我仍害怕着不敢睁开我的眼睛。
我听出来是同班的同学来了,他们的话语声在翻滚着我的记忆,清晰着我的心跳。
这让我想到,我忽视了一些自己没有察觉的细节——
那片无尽的大海中也有一份属于自己的记忆吗,是否他们一直埋藏在那里而我却没有在意呢。
我是否也有过一丝小小的心愿要实现——在那个异世界中,也去寻找属于自己的那一份记忆呢。
那些记忆是一份仍然需要自己继续延续下去的记忆,它们还尚未到一个应该被海水所掩埋的时刻。
处在这个生者的世界里的我,是否只有那个异世界里才能找到唯一的归宿,而该早早的就去呢。
可是活着不该是一个被轻易淡忘着的概念,而让记忆独自在无尽深海之下,去忍受着永恒的没有终结的孤单。
是啊,刚才心头居然有抛弃“生”的世界的想法,自己觉得可笑。
去寻找活着的的自己的意义所在,才是对那个死后世界无尽时光最好的终结。
况且一直有人在为着我的“生”而努力呢,让我能够再次的活过来,而我竟想拒绝他人的心愿。
它们的努力让我能够有机会去真正地体会到自己内心的希望,这同时也是他人给予我的希望。
不论是给予的关心或者是给予的治疗,都是由这一切的努力才让我又回到了这个世界中。
【同学】 真希望你能够早点醒过来哦,和我们一起去看班级篮球赛。
熟悉的声音在面前在病房中弥散开来。
而我的内心出于一种固执,依然让自己的双眼紧紧闭着,用拒绝去回避心头残余的害怕。
【同学】 今天我们先走了,明天放学的时候还会来看你的。
然后是一个轻轻的关门声。
病房里恢复了先前的安静,又只剩下我孤单的一个人在这狭小的房间里。
此刻安静得如同深海的气息,我心头的另一种害怕又让自己猛地设法睁开自己的眼睛。
可是我发现自己的眼睛没能睁开,只是忽然有泪水的热度在脸颊上流淌开来。
我努力地张开口,努力地说着——
【我】 只是一个旅途,这几天里我去了一个遥远的地方,现在我已经回来了……
破碎的言语没有出声,只化作苦涩与甘甜,混杂着口角泪水的味道。
而所告别的旅途呢,
将依然安静地在一个遥远的地方延续下去,延续往彼世界中所不存在的尽头。

话说这种质量不高的应急式产物果然连自己都讨厌,当然GalGame是不应该用这样东西做剧本的。
还是去看 阿加莎 克里斯蒂 的小说去吧。。。
顺便觉得小野不由美的也不错。。。
我这里质量拙劣,仅此而已。。。

----
后来试了试html5+Biwascheme或Processing+Kawa或Processing+自己解析脚本,just work。