2011年5月3日星期二

关于UTF-8编码的使用

编码转换的话,最通用的工具算是iconv/libiconv,可以当命令行工具使用,也可以当C语言库使用。在C语言中有两种类型来存放字符,通常char用来表示单个ASCII字符,用wchar_t来表示宽字符(可以是Unicode),字符串用'\0'表示终止。其实语言标准只是规定这两种类型的长度,其中char是8-bits,而wchar比char长,至于具体的数据所使用的编码没有固定。不过把wchar[]放在char[]中会导致连续8bits的0会让已有代码误以为字符串已经结束,并且统计字符个数时可以直接使用wchar[]的长度(例如中文的一个汉字和英文的一个字母都占一个wchar)。在8bits的char中ASCII编码占用低7bits,中文gb/gbk编码的做法是使用最高位做标记,每个中文字符占两个char。如果要转换为unicode编码的话,可以使用建立好的映射表。UTF-8是Unicode字符的一种编码方式,当最高位为0时和ASCII编码一致,否则最高位为1的个数表示这个字符将占几个char来储存。这样一来UTF-8编码的字符用char[]来存放,当仅使用ASCII字符时与ASCII编码一致,相比wchar每个字符都占用的等长的空间要节约空间。在使用的时候,UTF-8编码的char[]可以直接转换为wchar_t[]的宽字符,之后按照宽字符处理,某些系统会提供ASCII的接口和宽字符的接口两个版本。不过,最简单的做法是,只把字符编码在IO时处理,外部用ASCII和UTF-8来储存,然后统一读取为Unicode来处理。比如Java和Python3都是内置了这样的做法。

常用的简体中文编码GB2312/GBK以及后续兼容标准所属的代码页为CP936,使用如下命令可以把CP936转换为UTF-8的编码格式:
iconv -f CP936 -t UTF-8 < input.txt > output.txt
具体的,命令行用法详见"man iconv",函数库用法见"man 3 iconv"。

在iconv不可用的情况下,UTF-8转UNICODE可以很直接的实现,以下代码摘自SDL_ttf:
/*
SDL_ttf: A companion library to SDL for working with TrueType (tm) fonts
Copyright (C) 1997-2004 Sam Lantinga
*/
static Uint16 *LATIN1_to_UNICODE(Uint16 *unicode, const char *text, int len)
{
int i;
for ( i=0; i < len; ++i ) {
unicode[i] = ((const unsigned char *)text)[i];
}
unicode[i] = 0;
return unicode;
}

static Uint16 *UTF8_to_UNICODE(Uint16 *unicode, const char *utf8, int len)
{
int i, j;
Uint16 ch;
for ( i=0, j=0; i < len; ++i, ++j ) {
ch = ((const unsigned char *)utf8)[i];
if ( ch >= 0xF0 ) {
ch = (Uint16)(utf8[i]&0x07) << 18;
ch |= (Uint16)(utf8[++i]&0x3F) << 12;
ch |= (Uint16)(utf8[++i]&0x3F) << 6;
ch |= (Uint16)(utf8[++i]&0x3F);
} else
if ( ch >= 0xE0 ) {
ch = (Uint16)(utf8[i]&0x0F) << 12;
ch |= (Uint16)(utf8[++i]&0x3F) << 6;
ch |= (Uint16)(utf8[++i]&0x3F);
} else
if ( ch >= 0xC0 ) {
ch = (Uint16)(utf8[i]&0x1F) << 6;
ch |= (Uint16)(utf8[++i]&0x3F);
}
unicode[j] = ch;
}
unicode[j] = 0;
return unicode;
}

其他语言中的实现,例如OCaml中的,可以参考如第三方下库:
http://ocaml-extlib.googlecode.com/svn/doc/apiref/UTF8.html

上面已经说过用UTF-8来储存并读取为Unicode来处理,我们现在所需要的只是这两个编码间的互相转换就可以了,并注意Unicode编码还有一个字节序的问题。

2011年4月15日星期五

鼓励表达

原文2011年03月28日写在自己的QQ日志上,标题与内容无关,现在备份与此。
当时写文的目的是想表达缩手缩脚不到代表能避免失败,意思是说要有积极的行动吧。

这篇的话题是关于向别人介绍东西的模式。

这个话题首先来说介绍东西,其目的是自己向别人传递信息。其中所要达到的效果是自己把要表达的意思传递出于,而同时别人也能有效准确的接收并认同。

这个话题的另一方面,或者说是此篇的重点和目的所在——模式。

对于做一件事情,例如这里说的介绍东西这件事情。我们可以说要认真努力地去做,并说是克服惰性不让自己偷懒什么的。但是这样的问题在于,一些主观的情感上的态度并不能代表对处理一件事情的经验,而且对于特定的事情经验会比热情要稳定可靠。另一方面,一味强调精神和品格,会为行事过程中带来很多压力。

模式(Pattern),是针对一类事情在处理方式上的共同点,是处理相似的不同的问题时可复用的结构。例如这里的问题是介绍东西,而模式就是一些向别人介绍东西的方式和方法。通过使用模式,可以获得前人处理问题的经验,也总结了自身的经验,使得问题可以处理的越来越好。同时,依照模式来处理问题,简化了问题的分析思考,使得问题的处理有了一些最基本的质量保障。

具体的说就是。模式的作用体现在两个方面:
1.保障。通过遵循已有的模式的约束,可以在有限的行为下获得充足的效果。
2.复用。将处理问题的经验总结为模式,以便之后使用和遵循。
比如说一些文书有特定的格式要求。依照这个格式来写就是给文字的展开提供了直接的思路,同时在满足了格式的同时也保证需要的内容都有提及。

也就是说在处理问题中运用模式,对于减少负担、提升质量都是有益处的。

那么这里回到在这一篇里所说的模式——向别人介绍东西,下面的内容将试图为此类模式提供一个示例:

首先可以向别人描述整个介绍行为的“动机”,也是是说为什么要介绍,为什么要介绍给对方,为什么由自己来介绍等等问题。在介绍动机的时候,可以是从历史的角度来说,介绍事情相关的背景。或者展示情景和问题,而让介绍的内容成为对应的一种解答。在对动机的描述中,重点还没有切换到所要介绍的事物具体是什么,而目的是为接下来的介绍行为提供一个连贯的思路和线索。

接下来可以展示一个简单却完整的“示例”,即使对方不理解也没有关系。这里的目的不是让对方完全接受所介绍的内容,只需要对所给的例子有整体的印象便可。在这么早的时候给实例的目的,是想给对方一个具体的全局的观感。对方在知道实例后,便能够去尝试应用示例,便可判断自己是否已经迈出了第一步。通过先给的实例,也让介绍者先试图以简化的视角来开始整个介绍。虽然事情会有它自身的复杂性,但是介绍过程是为了简化事情被介绍的复杂性而进行的,而不是出于复杂性给介绍者的满足感。同时,这个实例也是为引出更具体的描述和更多方面的展开。

现在便到了整个介绍过程中占据时间最长的部分,也就是介绍内容的主体部分,对要说的内容的“展开”。这里原则是尽量减少这部分带给人的意外感。把所要说的话题和描述话题的方式都尽量在之前给实例时候都简要的提及,这部分只是对先前给过的思路的展开。在这一部分中对信息处理的主动权是交给接收者的,而对方依据自己已经描述过的思路来获取信息。而作为介绍者而言在这一部分所要做的是对提出的问题进行分析,并将新的要点在分析中展示出来。从外表上看这一部分会和前面的那部分有所重复,这种重复感是让别人接受的一种方式。并且让这个分析解释部分显得冗长的时候,反而会给对方带来期待感。

从某种意义上说,下面要说的这部分占的时间不是很长,却是会给接收者带来最深刻印象的一部分——这就是对对所介绍内容进行的“抽象”。上面详细的分析说明的那一步,虽然占时间最多,但是信息传递的主体是在接受者上,那么现在这一步就又将主体转回到说明者上了。让说明者来强调所介绍的是什么,而不是像先前的分析过程重在思路的展开上。在这一步里,可以借助一些名词来概括,或者提供一些用来回忆再现和相互交流的手段。这是也可以推荐一些手册供对方查阅。

现在到了最后的一步,作为介绍者而言需要给对方的反应以“回应”。给予对方正确的判断以肯定,同时双方自由交流一些态度和看法,并留给对方展示的余地。而不是让介绍过程以一种反正自己尽责了的心态来进行。

此外,在介绍过程中可以结合图表实例来获得更好的演示效果。

那么,我这里所说的一种模式便介绍完了。这里的动机并不是什么应该怎么做或者可以怎么做,以上内容的选取并不是从实际的经验的出发的。因为这里介绍者的身份很容易联想到教师或者作者之类的身份。而其实呢,向别人介绍一些事情是人与人之间总在发生的事情。

以上所写的内容的出发点,是想展示一下“模式”带来的审美观念。假如处理问题是从模式出发的话,可以较少负担,并获得有保障的效果。从这样一个角度来想问题还是很有趣的。

就像是说,对于先前做过的事情,去向“如果当时……就能……”。这样的想法是对以后做同样的事情提供经验,而并不能保证先前的事情一定就有最好的结果。如果想先前一点可遗憾的东西也没有的话,那么一个逻辑上说的通的选择是——什么都不要做。

好了,本文的内容也就是尽量逻辑通顺而已,什么模式也没去用。

2011年4月14日星期四

用OCaml的进行形式文法解析

本来是想写这篇的,因为OCaml确实在这一个领域很出色的语言。虽然ML代码结构看上去比C语言要清晰易读许多,不过说实在的自己的大脑还是实在没养成按照写公式一样的写出代码来。结果就是在FP中还是用了已经比较熟悉的Scheme进行试验了,感觉写Scheme就像玩橡皮泥,在拿捏和拼装之后,一个原本还较陌生的逻辑就写出来了。这里的意思是说对于我这篇所要达到的目的,还是用Scheme给先写出来的。这不是关于语言特性的,而是关于思维的波长正好和问题一致而已。如果一定要牵涉到语言层面的影响的话,只能说我是在问题和语言都初次为一个情景而组合的,暂时还达不到追求美感的境界。就像听音乐,不管音乐是好好听,音质高就会很有震撼了。

于是本文成了草稿的回收了,那个另一个版本的半成品(至少是半成品,有个完整的结构也好啊)写在别出了,过段时间在整理过来。当然,不要指望这些文字里有什么东西,个人日志什么的(至少像我这里的几篇)写起来还是很随意的。

这里所用的示例是最简单的整数四则运算表达式的处理,目的包括求值和转化为前缀形式的语法树。



在OCaml手册中有对ocamllex/ocamlyacc的描述,并给出了这样一个例子。虽然写法上还是是依照lex/yacc的,不过鉴于OCaml的语言风格所以看起来似乎要直观一些。

于是这里就直接大篇幅的引用过来了。

http://caml.inria.fr/pub/docs/manual-ocaml/manual026.html
12.6 A complete example
The all-time favorite: a desk calculator. This program reads arithmetic expressions on standard input, one per line, and prints their values.

Here is the grammar definition:
/* File parser.mly */
%token INT
%token PLUS MINUS TIMES DIV
%token LPAREN RPAREN
%token EOL
%left PLUS MINUS /* lowest precedence */
%left TIMES DIV /* medium precedence */
%nonassoc UMINUS /* highest precedence */
%start main /* the entry point */
%type main
%%
main:
expr EOL { $1 }
;
expr:
INT { $1 }
| LPAREN expr RPAREN { $2 }
| expr PLUS expr { $1 + $3 }
| expr MINUS expr { $1 - $3 }
| expr TIMES expr { $1 * $3 }
| expr DIV expr { $1 / $3 }
| MINUS expr %prec UMINUS { - $2 }
;

Here is the definition for the corresponding lexer:
(* File lexer.mll *)
{
open Parser (* The type token is defined in parser.mli *)
exception Eof
}
rule token = parse
[' ' '\t'] { token lexbuf } (* skip blanks *)
| ['\n' ] { EOL }
| ['0'-'9']+ as lxm { INT(int_of_string lxm) }
| '+' { PLUS }
| '-' { MINUS }
| '*' { TIMES }
| '/' { DIV }
| '(' { LPAREN }
| ')' { RPAREN }
| eof { raise Eof }
Here is the main program, that combines the parser with the lexer:

(* File calc.ml *)
let _ =
try
let lexbuf = Lexing.from_channel stdin in
while true do
let result = Parser.main Lexer.token lexbuf in
print_int result; print_newline(); flush stdout
done
with Lexer.Eof ->
exit 0

To compile everything, execute:
ocamllex lexer.mll # generates lexer.ml
ocamlyacc parser.mly # generates parser.ml and parser.mli
ocamlc -c parser.mli
ocamlc -c lexer.ml
ocamlc -c parser.ml
ocamlc -c calc.ml
ocamlc -o calc lexer.cmo parser.cmo calc.cmo

这里把四种运算符都写在定义层次的产生式上了,然后额外的指定了结合顺序和优先级。



在OCaml中另一个用于功能类似的模块是CamlP4,作为库它基于Stream类型提供了用于词法与语法解析的功能,并同时自身也在OCaml中充当了预处理的角色。

递归下降的形式可以用camlp4可以写为这样的形式:
http://www.codecodex.com/wiki/index.php?title=Recursive_descent_parsing
(* File parser.ml *)
let rec factor = parser
| [< ''('; e = expr; '')' >] -> e
| [< 'c >] -> int_of_string (String.make 1 c)
and term = parser
| [< e1 = factor; e = term_aux e1 >] -> e
and term_aux e1 = parser
| [< ''*'; e2 = term >] -> e1 * e2
| [< ''/'; e2 = term >] -> e1 / e2
| [< >] -> e1
and expr = parser
| [< e1 = term; e = expr_aux e1 >] -> e
and expr_aux e1 = parser
| [< ''+'; e2 = expr >] -> e1 + e2
| [< ''-'; e2 = expr >] -> e1 - e2
| [< >] -> e1
let rec statement = parser
| [< e = expr; '';'; ''.'; stream >] ->
Printf.printf "= %d\n%!" e;
statement stream
let rec read() = match input_char stdin with
| ' ' | '\t' | '\n' -> [< read() >]
| c -> [< 'c; read() >]
let () = statement (read())

然后执行如下的编译命令
ocamlc -g -dtypes -pp camlp4o parser.ml -o parser

其原理仍然是基本的递归下降文法解析,特别的地方在于parser是对stream进行的模式匹配和处理,使用语言糖使得函数的定义和生成式的书写有申明式编程的感觉。其他语言如Haskell里也有类似的模块存在,语法糖长相的差异不是问题的说。



关于递归下降语法解析,编译原理方面的书(TC++PL里怎么也会有呢?)都会给出示例的,比如像下面这个:
http://en.wikipedia.org/wiki/Recursive_descent_parser
What follows is an implementation of a recursive descent parser for the above language in C. The parser reads in source code, and exits with an error message if the code fails to parse, exiting silently if the code parses correctly.
Notice how closely the predictive parser below mirrors the grammar above. There is a procedure for each nonterminal in the grammar. Parsing descends in a top-down manner, until the final nonterminal has been processed. The program fragment depends on a global variable, sym, which contains the next symbol from the input, and the function getsym, which updates sym when called.
The implementations of the functions getsym and error are omitted for simplicity.

typedef enum {ident, number, lparen, rparen, times, slash, plus,
minus, eql, neq, lss, leq, gtr, geq, callsym, beginsym, semicolon,
endsym, ifsym, whilesym, becomes, thensym, dosym, constsym, comma,
varsym, procsym, period, oddsym} Symbol;
Symbol sym;
void getsym(void);
void error(const char msg[]);
void expression(void);
int accept(Symbol s) {
if (sym == s) {
getsym();
return 1;
}
return 0;
}
int expect(Symbol s) {
if (accept(s))
return 1;
error("expect: unexpected symbol");
return 0;
}
void factor(void) {
if (accept(ident)) {
;
} else if (accept(number)) {
;
} else if (accept(lparen)) {
expression();
expect(rparen);
} else {
error("factor: syntax error");
getsym();
}
}
void term(void) {
factor();
while (sym == times || sym == slash) {
getsym();
factor();
}
}
void expression(void) {
if (sym == plus || sym == minus)
getsym();
term();
while (sym == plus || sym == minus) {
getsym();
term();
}
}
void condition(void) {
if (accept(oddsym)) {
expression();
} else {
expression();
if (sym == eql || sym == neq || sym == lss || sym == leq || sym == gtr || sym == geq) {
getsym();
expression();
} else {
error("condition: invalid operator");
getsym();
}
}
}
void statement(void) {
if (accept(ident)) {
expect(becomes);
expression();
} else if (accept(callsym)) {
expect(ident);
} else if (accept(beginsym)) {
do {
statement();
} while (accept(semicolon));
expect(endsym);
} else if (accept(ifsym)) {
condition();
expect(thensym);
statement();
} else if (accept(whilesym)) {
condition();
expect(dosym);
statement();
}
}
void block(void) {
if (accept(constsym)) {
do {
expect(ident);
expect(eql);
expect(number);
} while (accept(comma));
expect(semicolon);
}
if (accept(varsym)) {
do {
expect(ident);
} while (accept(comma));
expect(semicolon);
}
while (accept(procsym)) {
expect(ident);
expect(semicolon);
block();
expect(semicolon);
}
statement();
}
void program(void) {
getsym();
block();
expect(period);
}

在这个解析过程中,是根据下一位的单词进行解析状态的判断的。
比如说在读到1+1时,下一位是+(1+1+)或者*(1+1*)就要按照不同的语法树解析。


关于OCaml做这方面应用的还有一个优势是ADT(抽象数据类),很方便用来描述类型的并级。
并且OCaml的纯静态类型是在编译时擦除变量类型的,这也一个简单且基础的类型机制。
此外将虽然句法解析的能力包括了词法解析,但是将两者分开来的话能都使得实现变得简单。
比如整数运算到浮点数的扩充,就可以放在词法解析的过程中先识别。
上面的示例还有一些可以扩充的方面,比如顺便增加一下条件语句和赋值语句什么的。

那么就顺便说下OCaml中类型的复合,有:
Tuple/Recode —— int*char,{num: int; denum: int};
Enum/Union —— Int|Char,Int of int;
Variant Type —— 'a t;
Module/Class —— 有点像C++中的模板
Signature —— 这个是作为参数使用的类型
其中类型用tpye定义,并允许定义中递归。
此外的arrary和list类型可以看作已有复合方式的再一次复合,虽然说会单独被实现的。
使用ADT的好处是在解析文本后生成的节点都用类型的Union来表示,
至于OCaml的特有特性Polymorphic variants则可让系统自行定义类型的联合。


最后强调一点,一定要看到下面这句话哦,不然出什么问题就会麻烦了
——以上的内容仅娱乐目的,不保证内容一定正确哦。
http://rosettacode.org/wiki/Y_combinator

2011年3月31日星期四

lex/yacc

乔姆斯基体系将形式文法的表达能力划分为四个层次,分别是Type-0到Type-3,并呈包含关系。

* Grammar

先为了使表述简洁清晰,来定义如下名词:
non-terminal
terminal
ε
rule/production

* Regular grammar

其中的Type-3称为正则文法,可以用正则表达式来描述,并转化为有限自动状态机。
它是文法中最简单的一种情况,它可以用来匹配和捕获文件中的字符序列。

正则文法的形式是:
B -> a
B -> aC
B -> ε

正则表达式中用基本字符表示其本身,并用若干方式表达字符的组合方式。
其中“|”表示选择,“*”表示0个或多个,“()”表示结合顺序,连续排列表示相连接。
在日常使用的正则表达式中,会用到一些其他的常用的表示符号。
它们可以用上面的更基础的方式表示出来,这里不必举例。

借助于非确定有限自动机(NFA),正则表达式可以转化为确定有限自动机(DFA)。
通过已接受的字符确定的当前状态可以通过将要介绍的字符转向下一个状态。
可能到一个中间状态,或者匹配失败或者转移到某个接受状态上。

通常我们借助Lex这个工具生成又DFA表构成的程序,例如用GNU的flex可以:
下面这个.lex程序可以用来输出文件中的全部带符号整数:
%{
#include
%}
%option noyywrap
DIGIT [0-9]
INTEGER ("+"|"-")?{DIGIT}+
%%
{INTEGER} {
printf("Got integer %s\n", yytext);
}
.|\n
%%
int main(void){
yylex();
return 0;
}
如果要在别的代码中使用它的话,例如结合yacc来使用,会在下面的内容里再说。
详细情况可以参考http://flex.sourceforge.net/manual/


* Context-free grammar

其中的Type-2称为上下文无关文法,可以用BNF(巴克斯-诺尔范式)来描述,并转化为下推自动机。
它是多数程序设计语言文法的基础,可以用它来将Token序列转化为文法对应的语法树。

上下文无关的意思是满足 V -> w 使得V总能被w替换而无需考虑上下文。
虽然程序语言中变量的含义是上下文有关的,不过我们可以到更高级层次的文法再处理。

和正则表达式不同的是,在BNF中允许递归文法的出现,其表达能力呈包含关系。
而在EBNF(扩展巴科斯范式)中,递归文法可以表达为产生式右端的可选部件。

当一个BNF使得一个词法满足多个结构的语法树时,则称这个文法是有歧义的。
可以通过改写规则增强约束来消除歧义,或者可以在解析时增加额外的限定。

不同的Parse方式对BNF文法的处理能力有差异,所以下面将结合Top-Down和Bottom-Up来说明。

常用的Top-Down方法是LL(1),意思是从左到右根据一个预读单词解析当前最左端的单词。
实现时可以手写递归下降的解析器,或者基于Stack使用解析转移表。
例如对于EBNF:
exp -> term { addop trem }
addop -> + | -
term -> factor { mulop factor }
factor -> '(' exp | Number ')'
可以把exp写为tmp=term();while(peekToken==op){match(op);tmp=op(tmp,term())}return tmp;
这里可选部件和做递归是等价的。

条件语句和语句块可以写为:
补丁
需要避免else结合时的歧义

递归下降的解析器可以直接通过EBNF来改写,额外的要求是在预读字符后要能够转移到特定的规则上。
虽然用Left Recursion Removal和Left Factoring可以改写部分EBNF是指满足上述的改写方式。
但是因为这种解析方式的能力限制,依然对文法的First Set和Follow Set有所限定。

常用的Bottom-Up方法是LALR(1),可以通过工具yacc来生成程序。
LALR(1)文本的状态不再直接对应所给的规则,而是基于Stack和Action table进行Shift和Reduce操作。
当Action table不确定时会产生歧义(shift-reduce或reduce-reduce),在该解析算法下有歧义时工具会给出提示。

下面会使用GNU的Bison来构建一个简单的正整数四则运算的运算器的,容错机制通过在解析规则中添加额外的规则来弥补。
%{
#include
#include
#define YYSTYPE int
%}
%token NUMBER
%%
stmt :
| stmt expr '\n' { printf("%d\n", $2); }
| '\n'
| error '\n' { yyerrok; }
;
expr : expr '+' term { $$ = $1 + $3; }
| expr '-' term { $$ = $1 - $3; }
| term
;
term : term '*' factor { $$ = $1 * $3; }
| term '/' factor { $$ = $1 / $3; }
| factor
;
factor : NUMBER { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| '(' expr error { $$ = $2; yyerror("\")\" missing"); yyerrok; }
| '-' factor { $$ = -$2; }
%%
int main(void){
return yyparse();
}
int yylex(void){
int c;
while ((c = getchar()) == ' ');
if (isdigit(c)) {
ungetc(c, stdin);
scanf("%d", &yylval);
return NUMBER;
}
return c;
}
int yyerror(char *msg){
fprintf(stderr,"%s\n", msg);
return 0;
}

这里还需要做一些额外的说明:
使用%left '+' '-';%left '*' '/';可以标记运算符的优先级和结合顺序,这样可以不必把expr的规则分解为多个层次来避免歧义。
上面的yylex通常是由lex生成的,其中NUMBER或者其他类型的单词类型可以用yacc生成的.h文件在两者之间共享。
这里指定“YYSTYPE int”表示每条规则的获得的值和返回的值都是int,若要使不同规则使用不同类型的话用%union申明并在%token和%type指定。
详细参考http://www.gnu.org/software/bison/manual/bison.html


* Abstract Syntax tree

最近玩多了Scheme,其中的sexp是AST的一种很天然的表达形式。C语言的Struct,用Class构成的Composite模式,ML语言的ADT,还有json都是很相似的表达。
根据本编的上下文的话,就是在lex中获得匹配的文本,而在yacc中构建树结构。
例如在lex将匹配的类型放在由yytext存放在yylval中,而yacc中可以{$$=new Node();doSthWith($$)}。
不过貌似ML系的写法要好看一点。


* Sematic Analysis

上面也说过在词法和语法解析过后,目的是获得语法树。能在解析中直接处理当然更快捷,不过没有处理语法树来得简单。

这里要做一些事情,标记语法树节点的类型,将数值字符串转化为数值类型,生成符号表判断词法作用域。


* Compile

如果要让程序可以运行的话,可以构建Stack虚拟机或者三地址虚拟机,并设计内存在运行时的管理方式。
C语言是全局变量Static,函数调用是基于Stack,而分配的空间在Heap上。
然后,在生成代码的时候,计算好内存间的相对位置,再做一些不影响程序语义的优化。
简单的说,就这些内容了,写出来整理一下思路。

* End

详细见这份资料吧:
《Compiler Construction. Principles and Practice. by Kenneth C. Louden》

2011年3月16日星期三

Game & FP

随意记点东西。


* Game

在Linux下SDL是最知名游戏库了。它本是作为商业产品移植Windows下游戏而生,所以作用是游戏和本地平台间的代理。其功能主要来说就是图像、声音、事件三个部分,也包括格式、字体和一些系统相关的组件。

不过呢,通常称SDL是多媒体库,是指它提供的功能在娱乐之外也会用到。而只要能让代码执行,不论是否属于多媒体,也可以用于娱乐的目的。

Java中处理2D图像有通常有两种方式,自带的Java2D或者系统的OpenGL。它们和SDL一样,都可以用来绘制2D图形。

不过在游戏中,绘制的概念已经有些底层了。通过抽象出Spirit的概念,可以更好的描绘游戏世界。例如按钮或者人物、背景都可以作为精灵对象出现在屏幕上,并参与交互。

一种做法是建立抽象的Entity类,供完成具体实现,并提供实用方法。例如Qt的GraphicsView中的Item类,提供的接口包括绘制托管、事件实例、坐标变换、碰撞检测。

最简单的Spirit会有两个最重要的接口,是update和paint。在固定帧率的游戏中,以固定的方式被调用。

基于对象的方式,可以使场景的设计可视化进行。一个例子是Scratch,只需要添加属性和事件,而不用直接面对事件分派和如何绘制的问题。

采用上面的抽象方法的一个问题是,为了简化逻辑我们希望主循环负责全部的事件处理,Spirit的职责仅需是查询抽象事件。

如果要给已提供Spirit添加功能的话,可以派生子类或者以其他的方式重用其组件。而另一方面,对于游戏又希望逻辑部分和平台部分分割开来。

这样的直觉是想到了MVC模式,游戏数据代表模型,游戏的展示效果代表视图。

游戏逻辑可以看作模型的一部分,他的作用是发挥在控制器部分——由视图的上操作来修改模型。而模型上的修改又被视图展示出来。可以通过观察者模式连接组件。

这样的话上面的Spirit类就有了功能上的细分,Image对象负责显示效果,Item对象负责管理Image对象,它们是视图的部分。模型上可以有Shape对象,它可以用来检测碰撞,以作出回应。也可以用来施加变换,并引起对应的Item的改变。

一些类的用法也就得到了丰富,如Tile类不是指单个的Spirit类的实例。而是提供地图的数据和对应到图像的方式,就可以获取碰撞情况并让视图负责显示出来。作为比较可以看J2ME和Slick2D在该模块上封装的差异。

所得的结论是:用对象的组合(包括拥有、回调、通知、链式)要比用类的扩展具有更高的灵活度。当已有的组件越丰富时,重用也就更加方便。由于可以重构实现,两种方式可以提供一样的接口,就可以按需选择。

对于游戏需要一个存放全局状态的地方,可以是一个单实例类,或者让用户在使用库时自己创建,或者让别的对象的方法增加表示这个状态对象的参数。


* UI

CLI和GUI的体验差异不是在视觉的感官上,因为通常的程序可以划分为以下三个部分:
1.平台依赖,包括硬件设备和所用的库;
2.用户界面,是和用户产生交互的地方;
3.程序逻辑,从上面两方面获得的信息对它们产生影响。

程序的逻辑可以封装成lib*.so,而CLI和GUI是属于UI的部分都可以作为这个so文件的提供的函数的视图。如果以是以鼠标还是用命令来区分界面的话,也不严密。因为CLI可以用curses库,而GUI也可以只放一个文本框。

这里想说的是,似乎对CLI和GUI的差异的感觉,是出于对有有状态和无状态的交互的差异感。多数CLI程序是无状态的,至少程序内不含系统的部分是无状态的。由命令调用后返回结果后,程序立即退出。

如果一个CLI在进入后是可以进行用户命令的交互话,这样的CLI就可以认为是有状态的。它的状态是以程序的进入和退出为一个的周期,这是一个交互的回话存在的周期。前后命令间会有影响,同样的命令会在不用的状态下可以有不同的意义(特别是Y/N的时候)。这里的交互是程序与人双方的,人可以由程序不同的状态执行不同的操作。

相比而言GUI是有状态的,我们称当前界面对用户的回应方式为模式。一个模式对话框弹出,然后用户使用并提交,这个CLI中有状态的交互方式是一致的。关于不同界面的效率问题,可以参见《人本界面》这是模式之外的另一种衡量方式。


* Iter

在Scheme中为了提高程序效率,可以把递归改写为尾递归。利用尾递归优化将递归变为迭代,此时引入副作用可以减少在表达“反复”时临时状态所带来的消耗。

这里以求5的阶乘为例:
(letrec ((f (lambda (x) (if (zero? x) 1 (* x (f (- x 1))))))) (f 5))
其中f是一个递归函数,自变量x在特定的值上递归终止。
引入一个迭代变量后得到尾递归形式:
(letrec ((f (lambda (r x) (if (zero? x) r (f (* r x) (- x 1)))))) (f 1 5))
这个利于语法糖衣也能写为:
(let loop ((r 1) (x 5)) (if (zero? x) r (loop (* r x) (- x 1))))
(do ((x 5 (- x 1)) (r 1 (* r x))) ((zero? x) r))
其中的共同点是在使用跌倒变量保存中间结果,并在迭代终止是将结果返回出来。
也可以在引入一个变量i供迭代用,原本的x以原样传给它的连续:
(let loop ((r 1) (i 1) (x 5)) (if (> i x) r (loop (* r i) (+ i 1) x)))

另一种例子是不使用数而使用列表:
(letrec ((f (lambda (x) (if (zero? x) '() (cons x (f (- x 1))))))) (f 5))
按同样的方式改写,并引入副作用的话,就得到(set! r (cons ... r))。这的使用方式是一个Stack结构,引入有针对的实现可以提高代码的执行效率。
并且有时为了更好的查询迭代情况,可以引入专门结构冗余的存储信息。

迭代代表了从一个状态到下一个状态的映射,并且不同于递归,我们所求的信息是最终状态的一部分,而不需要综合所有的状态来得到结论。

(cons+delay可用于过程见间的协同,display的话就不用储存临时数据了。)

map函数可以看作是基于flod的,或直接改写为对cdr的递归直到null?


* IO

在纯函数语言中,副作用是被严格禁止的。而IO操作的副作用是无法避免的,这就需要了间接的处理方式。
先以对变量的副作用为例,方法是将它作为自变量引入所有和它有关的函数。如果需要改变该变量的值,则直接原样传给它的连续,否则传入修改后的值。
我们可以所有可以被复制的变量看作一个整体,代表副作用发生的范围,在跳转到连续时作为一直存在的参数。
在带类型的Lambda中IO的情况略有不同,它不是连续传递时额外的参数,而是代表了函数返回值的一种类型。
例如一个数学返回的数值和IO.read读取返回的数值,虽然都是数值却是不同的类型。就像代表梨子的数目和代表苹果的数目,只有在水果的意义上才能直接相加,此时已经从子类变为共同的父类。
同样是IO类型的连续,可以依次执行传递变量。通过IO类型和一般类型间的转换,可以将副作用限定在一定的区域内,从而简化了整个程序的逻辑。


* Overload

在C++中,函数的Overload和类的成员非virtual函数都在编译时确定所调用的函数的。它们根据调用的参数变量的类型,将同名的函数保存为不同的函数。
我们将一个结构和针对该结构的方法看作一个整体,在C语言中函数的命名是唯一的,习惯上会给函数的名称添加代表类型的前缀。当使用Overload或使用非virtual成员函数时,就不需要这个前缀了。
此时代表相同意义的操作符,可以使用相同的名称,并作为模板的参数时在编译时检查方法是否存在。
当方法名称和模板中代码调用的方式不一致时,可以引入一个代理类。但是在一致的时候,也可能只是巧合,不代表语义正确。
这里可以的方法是引入代表语义的Concept,需要显示的表明某类型可以的调用方式,并提供相应名称的一组函数。
静态的多态相当于该函数的参数的类型是它所有Overload了的类型的并集,这个并集是在编译时临时产生的。
而动态多态,也就是Override的方式,是以函数作为了结构的一个成员。该成员的存在是类型的一部分,而该成员是不同的函数所带来的不同的行为,不一定要产生一个新的类型。
至于static member则是和namespace一类的东西,可以include。

(CLOS是形式像这里不过是动态的,外加上模板和类型擦除都叫泛型来着。)


* Subclass

在Java中extend表示扩充某个类,可以是添加新的成员而不影响原有的东西。而implement表示提供特定作用的一组函数用于回调。
不过实际上extend包含的功能是多方面的:
1.类的扩充,把父类看作子类的一个成员,并提供方法的委托调用。
2.实现了父类隐含的一个界面,将自己提供给父类调用改变了父类的成员。
3.子类是父类的子类型,子类可以充当父类使用。
4.提供默认的访问控制,虽然说其实访问控制是可以认为改变的。(指protected,其他的可看作词法作用域)
5.给interface的方法提供了默认实现。

需要说明的是:
1.当需要类A以界面B的方式使用时,不是必须要类A派生自界面B,而可以用代理模式实现界面B,也可以实现其他的界面。这里可以看作一个类型转换,或者是Functor,用C++模板和Inline可以在编译期完成。
2.当把类继承看作是代码复用的话,一个类是可以使用其他多个类的对象的。一个类型可以是多个父类的子类,这和它复用其他类的代码是无关的。
3.当把函数也看作对象时,Override实际上是改变一个结构的成员,与类型无关。
4.结论是,单继承或者类的派生是一个混合产物。

再比较C++和Java中的Subclass的话,C++提供了一种灵活性更高的机制,而Java的优点是提供方面思考的模型。

还有一些OO是提供消息传递实现的,Class的作用是工厂,不同于这里的类型关系。


* OCaml & Haskell

现在对ML语言很有好感,其中代表性的实现是OCaml。该语言较明显的特征是可扩充的预处理和带类型的lambda演算。自己写起来比Scheme慢不过产物看起来更加严密一点,这样的缺点是藏丑陋但能用的代码要麻烦一些。

有个很显眼的特性叫Algebraic data type,例如通过Tuple定义List:
type 'a pair=Nil|Cons of 'a*'a pair
let null p=match p with
| Nil -> true
| _ -> false
let car p=match p with
| Cons(x,_) -> Some x
| Nil -> None
let cdr p=match p with
| Cons(_,x) -> Some x
| Nil -> None
List和Record都可以看作由Tuple定义,此外也可以这样来定义数和数的运算。
每个函数只接受一个参数,可以接收Tuple,或者返回可接受参数的函数再接收参数。

Haskell中的type class实现了静态多态和语义上的约束的表示,在Ocaml中是模块的Functor用于算法对于类型的复用,这里算一致的。不过默认不用静态多态是的Ocaml中整数除法和浮点数乘法得用不同的名称,或者显式转换为操作的。
静态类型的派生关系特点是子类可以映射到父类被使用,而不代表子类型可以充当父类存在。如果不涉及赋值或函数调用的话,这个细节不明显。

OCaml有个比较简单保守的core部分,好处是ML语言存在时间较长,使用起来简单一些。Haskell看起来有很多有趣的想法,不过比较零碎,以自己口味不大用得惯。它们的资料在各自官网上都有提供,包括教程、手册以及其他的文字。这里不往下说了。
它们都有源自数学的抽象的概念,这些还没有涉及过。

Miranda和Maxima和Axiom中的表达式都很有数学表达式的样子,视觉上充满了表达力。


* 相关链接

略

2011年3月14日星期一

梦境的天空,黎明的色彩

我办完休年假的手续,正在回办公室的路上,口袋里的传出了响铃声了。我掏出了口袋里的手机,看了看屏幕上的显示,是妈妈打来的。我按下接听键,把手机习惯的放在左耳边,右手从胸前伸向左侧。
“喂,妈妈。有什么事情吗?”
“你哥哥来电话说马上要到车站了,你去接他一下吧。你五点半的时候去,在那里打个电话给他。”
“恩,好的。”

我挂断电话,继续往自己办公室的方向走去。现在的时间刚好四点,准备赶快收拾收拾,毕竟下班时段的交通很不好走。因为不生活在一起的缘故,我和哥哥也是好久未见面了。这次哥哥回来的时候,我正好是年假的时候,让我不禁想起了小时候放假在一起的时光。
想到小时候啊,我的嘴角露出暗暗的微笑。其实是可笑吧,比较其现在和儿时的自己,觉得那时的自己真是幼稚。
人就这么从小长到大,小时候的心愿就仅仅是这么平淡的活下去么。

我走下公司,沿着街道走着。这里离父母家不远,而离我一个的住的地方倒是有些距离。那年哥哥出门工作的时候,我也便随即一个人搬出来住。这些年一直这么一个人生活着,虽然看到过喜欢的女孩子,但是一直没有去追求谁然后想成家的想法。
这大概是哥哥给我的影响吧,他比我大,而我也很羡慕他的生活方式。这么多年里,他就像一个登山队员一样,走到的世界的很多角落。或许对于成家,他更喜欢那种显得自在的样子吧。

旁晚的时候,天空的变成了橘红,街道充满了回忆的色彩。车流于街道穿梭,在耳边唤起一阵阵喧闹。我抬头望着天空,望着高处的防护罩折射的阳光,想去望,却始终无法捕捉太阳的形状。
我对这个防护罩有着特别的敏感,因为我所生活的地方,位于这个天空中的城市的边缘。就在我现在所走的这条路的对面,就是一个被称为海的地方。
海,大陆的边缘,是在遥远的过去人们认为是和世界的尽头相连的地方。后来航海技术发展了,海的意义告别了死亡与悲伤,而变为代表财富希望梦想的地方了。
可以,在这个天空中的城市中,海的意义又该是如何呢?

我看了看路边的标牌,上面说下一班列车还要有10多分钟。于是我又忽然有了冲动,想到路的对面,再去看看这片伴随着我从小长到现在的“海”。
所谓海,是指这个城市边缘和天空相连的地方。隔着一层透明的防护罩,可以看到遥远的地面的色彩和更遥远的天空。这是只有来到城市边缘这个地方才能见到的奇观。
对于我这辈从小在这座城市里长大的人来说,对于那个地面,或许是不会有任何感情了吧。现在还活着的父辈们也是从小在天空之中长大的。所以那种对遥远地面的怀念,可以已经不再存活于人们的记忆里了。

历史书上说,这座城市的产生是出于很久之前的一次战争。当时事情的发生有两个线索,一是空中生物圈的建立,一是核武器的肆意投放。其中的具体的因果缘由我不是很明白,总之是说脚下的土地辐射过于强烈,已经不再适合人类生存了,于是人们便转而建立了许多空中的城市然后生活至今。
这样的一种历史,对我而言仅仅是一种生活的背景吧。地面也好,天空也好,一旦自己适应了,文化也适应了,就没有任何要素让我觉得有所不自然。
世界是这样的,也应该是这样的,以后也会是这样的吧。

我看好时间,赶回站台,公交车也正好驶过来。我上了车,选择了一侧靠窗的位置,实现依然停留在那片被称为海的地方。
在这柔和的光线中,在遥远了琢磨不定的视线里,我沉浸在朦胧的思绪里。想到人类的我所不了解的曾经,我自己的过去,以及我自己的未来。
这里,这个天空中城市的边缘,便是我从小生活过的地方。

车在一阵摇摆中开动,我需要一直做到终点站,也就是车站的位置。这座城市虽然有点大,可是哥哥他是去了一个更遥远的地方。
想到这样的哥哥,我总觉的和记忆中的那个影像有点矛盾。那个小时和自己生活在一起的哥哥,与这个又去了一次地面然后回来的哥哥。
其实小时候,我一直觉得自己更像一个哥哥的样子呢,因为哥哥他啊说是出身的时候出了点问题,体质一直没有我好。想来在人们通常的想法里,和弟弟相比,哥哥因该是一副身强力壮的模样吧。
于是小时候,我就一直有一种自己成为了哥哥的角色,担当起了一种保护“弟弟”的感觉。当然,也仅仅是感觉。因为我和哥哥只相差两年出生,我个子又长得和哥哥差不多高了,在外人看来我们之间就没有太大的差异。

可以在心态上,始终的,我有一种成为了哥哥的感觉,因为这里“弟弟”是一种一直受我照顾而存在的形象。于是,这种心态在成长中,让我觉得这不仅仅是对哥哥一个人的看法。也变成了我对于自身未来的一种看法。
这让我觉得相比于体质偏弱的哥哥,我应该是个去世界各地冒险的形象。
真是小时天真的想法呢,只是觉得去世界冒险这种形象更符合一种“哥哥”的形象,而不是像我现在这样每天在办公室里度过。

小时候的想法,和现在的我的存在,之间是没有多大的关系吧。因为小时候的自己,总无法断定自己的将来会呈现出什么样子。所以那种幼稚的想法就本该淡去了,应该掩埋于记忆之中等到年老的时候再拿出来填补空洞的幻想。
可是现实的差错就在于,哥哥他现在的生活状态很接近我儿时憧憬的那份形象。而我现在的生活方式,一种让人觉得是个“弟弟”的生活方式,我认为让体弱的哥哥来做才更为适合。
不过,这么些年来,当回家来看望我们的时候,他还是留给我一种体弱的形象。不过这只是中看起来的形象吧,从哥哥的工作经历上看他一定有一个很强壮的身体吧。
而变得体弱的倒或许是我,虽然长得一个算是魁梧的体型。但是现在的锻炼已经越来越变得不积极了,在我的这份外表之下,我的内心中自己已经变得一个体弱的形象了。

这或许就是成长带给我的变化吧。我抬头看了看车上的指示灯,离目的地还是有一段很长的距离。
很长的距离,对于步行或者坐车而言,是两种完全不同的感受,前者是主动的,而后者则不完全是。步行的话,心中要始终想着那个目的地才能,到达那个地方,也有可能永远不会到达。而坐车的话,只要一直等待便足够了。只要有那么一时冲动,买票上了车,就让自己陷入漫无目的的等待之中。此时心中的那个目的地变成了一个终究会到达的地方。即使自己心中对那个目的地有了害怕,也通常不会在途中下来车,然后再不知道的地方不知道去哪里。

说到哥哥他,我来简单的描述一下他的职业。不过首先说一个下我自己,正如大家在上面已经看到的,是个普通的办公室职员。每天见着一堆材料,然后每天把他们按照要求处理掉。
而哥哥的职业明显和我有着不同,甚至让我在想象他的职业是披上许多光环,觉得憧憬羡慕甚至嫉妒。觉得他的那份职业才是符合我内心的曾经有过的愿望。
在我的种种幻想中,我很少去想他的这份工作到底是什么,只是羡慕哥哥他可以去世界的各地。
其实从细节上来说,这个世界各地指的就是城市下的那片地面。想到是在海面可以看到的那份地面,我从心里就变得更加羡慕了。

羡慕是归羡慕来说,不过若想简单的也只是工作的不同。我有我的工作,而哥哥有他的工作。如果单单是从去做一项工作的角度说,我想我和哥哥两人的生活状态是有很多类似的地方的。这样的一想,我心里又总觉的不自然,因为我觉得我是在牵强的为毫不相干的事情找相似的,因为世界上的事物之间毕竟还是有所差异的。
于是就是这样差异诱发了我连绵的幻想,于是我想这种是作为一个人的存在而理所应当的想法的。就像我看到的儿时的自己与现在的自己的不同,看到哥哥的生活与我的生活的差异。
就是这种种差异的存在,代表我已经经历的这么些年的时光,代表的我未来尚未度过的那么多年时光里,自己的憧憬。

思路随着车的行驶在颠簸着,我忽然发觉,我对哥哥的工作仅仅是一种憧憬,而无法用言语去描述他具体是什么。我越是去想它具体是什么,越是是只会让思路转而去想到自己的身上。
想到自己曾经是怎样度过的,想到现在的自己是如何。然后想象未来会怎么样,或者说想想就目前的自己未来是注定的必然的又或者是只可能会怎么样。
想到这里,内心总是出现了一种很悲伤的心情呢。于是哥哥的形象便想一张美丽的画纸,来遮住我想法上的伤口。
可是,会不会哥哥的形象只是一个明晃晃的刀口呢。折射出美丽的光芒,却又让我增加了不必要的伤口。

好了,不去想哥哥的职业到底是什么,这不是我关心的问题的所在。我所关心的只是哥哥他能够去地面,能够去这个空中的城市下方,去那一片地面所在的地方。
想到这里,我的心情总是开始变得复杂,然后就有了一种满足的感觉。内心便沉静在这这份满足感里面,不愿意离开。
此时,我在此看看车上的指示等。目的地已经快到了,不过现在还没有已经到。
我从车窗看穿梭而过的风景,一阵一阵的飞驰而过,片刻清晰的画面也无法被视线捕获。

这座天空中的城市体积庞大,如果想从一端到另一端的话,需要6个小时的时间。虽然说这个距离和更广阔地面相比是微不足道,但是种时间带来的阻隔就是树立在人们心中的一个屏障。人们习惯于他所生存的那个角落,并只乐意在以那个角落为中心的区域活动。
时间的距离,空间的距离,虽然都同生活在这一座空中城市之中,却仍然会对其中自己熟悉的地方有特殊的感情。我想我的这种想法是很自然的事情吧。

小的时候,我和哥哥都是父母生活在一起。后来哥哥因为工作的关系,搬到了一个有点远的地方。而那时我也租了一件房子开始了自己一个人的生活。
当我自己从那个我从小生活着的地方搬出来的时候,我还是感到了一种距离感。虽然我可以很方便地再去父母家,也就是那个我从小生活的地方再去看看。就是这简短的距离,还是让我感到了一种距离感。
这种距离感我想仅仅是出于空间上构建起的距离感,也代表了我的小时候与我的现在之间所建立起的一种阻隔。是一种在空间上的,无法用脚步去跨越的距离感。
哥哥他每隔一段时间也会从他工作的地方回来看望我们,除非有时有任务要去地面探查,就不能回来的那么有规律了。

和遥远的地面与这座空中的城市的距离相比,哥哥他平时生活的地方和我与父母所生活的地方相隔的路程实在不算什么。可以是就是这不算什么的路程,还是让我自哥哥他搬离这里的那一天起,深感了其中的距离。
至少,每当哥哥回来的时候,我明白这是回家探望的举动。这代表着即使每年都有若干天可以生活在一起,却这一切已经不同于曾经那种在一起的生活了。
是的,曾经的相见,会我明白他家都在一起。而现在的想见呢,只是让我明白大家是分开的。
他是他,而我是我。他和我住在不同的地方,他和我已成为了不同的人。

好了,车已驶到了他所要到的终点站,也就是我此此行的目的地——我这片区域长途汽车站的位置。
我下了车,往车站里下车的地方走去。四处的张望,捕获哥哥的身影。
我觉得我总是把哥哥的形象给忘记了,想去描述的他的长相的时候,大脑里总是一片空白。不过每次见到他的时候,我就会片刻唤回曾经的那份熟悉感,知道他就是我的哥哥。
他的长相,一个看起来和自己很像的长相,我竟然还总是记不住。想到这里,我的心中总是又开始了那种很复杂的感觉。

我听到了自己的手机响起来,“呦,你来里呀。”
这个声音我听见不仅仅是从我的听筒里传出来,我也能能分辨的出,就在我所在的地点的周围,切切实实的传来了某人那熟悉的说话声。我紧紧的握着手机,不断地向周围在,在其中捕获熟悉的轮廓。
此时,一个身影从我的背后走来,然后拍了拍我的肩膀。
我转头去看,正是哥哥。他说“等了我很久了吧。”
“没,我刚到,正巧。”
我这样说着,觉得其中的不自然。我是来接哥哥的,哥哥他说的那句话应该我说吧,然后我的那句话是由他来回答的。
因为我是在个地方生活的弟弟,而他是从远方回来的哥哥,我自己要让自己显示的更像主人一些才对吧。

接下来的事情就很平常了,像哥哥他之前回来的一样。先去父母家看望一下,然后度过又一个看起来似乎平淡无聊的年假。
这么一天这么有假的几天,没有任何特别的事情想去做。去把时光度过,有时想来便是自己对自己存在于这个世界上最好的证明了。
等到再若干年过去,等到再若干段时光堆积起来,等待感觉到时光已经建立起了无法逾越的阻隔。在这同时,也就能感受到曾经的我现在的我与未来的我的之间的不同,分明感受到自己确实在这个世界上存在过。

有一天,我和哥哥一起又来到的这个城市的边缘,像小时候做过的一样,去看看这片一直被称为“海”的地方。
海所连接的是洋吧,一片宽广的,无边的,深不见底的水域。而这里呢,仅仅是又城市边缘的防护罩所阻隔的一片让人难以走入的更宽广的世界。
“那里的景象一定很凄凉吧,被人破坏,然后被人遗弃。”我透过防护罩,尽力往脚下的地方看去。
那里是遥远的地方,因为距离而无法分辨清楚。因为视野中是呈现的是一片黯淡的色彩,所以在我心中也是留下了悲伤的印象。

“被遗弃的恐怕是我们吧,是我们让自己只能生活在这个天空之中的城市之中的。”
接着,哥哥向我描述了在他的心目中,那个人们曾经生活了数千年的地面现在是怎样的情况。
那里啊,有植物,有动物,有众多的生物,在生存着。其中有,人类历史上古老的遗迹,为繁茂的植物和丰富的物种包围着,如同土地里的宝藏。
哥哥向我描述着他眼中的“事实”。
可是到底什么叫做事实呢,我想这可以从两个方面来说。一种是他所要转述给我的事情的原貌,这是他的描述中我相信的部分。另一种则是他对他对这个事情的原貌的态度,这是只属于他个人的事实,也是我无法认同的一部分。
毕竟在我的心中始终相信一种差异是存在的,一种存在于我和哥哥之间的一种差异。正是这种差异的存在,使得两人对事实的看法有所差异。

我的思绪总在想到这方面的话题时开始陷入复杂,一种似乎是多余的复杂。可是心中即使明知多余,又之中认为这种想法的存在是理所应当的。
此刻的心中,总是会产生莫名的怨气。仿佛是受了不公正的对待,又只能去怪罪自己确实作出了不正确的事情。以自责的无法排解的心态中,暗示自己得到了某种解脱。
此刻我的心中想说的是:
“那是因为你去过那里,所以和我的想法是肯定会不同的。”
我想说,但是一直没有说出来。这样我这句没有被认同是因为自己一直没有把它说出来,而不去想这句话本身就会是错误的。

哥哥他已经看出了我的想法,或者说感受到了某种迹象了吧。说实话我是讨厌自己的想法被人人猜测,可是另一面又在希望,自己是个被人理解被人接受的存在。
于是在这海边,在这城市屏障之内无风的地方。哥哥他对我说,
“已经这么些年了,乘着几天休假,带你去我工作的地方看看吧。想去吗?”
这……哥哥他这样对我说着,按着一个我曾经未去想过的方向……在等待着我的回答。
我知道,我是一个在意自己的选择的人。并有时错误的以为,在没有选项的时候制造出的想象,在能够算真正自己的选择。
那么被动的选择是算选择么。当面对或逃避都成为选择中的一个选项的时候,自己的判断里还存在的足以作出希望的选择。
甚至有时还会在害怕中产生莫名的怀疑,去怀疑所实现的事情,和曾所希望的事情之间,是否存在着差异未被自己发现。

第二天,行程便出发了,去一个陌生的地方,去一个在曾经的心中想去回避的,一直在心中不敢去想的地方。
那天,我和哥哥一起坐上火车。两个人的一起,让我有种回到了小时候的错觉。可是即使能够回到过去,我是否有勇气让未来的自己和现在的自己不同,用一个未知的自己来代替一个有着不满却又现实的存在于此的自己。
我不知道如何回答,想用一个否定的回答来装作自己什么都不知道的样子,并以此认为自己已经作出了回答。
于是一切在不经意中显示出改变的,又在这微小的的改变之中,想寻找到其中被称为永恒的东西。

到底内心想寻找的是怎样的东西呢,称之为永恒,却不知什么才可以被称为永恒。
是那种随着丝毫不会改变的东西吗,一种超越了时间和空间永远不会改变的东西。这样的东西就像历史馆里的遗迹,每天都反射着新的一天的光芒,而一生却永远躺在一个将被遗忘的角落。
那些遗迹只能算是遗忘中的幸存者吧,他们只是历史的一角,是过去遗留下的碎片。而人们的目光只是想从它们的光泽中,揣测已经无法改变了的过去。
人所生存的世界,因为心灵之间的交互,使得其中的真实感能够分明地被感受到。而在交互的进行中,时光也不可避免的流逝着。
于是,当旧有的对永恒一词的观念,已经因为一天天的生存寻找到更加合理的解释。
能被称为永恒的东西呢,不是一尘不变的东西呢。而是在经历了似乎不相干的事情之后,心中总觉的它们公有着一些不同的却又一致的方面。

“最近几年,这些方面的技术有特别的突破了,不像以前只有经过严格训练的人才能离开这个空中的城市了。”哥哥他向我介绍着关于地面之旅的一些情况。
就原本哥哥他现在说给我的介绍来说,是不仅仅像我介绍着其中的情况的,也是在传达着其中的一种体验。
这种体验也可说是多方面的,主要是来自哥哥他这么些年的工作经历。这是只属于他的部分,是时间积累下的部分。并且积累并不是说仅仅是知识或者知道什么那么简单,同时也是一种对自己经历的过的事情的一种情感。
有时我会想,情感是不是算记忆的一部分呢。人之间记忆的传递,是否会在在同时传递其中的幸福与悲伤呢。
出于对外来的情感的阻隔,或者说是就像抗体一样的工作机理,我在筛选着言语,想伪装出一种客观的心态。
因为哥哥的介绍已经不仅仅是一种信息的呈现,而是在营造一种氛围。把他在这样的氛围之下不需怀疑的想法,不加粉饰的直接转递给我。

我来是简述一些在两人的交谈中,一些有必要记录下来的信息吧。
首先是说这些对离开城市,去地面探查的活动的限制,已经放开得相当宽松了。以前的限制一方面是怕技术不代表会带来忽视生命的冒险行为。而另一方面,是以前的人担心这样会造成的社会影响难以预计,所以需要保守一些的行动准则。
但是现在,和当初那个空中城市刚建立起来的时间比,已经是相当遥远的距离了。
当着大多数人们已经习惯于这个广阔而渺小的空中城市中的生活的时候,大多数已经不会再去想着地面之旅的可能性的。就像我即使想过,也只是当做而是的天真幼稚了。
这种放弃不是因为一件是确实做不到,或者认为有做到的困难。而是因为在在心里已经认同了一些事实,知道一些事情再想去做已经是没有必要的了。
一些事情已经告别,当再花更多的经历想是把他们换回的话,只是得到的更过的遗憾。这是不是由于得到的东西有所,而是得到它的已经改变,不会再回来了。

如果说现在的地面之旅的难以程度的话,大概可以用登山活动来做比较,它和人们曾经梦想过的太空之旅显然不是一个难易程度的。
不过即使在另一个艰难的活动来比较,让登山活动的难度显得微不足道,但是这不等同于就可以拿简单这个词来形容。因为就是那登山活动为例的话,其中难度可是可以相差巨大的。
因为山的高度可以相差很大,可以有供人休憩的土坡,可以有耸入云霄的山峰。并且高度所带来的难度并不是距离的加法,它也不是在为人们的行动制造着难度。
这只是它于人之间产生的一种阻隔,一种由高度所造成的阻隔,使得它在人烟罕至中焕发着吸引人的美丽。
除了高度,山脉绵延的面积,也是对人的行动的一种阻碍。毕竟山路的崎岖和危险,只是给路途上的人带来疲惫和紧张。而当山路消失的时候,就会刚到仿佛生命的纽带被剪断。着让路途所发掘的,在空间的距离之外,也蕴含了生与死的距离。
于是这样来说吧,现在地面之旅的难度还是可以很简单的,当然如果你把脚踏上地面的那个世界就认为满足的话。

我知道我不必要在这些方面显得不知足,甚至去怀疑自己自己能够获得的成功,去认为自己所获得的永远只是一些微不足道的东西。
虽然说微不足道是个肯定的事实,但是这不是一个理由要让自己去怀疑所面对一起的事情。毕竟自己的存在,也就由众多的微不足道构成的。
哪怕是漫长的时光中自己所能在记忆上分辨出的痕迹,也不是独立的存在的,而是闪光于自身的遗忘之中的。
所以说我没有必要去放弃可能能的结果,哪怕这个结果代表着曾经的希望,代表着为曾经的希望留下永远的遗憾。
是啊,在机会在我面前的时候,我便会忘记曾经不顾一切的憧憬,变得犹豫起来。

现在还是把话题回到现实上来吧,现在的事实是我可以有机会经历一次简单的地面之旅。
而此刻我的正在,因为总是想怀疑这样的地面之旅和我曾经所愿望的有什么不同。肯定不同的吧,这我是肯定。
通过一次地面之旅我所得到的,只会是这么几天的短短经历,一个会被以后的我所淡忘的经历。而我现在的生活和将来的生活,不会有任何的改变,不会有任何改变的可能的可以察觉。
现在的另一个事实是,这里年假的时间恐怕不够了,只来得及接受一些初步的培训。
这样说法的意思是,那个地面之旅机会并不属于现在,而是属于未来。属于一种未来的可能性,属于一种在等待之后不得不面对的事情。
在此刻,是心愿,还是说是恐惧,自己已经变得难以分辨了。

“我们单位有建招待所,我宿舍面积太小,你就在那里住一晚吧。明天早上我来叫你参加一些基本训练吧。”我麻木地答应了哥哥的话,一人呆在小小的屋子里。在一个和我平时生存的地方相隔很遥远的地方,一个人,望着白色的屋顶,和窗外陌生的风景。
脑海中中盘旋着一片陌生的天空的影响,一直大鸟煽动着他的翅膀划过天际,留下转瞬即逝的痕迹。鸟所看到的是天空下广阔的大地,而我之后看到是一尘不变的天空。
这便是人与鸟之间的差异吧,也是人与人的差异,也是我唯一的哥哥与他唯一的弟弟之间由时光所带来的差异吧。
孤独的时候,总是想着一些毫无意义的感触。于是觉得生活便是一个容器,在收容着感触,在蒸发着感触。在清澈或浑浊的水面之中,不知道什么被保留,不知道什么已经逝去。

第二天,阳光早早的透着窗户温暖着小屋,而我躲在被窝里意识模模糊糊的。随后,传来了敲门声,“准备好了没有”。我等够听出来哥哥他已经来叫我了。
可是现在的我,我自己也不去想是出于怎样的缘由,感到全身深深的乏力。大概是昨天的行程有些疲劳还没有恢复吧,我这样想着,认为休息一下便可。
“我还有点困,”我对门口说到,“想再休息一下。”
“那我先去买点早饭哦吧,你赶快起来哦。”于是我听见哥哥的脚步声暂时往楼外走去了。
此时,这个小屋的氛围在这一阵喧闹之后,变得安静的寂寞下来。自己的犹豫并不能让自己推迟选择的到来,况且在回避的时候就已经在放弃了另一种的可能。
通常所说的选择的到来,就已经到了一种不得不做出选择的时候了,一种只要选择就会比放弃它就能获得好的结果的情况了。
我慵懒的活动了活动身子,无力地起身,然后穿衣洗漱。
透过窗户我抬头看着阳光,一种看上去永恒不变的光芒。不过在这的天空中的城市里,因为上空有保护层的关系,阳光被洒满了整个天空,视线无法分辨出太阳的形状。

这天很快就过去了,随后的一天也是,果然正如漫长的时光里一丝微不足道的痕迹呢。哥哥将继续他的生活,而我将继续我的生活。虽然我们各自的现在都是由各自在一起的过去相连的,可以时光已经使得各自的生活有着不同的方式。
纵然曾经的时候小的时候,也会有各自的憧憬,可是憧憬所连接的未来并不等价于现实中的未来。即使是满足感或者成绩之下的成就感,也会在不同的方式下显示出不同的差异。
总是有一些无法改变的事情,它们是一种注定会发生,却难以判断会发生什么的事情。
这便是成长带来的痕迹,让自己欣慰的成为了自己,又成为了无法填补的缺憾。
在这个年假我末的时候,是我一人登上回程的电车的。哥哥将继续在这里进行他的工作,而我也要去那个我所存在的氛围之中。

于是故事就这样结束了,像什么都没有发生一样。它是漫长的时光与广阔的世界里一丝微不足道的踪影,无法改变过去。
而对于将来呢,也很难看出会有什么影响。而且可能在很多年后,自己会把这一段经历淡忘。然后在可能遇见的微小的痕迹中,或事物或言语中,重新抹开距离的尘埃,拼装着记忆的残片。
不过即使是回忆,并代表其中所发现的东西,会比当时所察觉的要少。一件件不经意中的事情,正是组成时光与这个世界的一部分。它们渺小,它们微不足道,同时它们又无法抹去,而显得必不可少。
“存在”是在描述一种事实,而非痕迹。在以后的某个时候,某一“存在”的痕迹可能毫无保留。但是这并不能否定它的存在。它是时间与空间的间隙里无法改变无法替代的填充物。
正是在自我对存在的感的发掘中,让自己想到自身的现在对于自己的过去,以及自己的将来的意义所在。

在回程的车上,我打量着车窗外路边飞驰的风景。依然是陌生的感觉,虽然是第一次来到城市的这片区域,不过来的时候的应该已经见过这些风景了。
或许对来几次才能熟悉这里吧,使得这里成为自身一种无法改变的记忆。这样想着,我又产生了另一种错觉。
觉得自己所记忆的事物,并不是事物的存在,而是事物的差异。在过去和未来之间所改变的东西,在此地与彼方所察觉的不同。
脑海中想去捕捉的未来总是一种捉摸不定的东西,想去熟悉它却有感到这一过程中的阻力。
这种阻力来自于愿望中希望守护着存在着的自己,也希望有另一个我去敲碎现实的不完美感。

这几天简短的培训,我当初以为我是很难完成的。这倒不是因为自己在考虑这件事情艰难与否,而是自己是否要坚持要做到的问题。
坚持不是固执,不是抛弃着其他的可能独孤一掷,也不是以放弃为自己的犹豫寻找借口。事情的目标并非只是计划与期望中的循序渐进或按部就班那么简单。
连接着过去和将来的纽带,是一个被称为“寻找”的过程,一个连接着虚幻与现实的过程。
第一天的早上,我真想就沉沉的睡过去。像什么也不知道的一样睡过去,想把那天的时光从身体的记忆中凭空消失掉。
因为我认为,这个训练的机会,只是我多余的愿望所带来的负担。这个机会不能改变自己什么,至少是把自己变成心目中所憧憬的那个哥哥的形象。
并且这样的一个机会还让我更加显出的意识到,那个愿望中的自己和现在的自己是个多么遥远的形象。
犹豫种种害怕,害怕去接受这个训练对于自己毫无意义,担心会接受了打击而让自己的心愿变得破灭。
真是可笑事情呢,说愿望之所以美丽,是因为他又变成现实的可能。而自己竟然只想把愿望放在夜空之中,去观察泛着月色的美丽。

即使自己知道看到的是一种不一定真实的美丽,自己也逼着自己去相信着这种美丽存在的可能性。
即使自己只是把它称为美丽,而在脑海中完全不会去形容它具体的状貌,也想把这种对美丽的认同感一直保存下去。
这恐怕就是我面对这个机会的心态吧。面对着一个有所残缺的机遇,却只是在对机遇怀疑甚至不满,还因为着对机遇的不确定性去无视它存在的意义。
就像这个地面之旅的机会,自己得到的只是一个训练的机会,而对于后续的可能却感觉到不确定。不知道自己何时合适才能踏上地面的天地,不知自己何时才能让自己变得像自己一种憧憬的那种形象。
现在的现实是,我可以接受一次训练,并且能之后有机会可以进行一次简单的地面之行。这个事实是因为这个年假所余的时间的有限,而对于我的一个可行的计划。
对于这个计划我是无法改变的,如果固执认为以己之间才能称为选择的话,那么所剩的选择只是——放弃。

那天早上犹豫了一下,还是起来去参加训练了。着不是因为自己对这个训练有什么坚持,而只是以一种不知如何是好的心态参加了。
是啊,以己之力渺小的不足以改变什么,既然当初说要参加一次训练,即使无精打采也只能摇摇晃晃地来到训练场上。
起初的训练如预料中的不顺利,我不知道这种所谓“预料”是否算自己真实的愿望的一部分,这也让我怀疑所谓愿望下掩埋的动机到底是什么。
这种不顺利一方面是因为对这方面的经验的缺乏,我想这算一个事实,也就是说事情本身存在着它的难度。
另一方的不顺利,是因为我认为这种不顺利是应该的,也是理所当然会存在于自己身上的事情。这也可以算一个事实,因为这是自己结合自身情况可以做出的判断。
可是这不顺利在事情的进展中只是越发的变得不顺利,就像让自己中了诅咒,一个无法摆脱的诅咒,一个让自己在意现在能与不能如何而让未来陷入迷惘的诅咒。
毕竟我是我,一个“哥哥”那种形象已经彻底与我无关的人。我这样想,似乎想安慰自己而得以解脱,可是接下来又让脑海中的想法变得更为复杂。

是的,哥哥的形象,那个瘦弱的形象,让我觉得自己强壮的是种可笑。这种外表的强壮,在长时间的缺少锻炼之后,只是让我增加了许多对自身的怀疑。
于是就在这样的心态的,参加了第一天的训练,接下又参加了几天,直到年假的时间就要结束。
起初已经知道地面之旅在这次已经没有可能了,关于所参加的训练也就很平凡的告终。暂时的可以有所期待的结局。
之后那几天的训练课程,是每次都参加就是了,自己也很难说是叫顺利还是不顺利。
自己参加的这个训练本身就是一种体育锻炼之类的课程,并不是作为职业的一种硬性指标。
课程的教授者只是在讲述的事情的要求,这难说严格或困难与否,而只是对做这么一件是有如何的要且。
从单纯的去做一件事情的角度说,自己还是很乐意去做的。因为相信着付出对于自己的意义所在,相信改变或者心愿什么的总有一个来临的契机。
事情就是这样,想着一个飘渺的结果而一事无成,或者在所做的事情中寻找着让自己满足的事情。

这两方面的想法,自己是都觉得的可笑呢。很都事情在事后放在一起,然后议论着过去应该如何。
当这种议论是为了未来的事情时,这样的总结还有它可发挥的价值。而如果仅仅是把过去作为一种谈资,并让抱怨或者自恋变得不休止时,只会在空气中留下热气一般的笑声。
这次参加的训练课程自己后来还是觉得挺投入的,不是因为已经自己将会变得怎样,而只是在心态上去认同它。
或许就是一种本能吧,觉得看着一件事情又虚无变得清晰是满足的,觉得有些事情一旦开始接受也是可以接受的事情。
这也算是欣慰吧,在我坐在回程的车上时回想起这几天的事情所获的感触。一段短暂的几天里的经历就这样结束。
我将回到我往日的生活中,并将自己的生活延续下去。那个地面之旅的愿望,依然如同的夜空星光一样美丽。
想说让这几天的经历就改变了自己是很难说的事情,并且也很难说什么是需要改变的。
希望也好曾经也罢,都是在延续之中传递的一部分,不必要陷入纠结之中丢弃了所获的的东西。

回程的那天,哥哥他送我到车站。我站在车里,他站在月台上。
“回去好好准备准备要上班了哦,坐车还是会疲劳的。”
“恩,下次见。”此刻不是告别,而是保留了要期待的事情。
“看我个这样四处行走的工作还要单身一阵了。你条件比我成熟,看到喜欢的妹子要乘早追哦。”
“……”我回应以一笑。等着车随后的行驶以距离拉开了我与哥哥之间的距离。
正是距离让我们感受到我们之间的差异,在各自盼望着憧憬的时候,也在回应着对方的愿望。

路途略有漫长,等到了目的地的时候天已经黑了,世界又喧嚣回归安静。
我抬头去看天空,在这个天空中的城市上方的保护障上,只能看到模糊的光影。
我能想象,曾经地面上生活的人所看到的布满繁星的天空,是份万分美丽的景象。
这种美丽不是因为所看到的景象究竟是如何,而是因为那份打量天空的心态。
在安静的黑夜中,寂寞的时候,可以去看一看星光与天空。
等待着天空开始泛白,等待着天色黎明,等待着它所预示的新的一天的开始。

2011年3月6日星期日

发贴,存活证明

如题,很久未更新了。