Legend Since 1984
Cruising between Fantasy and Reality...

Wednesday, June 28, 2006

Project Named as Pallyc

It is high time for this compiler project to be properly named!

Animal:

  • raccoon
  • falcon
  • hawk

Misc:

  • shatter
  • rex
  • emp
  • razgriz
  • palloyc
  • pallyc

Final Decision: pallyc

Specially meanings: “p” stands for PASCAL’s nested function definition, while “c” for C’s grammar base frame; the word “ally” represents the combination of those features. And luckily the last 3 letters “lyc” happen to be the abbreviation of my name.

Labels:

Monday, June 19, 2006

Phase 1 Accomplished!

偶的小小编译器计划的第一阶段终于在2006年6月19日22点37分完成了!!

从上周日的凌晨这个小小的计划令我不能入眠开始,经过了整整一周都一点的时间,我亲自见证了这个编译器的设计、反思、孕育、诞生的过程。

之前这10篇blog纪录了完整地记录了这项计划的演化,有文法的初步设计、反复修改,有符号表结构的构建、功能增加,有源代码的录入、精简……

使用Easy Code Count 1.0.0.3统计的结果是共计代码2,242行。可以说,每一行代码都是精心雕作的结果,每一个类都是心血浇注的结晶。

虽然现在功能还不太齐全,相对于完整的C语言有太多不支持的要素,但是作为一个大三学生的业余爱好,基本的语言特性已经得到很好的实现——多函数定义、函数调用、多维数组支持、变量检查、变量类型检测;并且还有不断扩充的空间。

阶段1告一段落之后,下一步是计划第2阶段的任务——由于现在输出的都是原始的四元式,而且有很多冗余部分,所以下一步工作的主要任务就是对四元式进行优化;并且参考一些汇编语言的资料,争取做到汇编代码级的输出。

在符号表、四元式输出的设计上我参考了很多GNU的gcc、微软的cl编译器输出的中间汇编代码的格式。在第二阶段中,我的打算是把我做的编译器插入到gcc的编译过程中——先用gcc的预处理器处理源文件中的宏定义,然后是我的编译器将代码翻译成gcc的汇编格式,再调用gcc的汇编器和连接器。这样,可以充分利用现有的资源,同时达到了创新的目的——为C语言加入函数嵌套定义的功能——这也是我最引以为豪的地方。

Labels:

Sunday, June 18, 2006

Done with Function Call

函数调用部分也基本上搞定了。这次还修改了前面的一些翻译方案,加入了赋值是类型判断的功能。内置有5种类型:VOID、INT、LONG、FUNC、ARRAY。类型完全相同当然视为匹配。其次,INT->LONG的自动转化也视为可以接受的。但是LONG->INT将输出一条Waring信息。其余的情况被视为类型匹配错误,直接终止编译过程。类型判断在赋值语句和函数调用语句是执行,检查左值和右值的类型是否匹配。不匹配的情况主要发生在数组和基类型间的错误赋值、返回VOID的函数赋值给其他类型。

函数调用的相关产生式为:

  • Factor->Function_caller
  • Function_caller->IDENTIFIER ( )
  • Function_caller->IDENTIFIER ( Argument_list )
  • Argument_list->Argument_list , Expression
  • Argument_list->Expression

传入参数的四元式为”param Var_Name”,函数调用的为”call Func_Name Param_Num Ret_Var”(其中Param_Num为参数个数,Ret_Var为接受函数返回值的临时变量)。

明天的任务差改写Boolean语句、条件分支语句、循环语句的部分了,这些都很容易。唯一值得关注的是函数返回的值的地址的确定——因为在分析return语句是还不知道该函数所有变量的空间大小(我的文法支持变量到处定义),返回值的地址通常是放在所有变量的偏移量之后——这里用回填的方法进行处理。

Labels:

When A Class Member Encounters 'static'

在C++中,当一个类的成员遇到static限定符会是什么样子呢?今天在更新类CSymbolTable的过程中遭遇到这样的困境。

由于之前遇到过定义static的成员函数的问题,对于static成员函数来说并没有什么需要特别注意的,仅仅是该函数不能引用类的非static成员、没有this指针、在函数还引用只能加类作用符而非对象名。所以这次对于static的成员变量很有信心的在头文件中定义了。既然都是static的,那么在static的函数中引用static变量应该没有问题吧。写好函数,开始编译——奇怪的错误发生了:在编译的最后阶段——Linking的过程中,Visual Studio报告无法定位刚才定义的那个static成员变量的引用。按经验来说,这种错误发生的情况用extern声明的变量不能从其他文件.c/cpp中找到其定义;但是这个地方明明在头文件中已经清清楚楚地定义了呀。

不解之下只能求助MSDN。在static member的索引中找到一个例子,倒是可以成功编译的。二者不同的地方是MSDN的例子中居然在class定义的外部又对其static成员变量作了一次定义。还是不解,抱着试一试的心情翻开了电子版的C++ Primer,方便的索引功能帮我找到了问题的根源——(引自C++ Primer)static data members must be defined (exactly once) outside the class body. Unlike ordinary data members, static members are not initialized through the class constructor(s) and instead should be initialized when they are defined.

原来,static成员变量不是在类的构造函数中初始化的(这点很好理解,因为构造函数是在构造一个对象时调用的,static成员隶属于类而非任何该类的实例(对象);而我曾一度希望找到能定义static构造函数的方法,结果证实这在C++中是不存在的)。C++对于类声明中的static变量仅仅也就是作声明处理(其实非static的也一样,只不过后者在构造函数中分配内存空间和初始化的)。既然static是对整个类而言,那么只存在一个static变量的实例;而头文件中的声明是不分配内存空间的,那么需要在类声明的外部显式地进行定义/初始化就不难理解了。最后,为了保证该static成员只被定义一次,其定义语句应该放在类的.cpp文件中而不是.h文件中——(引自C++ Primer)The best way to ensure that the object is defined exactly once is to put the definition of static data members in the same file that contains the definitions of the class noninline member functions.

总结一下就是C++对static的实现和C#、Java有很大的区别。不能不说以前使用C#的static的经验误导了我。由于C++对类的声明和定义是分离的,分别放在.h和.cpp文件中,而.h中定义的static成员变量并不会在构造函数中初始化,所以,显式地在外部定义是必要的。

Labels:

Done with Array

傍晚搞定了变量赋值了,对于与数组有关的部分也于今晚12点左右完成了。相应的,文法产生式也进行了修改。为数组引用添加了一个新的非终结符——Array_reference,与之有关的产生式为:

  • Array_reference->IDENTIFIER [ Expression ]
  • Array_reference->Array_reference [ Expression ]

同时赋值也有重新考虑。必须区分左值和右值。对于左值,只能是变量名和数组引用;而右值,除了这两种,还可以有常量、函数调用、四则算术表达式等——这些都包含在非终结符Expression的运算符层次结构中。

  • Statement->Left_value = Expression ;

上式为赋值表达式的文法,Expression的层次为:

  • Expression->Expression + Term
  • Expression->Expression - Term
  • Expression->Term
  • Term->Term * Factor
  • Term->Term / Factor
  • Term->Factor
  • Factor->( Expression )
  • Factor->CONSTANT
  • Factor->Left_value
  • Factor->Function_caller
  • Left_value->IDENTIFIER
  • Left_value->Array_reference

为了保证代码简洁并提高其重用性,将Left_value放到Factor之下是恰当而合理的。

对于归约IDENTIFIER的地方都要去查看符号表,以确定该变量名是否被定义。而为了支持引用函数外部的,需要为每个符号表添加一个指向其父表parentTable指针。全局符号表的parentTable为NULL。查找变量的时候先搜索当前的符号表,失败的话在逐层向外进行搜索,如果都没找到变量定义才作失败处理。而这些符号表直接的父子关联关系(即parentTable的赋值)是在建立符号表的时候就予以正确的完成了。

其实,有了parentTable这个指向父表的指针之后,原先的那个为支持函数递归定义而添加存放符号表栈可以只用一个符号表引用/指针来代替了。这个引用的作用时记录当前定义、引用变量时填、查符号表的起点。开始的时候该引用指向全局符号表,这样外部的全局变量就记录在全局符号表中;每注册一个函数(Register->epsilon)时该引用指向新建的符号表,使函数体内的局部变量可以记录在局部符号表中;每当一个函数的定义结束时,用当前符号表的parentTable指针取代当前符号表引用即可。

Labels:

Friday, June 16, 2006

Done with Symbol Table

今天工作的成果是——总算把建立符号表有关的产生式的语义动作全部写完并调试成功。这些产生式是:
  • Function_definition->Type_specifier
  • Function_declarator REGISTER Compound_statement
  • REGISTER->epsilon
  • Function_declarator->IDENTIFIER ( )
  • Function_declarator->IDENTIFIER ( Parameter_list )
  • Parameter_list->Parameter_list , Parameter_definition
  • Parameter_list->Parameter_definition
  • Parameter_definition->Type_specifier Variable_declarator
  • Variable_definition->Type_specifier ID_list ;
  • ID_list->ID_list , Variable_declarator
  • ID_list->Variable_declarator
  • Variable_declarator->IDENTIFIER
  • Variable_declarator->Array_declarator
  • Array_declarator->IDENTIFIER [ CONSTANT ]
  • Array_declarator->Array_declarator [ CONSTANT ]

包括了数组定义(支持多维)、函数头定义(支持数组作为参数)、变量定义(支持普通变量和数组变量的混合定义),符号表、数组描述结构、函数描述结构的填写全部正确。接下来的任务是处理函数内部的语句,这是编译实验课上做的工作,相信应该是驾轻就熟了。有点难度的是数组的引用和函数调用,还要判断是否匹配的问题,不匹配的话将弹出编译错误。

呼~~好累,看会世界杯吧~~~阿根廷6:0搞定了塞黑呢!

Labels:

Symbol Table Design

相对于实验课任务来说,扩展语言的功能除了要规划好合适的文法(太简单了功能不全,太齐全了语义的翻译方案没法写),另一个关键是符号表的重新设计。

为了加上多函数定义和函数调用的功能,就必须考虑到变量的作用域问题。一个全局变量和一个局部变量可以同名,显然他们代表不同内存单元。这样,原先那种没有层次关系的符号表结构就不再适合了。为了区别局部变量和全局变量,应该为全局空间和每个函数体建立单独的符号表。

其次,在词法分析阶段填写符号表的方法过时了。词法分析只能识别出一个个Token串。如果遇到两个同名的变量,词法分析对于判断它们是同一个变量的引用还是两个作用域中定义变量显得无能为力。因此,查填符号表的工作应该放在语法制导的语义分析阶段。此时,词法分析的作用仅仅是识别出变量标示符的词类编码,并直接记录它的名字(字符串),而不是记录其在符号表中的入口地址了。而符号表也完全采用按字符串索引的方式建立。多亏了STL中的map类,我可以直接把string作为索引映射到任何类型上。map,这里建立了一个CSymbolInfo的类记录变量的其他属性。

一张图能抵过千言万语。对于符号表的层次结构,下面的例子给出了清晰的阐述。

//file: Test_Example.c:
int a;
long b;
int c;
int f(int a, int b)
{
int c;
long arr[10];
}
int r[5][5];

对于上面的变量定义将产生下图所示的符号表层次图:

Labels:

Tuesday, June 13, 2006

Understanding the C Grammar

作完C的语法分析,一边感叹C语言文法的复杂,一边再次试图理解其文法构成。查阅MSDN的时候发现完整的C++语言有18级运算符优先级,而我自己写的文法只有4级。C语言怎么说也有15级优先级,那么这样的优先级在文法中是怎么实现的呢? 总算从

  • primary_expression->IDENTIFIER
  • primary_expression->CONSTANT
  • primary_expression->STRING_LITERAL
  • primary_expression->( expression )
看出了一点蛛丝马迹。类比我自定义的文法,primary_expression应该是处于最底层的与表达式相关非终结符了。再自底向上顺藤摸瓜,发现了
  • expression(,)-->
  • assignment_expression(= += -= *= /= etc)-->
  • conditional_expression(?:)-->
  • logical_or_expression(||)-->
  • logical_and_expression(&&)-->
  • inclusive_or_expression()->
  • exclusive_or_expression(^)-->
  • and_expression(&)-->
  • equality_expression(== !=)-->
  • relational_expression(< > <= >=)-->
  • shift_expression(<< >>)-->
  • additive_expression(+ -)-->
  • multiplicative_expression(* / %)-->
  • cast_expression( (type name) )-->
  • unary_expression(++ -- & * + - ~ ! sizeof)-->
  • postfix_expression(++ -- . -> () (argument_list) [])-->
  • primary_expression( () )

的层次体系。从各个非终结符的名字就可以很容易看出他们代表的运算符(如后面的括号所示)。从上到下运算符的优先级越来越高。一共是17个优先级。比C++少了'::'和'throw'的运算符所在的优先级。

理解了算符表达式(expression)的结构,C文法已经完成了1/3的数量。有了这个突破口,在理解其他的文法就只是时间问题了。

ps: 原文法中产生式unary_expression->unary_operator cast_expression显然是不合理的,因为cast_expression是unary_expression的上一级,这样将导致求Follow集的循环递归调用。改为unary_expression->unary_operator unary_expression解决了问题而不会改变语言的定义。

Labels:

Parse the Grammar of Non-stripped C

完整的C文法的确复杂。前段时间试图理解却发现始终无法找到切入点。昨晚想试一下自己写的SLR语法分析器的功能,看它能不能自动构造出C语言的SLR分析表。于是用下载的C文法替换掉自定义的文法放到文法规则输入文件中。(改格式就花了不少工夫,还好Visual Studio强大的正则表达式替换功能为我节省了大量的体力)

怀着忐忑的心情第一次运行,居然堆栈Overflow?用Debug模式一步步调试发现在自动构造分析表的过程中求解First/Follow集的时候出现了循环的递归的问题。第一个错误出现在求Follow(cast_expression)的时候。cast_expression->unary_expression->cast_expression。检查文法产生式,发现了如下这两条:

  • unary_expression->unary_operator cast_expression
  • cast_expression->unary_expression
这样循环才怪呢!首先注释掉第一条可以暂时屏蔽这个问题。后来在statement的地方也出现了类似的循环递归,简单修改文法得以回避掉了。

构建分析表的时候还出现了两个分析表冲突,一个是对于’:’的,注释掉statement->IDENTIFIER :可以解决,原因暂时不明。其次是

  • statement->if ( expression ) statement else statement
  • statement->if ( expression ) statement
的地方,这个冲突是来自if语句自身的二义性。由于SLR分析法只适用于非二义文法,教科书上的解决办法是手工修改分析表,依据else的最近匹配原则,在出现冲突的地方令其移入而不是规约。但由于现在是自动构建分析表,只能从产生式中去掉statement->if ( expression ) statement这一条。这样,语言中所有if语句都必须匹配else,即使没有else也要加上else ;(多少看上去有点怪异)。

所有错误和冲突都消除之后,终于可以对C的源程序进行语法分析了。不过受限于简陋的词法分析器,字符串和浮点数和一些双符号的运算符(如+=,*=)都不能支持。我的CPU是Pentium M 1.6GHz,内存1GB。构建分析表的时候CPU满负荷(占用率100%)工作了近11秒钟,可见的确实一个复杂的文法呀!

结果是令人振奋的。除了不支持浮点数、字符串还有须强制匹配If-Else之外,所有ANSI C的特性都能够被正确的解析——不管是for,还是switch;不管是struct/union/enum定义,还是指针类型的使用,通通给我显示出正确的文法结构来!

抛开由于二义性导致的冲突和来自词法分析器的局限来看,我写的SLR(1)分析器还是相当优秀的。有自动构建分析表的强大灵活性,这个分析器可以分析任何文法,即使是那么复杂的C语言,只要这样的文法适用于做SLR分析。

Labels:

A Customized and Simplified C-like Grammar

研究了一下C的文法后发现,要全部实现C的功能简直是难如登天。但是,做一个类C的简易的编译器还是相当可行的。

参考了C语言的完整文法后,花了一下午时候拟出了一个化简后的文法方案。简单的从文法上来看,可以做到和不能做到的:

  • 支持多个函数平行定义,不支持函数的递归定义;
  • 支持函数调用;
  • 支持数组定义和使用;
  • 支持多维数组,采用C的方式而非Pascal的方式;
  • 不支持指针类型;
  • 不支持结构体、公用体和枚举类型;
  • 暂不支持位运算;
  • 暂不支持逻辑运算、关系运算;
  • 暂不支持+=等运算符;(添加将会非常简单)
  • 暂不支持打括号内定义变量的作用域局限——即同一函数内的变量作用域相同;
  • 不支持字符串;(一大缺陷)
  • 两字节运算符支持有限;(受限于简陋的词法分析器)
  • 暂不支持浮点数;(受限于简陋的词法分析器)

尽管有这么多不支持的东西,但是我认为现在最重要的工作是处理函数调用的翻译方案,解决好函数调用,后面的工作才有基础展开。而解决函数调用最主要的工作是重新设计符号表,因为要解决变量作用域的问题就应该为不同的函数建立不同的符号表,同时,如果组织并存放这些符号表也将是一个值得考虑的问题。

完整的文法规则如下:

Start->Program
Program->Program External_definition
Program->External_definition

External_definition->Function_definition
External_definition->Variable_definition

Function_definition->Type_specifier Function_declarator { Statement_list }

Type_specifier->void
Type_specifier->int
Type_specifier->float

Function_declarator->IDENTIFIER ( )
Function_declarator->IDENTIFIER ( Parameter_list )

Parameter_list->Parameter_list , Parameter_definition
Parameter_list->Parameter_definition

Parameter_definition->Type_specifier IDENTIFIER

Variable_definition->Type_specifier ID_list ;

ID_list->ID_list , Variable_declarator
ID_list->Variable_declarator

Variable_declarator->IDENTIFIER
Variable_declarator->Variable_declarator [ CONSTANT ]

Statement_list->Statement_list Mark Statement
Statement_list->Statement

Statement->{ Statement_list }
Statement->Variable_definition
Statement->return Expression ;
Statement->continue ;
Statement->break ;
Statement->;
Statement->Left_value = Expression ;
Statement->while ( Mark Boolean ) Statement
Statement->If_Else_Condition Statement
If_Condition->if ( Boolean )
If_Else_Condition->If_Condition Statement else

Mark->epsilon

Left_value->IDENTIFIER
Left_value->Left_value [ Expression ]

Boolean->Boolean Mark H
Boolean->H

H->H & Mark G
H->G

G->! ( Boolean )
G->( Boolean )
G->true
G->false
G->Expression Rop Expression

Rop-><><= Rop->>
Rop->>=
Rop->==
Rop-><>

Expression->Expression + Term
Expression->Expression - Term
Expression->Term

Term->Term * Factor
Term->Term / Factor
Term->Factor

Factor->( Expression )
Factor->CONSTANT
Factor->Left_value
Factor->Function_caller

Function_caller->IDENTIFIER ( )
Function_caller->IDENTIFIER ( Argument_list )

Argument_list->Argument_list , Expression
Argument_list->Expression

Labels:

Monday, June 12, 2006

Complete C Grammar

在Google上搜到的。虽然对C这种完整的语言的文法的复杂程度有所思想准备,但是刚看到的时候还是吓了一跳——what a complexity and a detailed specification!

相比起来,自己做编译实验用的文法就太太太太不值得一提了。当然,别人C的编译器都是N多大牛合作才整出来的,自己一个小猫没有能力更没有时间去实现全部C的功能。以下的文法就当作参考,自己再在原来的基础上扩展出一个化简后类C文法吧。

statement->
labeled_statement
compound_statement
expression_statement
selection_statement
iteration_statement
jump_statement

labeled_statement->
IDENTIFIER ':' statement
CASE constant_expression ':' statement
DEFAULT ':' statement

compound_statement->
'{' '}'
'{' statement_list '}'
'{' declaration_list '}'
'{' declaration_list statement_list '}'

declaration_list->
declaration
declaration_list declaration

statement_list->
statement
statement_list statement

expression_statement->
';'
expression ';'

selection_statement->
IF '(' expression ')' statement
IF '(' expression ')' statement ELSE statement
SWITCH '(' expression ')' statement

iteration_statement->
WHILE '(' expression ')' statement
DO statement WHILE '(' expression ')' ';'
FOR '(' expression_statement expression_statement ')' statement
FOR '(' expression_statement expression_statement expression ')' statement

jump_statement->
GOTO IDENTIFIER ';'
CONTINUE ';'
BREAK ';'
RETURN ';'
RETURN expression ';'

translation_unit->
external_declaration
translation_unit external_declaration

external_declaration->
function_definition
declaration

function_definition->
declaration_specifiers declarator declaration_list compound_statement
declaration_specifiers declarator compound_statement
declarator declaration_list compound_statement
declarator compound_statement

……
太多了,贴在这里浪费空间。参看以下两个页面吧:
C的BNF范式
C的YACC文法

Labels:

Saturday, June 10, 2006

Why not make a compiler yourself

时间是凌晨3点05分,还有30个小时就是编译原理考试了。复习还处在攻坚阶段,而现在的我,却为自己的一个想法激动得难以入眠。

编译原理课程的5个实验我都是一步步兢兢业业走过来的,通过这五个实验,一套简单的编译器前端已经初具雏形。这个前端接受三个输入文件:文法产生式集合、关键字集合、待翻译原程序,输出了翻译后的四元式。

我自己最为满意的部分就是语法分析表的自动构建。不管是采用哪种语法分析策略(自顶向下、自底向上),我都费了很大的功夫在分析表的自动构建上面。现在看来,这笔投入是相当划算的。有了分析表的自动构建,我可以随意修改输入语言的文法构成而不必重新手工计算修改后语言的分析表(这是一个相当大计算量的工作,如果人脑完成的话)。在作语义分析的时候,为了验证我设计的翻译方案和属性的存放方法,通常是采用一个非常简单的文法来进行验证。有了前面工作的基础,在更换文法的时候,我要做的仅仅是替换那三个编译前端接受的输入文件——文法产生式集合、关键字集合、待翻译原程序;在配以相应的语义动作处理函数重新编译,我的前段就可以正常工作了。(实际上,如果不做语义分析的话,甚至连程序都不用重新编译就可以直接运行)。尤其是在采用自顶向下的语义分析时,属性的存放方式经历了较大的变化,每次变化都先采用一个简单的“计算器”文法进行了验证。还有后来对原文法进行了修改,使之改成了类C语言的定义规则,还加入了控制循环的break、continue语句的实现。这些改变都没有对原来程序的语法分析部分作任何的修改。

以下两图是两种分析策略分别的结构图。尽管他们采用的方法有所差异,但是最终的输出都是一致的——未经过优化的标准四元式及程序内部符号表。



图表 1:自底向上分析器的整体结构


图表 2:自顶向下分析器的整体结构

这是课程设计的终点,也许绝大多数人到了这里也就停下了。但是我想做得更多!前面的工作已经有了很好的开端——功能上我已经实现了分析表的自动构造,实现上我也没松懈:首先是采用面向对象从数据结构入手模块化的设计思想,每个类完成特定的功能、封装内部数据成员、依靠函数接口对外提供服务;各模块内聚性高,添加功能容易;编程语言采用最高效的C++而不是Java或者C#等使用方便但效率低下的基于虚拟机的语言;采用STL作为容器的实现,既高效又节省了大量精力;后期在实现上也作了一些优化,能用简单数据类型的地方尽量不用复杂的容器类,利用C++的异常来处理产生的编译错误,代码中使用宏来力求简洁,多次重构力求代码扼要而优雅;……

这些基础都是对后续工作的铺垫。我的梦想是完成一个完整的编译器,至少能输出通用格式的汇编代码。虽说从四元式到汇编只有一步之遥,但要想做到功能的完整还有很多难关需要攻克。最艰巨的要数支持函数调用。仅仅这一个名词下就包含的很多东西:运行时环境、活动记录、参数传递、局部变量与全局变量的访问、函数返回等。其他的还有对数组的支持、汇编代码的格式。初拟的一份TO DO List如下:

表格 1:Compiler DIY 's TO DO List

功能描述相关概念/说明
函数调用运行时环境、活动记录、参数传递、局部变量与全局变量的访问、函数返回
数组是否使用指针的形式、相应的翻译方案
更多数据类型指针、结构体、数组、浮点数
浮点数词法分析器
符号表的扩充内情向量、相对地址
非终结符的扩充把原先采用单个大小写字符作为非终结符的方式改为以一个字符串来标示
输出汇编格式Windows下:cl /Fa(VC++)、Linux下gcc –S(gcc)
标准IO可能需要用汇编编写、或者连接现成的库函数
错误处理忽略错误继续编译

如果能全部完成以上功能也就是一个较为完整的编译器成品了,但是我很明白这不是一个短期内可以完成的任务。明白前方的障碍才是前进的基石。也许,我可以申请把它作为毕业设计来完成吧。

一直以来,编译器就是令我感到神秘而向往的东西。如今,一个自己独立实现的编译器已经不再那么遥不可及了。什么才是计算机科学的基石?不管现在各种流行技术怎么发展,试问哪门语言的运行离得开后台编译器的支持?Java,PHP,C#,Perl,JSP?如果说操作系统是连接计算机硬件和软件的桥梁,那么编译器就是沟通操作系统与高级语言的纽带;如果把操作系统比作谁与争锋的倚天剑,那么编译器绝对是号令江湖的屠龙刀!

现在回到床上,在梦中给我未来的编译器想个诱人的名字才是当前最重要的~~~Zzzzz

Labels: ,

Sunday, June 04, 2006

White Space Contains '\r'

下午帮solit调一个编译课实验的程序。让他把输入文件传给我,我用自己的程序跑一下把生成的正确结果返回给他,帮他诊断程序中的错误。

输入的是文本文件,用QQ传的。从QQ的文本框中^C&^V到Visual Studio里面,Start Debug,怪了,词法分析都过不去。跟我自己以前的测试程序比较,都是符合所定义的文法的呀。既然我的词法分析显示"undefined keyword: "错误,想必不可能是语法分析阶段的问题,问题出在从输入文件中取得一个token后去关键字表查询的时候,如果没有查到,表示这个关键字没有被定义,程序异常退出。但是我们两人的关键字表都是一模一样的啊。

再次单步调试,发现出错的token字是char(13),怪了,ASCII码为13的字符是什么?再分析完前两个token之后本应该发现“{”这个token啊,中间的'\n'(回车,char(10))会被自动跳过的。那么这个char(13)是哪里来的?突然想起原来solit提到过的Unix和Windows处理回车的差异——在Unix/Linux中回车使用\r\n来表示的,而Windows仅为\n。说定这个char(13)正是\r啊。引入\r的源头应该是从QQ粘贴过来所致——很有可能在QQ文本框的内部表示中把回车都设置为\r\n了,而我的程序中判断空白符(white space)的时候只对' '、'\t'、'\n'进行了处理,程序把\r也当成了一个token去查关键表,不错才怪了!

简单在判断空白符的地方增加对\r的判断,问题就迎刃而解了。

Labels:

Thursday, June 01, 2006

推荐一本C++的入门读物

前几天去图书馆还过期了的书,顺便去科技借阅室逛了一圈。偶然从书堆中发现了一本挺旧的O'Reilly的书。通常来说,O'Reilly的计算机书质量还是相当有保障的。拿起来,竟然是一本讲C++的。当时时间紧,随便翻了翻,而且发现此书一共才200多页,正好还有借书的余额,于是就收下了。

昨晚抽空看了此书的第一章,介绍了面向对象编程(OOP)的基础理念。虽说现在讲OOP原理的书可谓遍地开花,但是这本C++: The Core Library的出彩之处在于它是假设读者有C语言的基础,完全使用纯C来模拟了OOP的四个基本特性——抽象(Abstraction)、封装(Encapsulation)、层次(Hierarchy)、多态(Polymorphism)。即使一个对OOP没有任何了解的人,只有具备一点C语言的基础,理解四个特性就很容易了。进而,读者可能会发现用传统的面向过程的C语言来“模拟”这四个特性多少有点拙劣。于是,自然而然的引入了“类”这个对C语言来说全新的概念。回避了许多同类书籍中已开始让毫无OOP经验的C语言读者摸不着头脑的不足。

其次,既然名为The Core Library,自然就是专注于语言的核心。书的指导思想也非常明确——只涉及最重要的语言特性。诚然,面向对象的每一个概念都很简单而易懂,因为面向对象的本质就是出事物的本质出发去抽象世界。但是更大的挑战不是这些孤立的概念,而是当如此繁多的特性杂合在一起的时候,怎样去判断和处理这些特性之间的相互作用——这些副作用很大程度上是造成初学者困惑的直接原因。作为一本入门书籍,选择语言中最重要的特性进行介绍无疑是明智之举。当然,针对高级读者,书中也提供了对那些高级语言特性进行介绍的旁注;当然,忽略这些旁注并不妨碍对其他部分的理解。

如果你是一个熟悉C的人,如果你还希望了解作为OOP语言的C++这个C的超集,相信这门书能给你提供概念上的指导。因为它并没有提供完整的示例,所以要熟悉C++还得参考其他的书籍。但是有了书中这些核心概念的基础,阅读其他的书籍应该会轻松许多。并且,这本不到250页的书对希望温习C++的老手来说也提供了一个快速的途径。

Labels: ,