C#专题之C#的语法和句法分析作者:思多雅[天行健] 2008-10-05发布正如前面的专题所说,编译从原理上来说包括了词法分析、语法分析、存储的组织与分配、中间语言、语法制导翻译、代码生成与优化这几大部分,下面我们来看看C#的语法分析和句法分析: 一、C#的语句分析: 语法分析阶段把输入字符流转换为标记流。 1.1 输入 input: input-elementsopt input-elements: input-element input-elements input-element input-element: comment white-space token 1.2 输入字符 input-character: any Unicode character 1.3 行结束符 line-terminator: The carriage return character (U+000D) The line feed character (U+000A) The carriage return character followed by a line feed character The line separator character (U+2028) The paragraph separator character (U+2029) 1.4 注释 C#支持两种形式的注释:规则注释和单行注释。 规则注释以/*开始,并且以*/结束。规则注释可以占用一行的一部分,单行或多行。 比如: /* Hello, world program This program writes “Hello, world” to the console */ class Hello { static void Main() { Console.WriteLine("Hello, world"); } } 包括多个规则注释。 单行注释开始与字符//并且延伸到行的结束。 // Hello, world program // This program writes “Hello, world” to the console // class Hello // any name will do for this class { static void Main() { // this method must be named "Main" Console.WriteLine("Hello, world"); } } 介绍了多个单行注释。 comment: regular-comment one-line-comment regular-comment: / * rest-of-r egular-comment rest-of-r egular-comment: * rest-of-r egular-comment-star not-star rest-of-r egular-comment rest-of-r egular-comment-star: / * rest-of-r egular-comment-star not-star-or-slash rest-of-r egular-comment not-star: Any input-character except * not-star-or-slash: Any input-character except * and / one-line-comment: / / one-line-comment-text line-terminator one-line-comment-text: input-character one-line-comment-text input-character 比如: // This is a comment int i; /* This is a multiline comment */ int j; 1.5 空白 white-space: new-line The tab character (U+0009) The vertical tab character (U+000B) The form feed character (U+000C) The "control-Z" or "substitute" character (U+001A) All characters with Unicode class "Zs" 1.6 标记 C#有五种标记:标识符、关键字、数据符号、操作符和标点。因为空白像是标记的分割符,所以被忽略了。 token: identifi er keyword literal operator-or-punctuator -------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明------- 二、C#的句法分析 句法分析阶段就是把标记流转换为可执行代码。 2.1 标识符 标识符的规则符合统一字符编码标准3.0,除了下划线允许使用起首大写字母,格式化字符 (类Cf)不允许用于标识符而统一字符编码标准中的escape 字符允许用在标识符中。 identifi er: available-identifi er @ identifie r-or-keyword available-identifi er: An identifi er-or-keyword that is not a keyword identifi er-or-keyword: identifi er-start-character identif ier-part-charactersopt identifi er-start-character: letter-character underscore-character identifi er-part-characters: identifi er-part-character identifi er-part-characters identifi er-part-character identifi er-part-character: letter-character combining-character decimal-digit-character underscore-character letter-character: A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl A unicode-character-escape-sequence representing a character of classes Lu, Ll, Lt, Lm, Lo, or Nl combining-character: A Unicode character of classes Mn or Mc A unicode-character-escape-sequence representing a character of classes Mn or Mcdecimal-digit- character: A Unicode character of the class Nd A unicode-character-escape-sequence representing a character of the class Nd underscore-character: A Unicode character of the class Pc A unicode-character-escape-sequence representing a character of the class Pc 合法标识符的例子包括“identifier1”, “_identifier2”, 和 “@if”。 前缀“@”使得可以在标识符中使用关键词。实际上字符@不是标识符的一部分,所以如果没有这个前缀,可能在另外一种语言中被视为通常的标识符。 注意:不是关键字也在标识符中使用前缀@是允许的,但是这是一种很不好的风格。 示例: class @class { static void @static(bool @bool) { if (@bool) Console.WriteLine("true"); else Console.WriteLine("false"); } } class Class1 { static void M { @class.@static(true); } } 定义了一个名为“class”的类,有一个静态方法名为“static”,他使用了一个名为“bool”的参数。 2.2 关键字 关键字是类似于标识符的保留字符序列,除非用@字符开头,否则不能用作标识符。 关键字: one of abstract base bool break byte case catch char checked class const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long namespace new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual void while 2.3 数据符号 数据符号是数值的源代码表示。 literal: boolean-literal integer-literal real-literal character-literal string-literal null-literal 2.3.1 二进制数据符号 这里有两种二进制数值:true 和false。 boolean-literal: true false 2.3.2 整数数据符号 整数数据符号有两种可能的形式:十进制和十六进制。 integer-literal: decimal-integer-literal hexadecimal-integer-literal decimal-integer-literal: decimal-digits integer-type-suff ixopt decimal-digits: decimal-digit decimal-digits decimal-digit decimal-digit: one of 0 1 2 3 4 5 6 7 8 9 integer-type -suff ix: one of U u L l UL Ul uL ul LU Lu lU lu hexadecimal-integer-literal: 0x hex-digits integer-type-suff ixopt hex-digits: hex-digit hex-digits hex-digit hex-digit: one of 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f 整数数据符号的类型按下面确定: 1 如果数据符号没有后缀,它就有这些类型中的第一个,这些类型可以表示出它的数值:int、uint、 long、ulong 。 2 如果数据符号有后缀U 或u,它就有这些类型中的第一个,这些类型可以表示出它的数值:uint, ulong。 3 如果数据符号有后缀L 或l,If the literal is suffixed by L or l, 它就有这些类型中的第一个,这些类型可以表示出它的数值:long, ulong。 4 如果数据符号有后缀UL、Ul、uL、ul、LU、Lu、lU或lu,它的类型就是ulong。 如果用整数数据符号表示的数值超出了ulong 类型,就会产生错误。 同时,为了允许最小的可能的int 和long 数值可以用十进制整数描述,存在下面两个规则: 1 当一个十进制整数数据符号的数值为2147483648 (231),并且没有整数类型后缀,而操作数有一元负操作符(§错误!未找到引用源。)时,结果是int 类型的常数,数值为-2147483648 (-231)。在所有其它情况下,这样的一个十进制整数数据符号是uint 类型。 2 当一个十进制整数数据符号的数值为9223372036854775808 (263),并且没有整数类型后缀或整数类型后缀L 或l,而操作数有一元负操作符(§错误!未找到引用源。)时,结果是long 类型的常数,数值为-9223372036854775808 (-263)。在所有其它情况下,这样的一个十进制整数数据符号是ulong 类型。 2.3.2.1 实数据符号 real-literal: decimal-digits . decimal-digits exponen t-par topt real-type-suff ixopt . decimal-digits exponent-partopt real-typ e-suff ixopt decimal-digits exponent-part real-type-suff ixopt decimal-digits real-type-suff ix exponent-part: e signopt decimal-digits E signopt decimal-digits sign: one of + - real-type-suff ix: one of F f D d M m 如果没有指定real 类型后缀,实数据符号的类型是double。不然的话,实类型后缀决定了实数据符号,相应的规则如下: 1 一个实数据以F 或f 为后缀是float 类型。例如数据符号1f、1.5f、1e10f、和-123.456F都是float类型数据。 2 一个实数据以D 或d 为后缀是double 类型。例如数据符号1d、1.5d、1e10d、和-123.456d都是double 类型数据。 3 一个实数据以M 或m 为后缀是decimal类型。例如数据符号1m、1.5m、1e10m、和-123.456m都是decimal类型数据。 这里要重点注意一下,如果所指定的数据符号不能用指定类型表示,在编译时会产生错误。 2.3.2.2 字符数据符号 字符数据符号是一个用号括起来的单个字符,如'a'。 character-literal: ' character ' character: single-character simple-escape-sequence hexadecimal-escape-sequence unicode-character-escape-sequence single-character: Any character except ' (U+0027), \ (U+005C), and new-line simple-escape-sequence: one of \' \" \\ \0 \a \b \f \n \r \t \v hexadecimal-escape-sequence: \x hex-digit hex-digitopt hex-digitopt hex-digitopt 在一个单转意符序列或一个十六进制转意符序列中,一个跟在反斜杠字符(\)后面的字符必然是下面的字符之一: '、"、\、0、a、b、f、n、r、t、x、v,否则,在编译是会发生错误。 一个简单的转意符序列表示了统一的字符编码的字符编码,如下表所示。 转意序列 字符名称 Unicode 编码 \' Single quote 0x0027 \" Double quote 0x0022 \\ Backslash 0x005C \0 Null 0x0000 \a Alert 0x0007 \b Backspace 0x0008 \f Form feed 0x000C \n New line 0x000A \r Carriage return 0x000D \t Horizontal tab 0x0009 \v Vertical tab 0x000B 2.3.2.3 字符串数据符号 C# 支持两种形式的字符串数据符号:规则字符串数据符号和逐字的字符串数据符号。规则字符串数字 符号由用双引号括起0 或更多字符组成,例如"Hello, world",并且也许会包括简单转意序列(例如\t 表示tab 字符)和十六进制转意序列。 逐字的字符串数据符号由一个@字符后面跟着双引号括起的0 或者更多字符组成。一个简单的例子是 @"Hello, world"。在一个逐字字符串数据符号中,分割符间的字符通常认为是逐字的,只有引用转意序列例 外。特别的是,简单转意序列和十六进制转意序列在逐字字符串数据符号中不支持。一个逐字字符串数据符号可 能会跨越很多行。 string-literal: regular-string-literal verbatim-string-literal regular-string-literal: " regular-string-literal-charactersopt " regular-string-literal-characters: regular-string-literal-character regular-string-literal-characters regular-string-literal-character regular-string-literal-character: single-regular-string-literal-character simple-escape-sequence hexadecimal-escape-sequence unicode-character-escape-sequence single-regular-string-literal-character: Any character except " (U+0022), \ (U+005C), and new-line verbatim-string-literal: @" verbatim -string-literal-charactersopt " verbatim-string-literal-characters: verbatim-string-literal-character verbatim-string-literal-characters verbatim-string-literal-character verbatim-string-literal-character: single-verbatim-string-literal-character quote-escape-sequence single-verbatim-string-literal-character: any character except " quote-escape-sequence: "" 示例 string a = "hello, world"; // hello, world string b = @"hello, world"; // hello, world string c = "hello \t world"; // hello world string d = @"hello \t world"; // hello \t world string e = "Joe said \"Hello\" to me"; // Joe said "Hello" string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" string g = "\\\\sever\\share\\file.txt"; // \\server\share\file.txt string h = @"\\server\share\file.txt"; // \\server\share\file.txt string i = "one\ntwo\nthree"; string j = @"one two three"; 介绍了多种字符串数据符号。最后一个字符串数据j 是逐字字符串数据,它横跨了很多行。在引号间的字符,包括空白如转行字符,都是逐字复制的。 2.3.2.4 null 数据字符 null-literal: null 2.3.2.5操作符和标点 这里有许多种操作符和标点。操作符用于表达式来描述操作涉及到一个或多个操作数。例如,表达式a +b 使用+操作符来把a 和b 相加。标点用于组织和分割。例如标点;是用来分割在声明列表中出现的声明。 operator-or-punctuator: one of { } [ ] ( ) . , : ; + - * / % & | ^ ! ~ = < > ? ++ -- && || << >> == != <= >= += -= *= /= %= &= |= ^= <<= >>= -> 2.3.2..6 Unicode 字符转意字符序列 一个Unicode 字符转意字符序列代表了一个Unicode 字符。Unicode 字符转意字符序列在标识符,字符串数据符号和字符数据符号中是被允许的。 unicode-character-escape-sequence: \u hex-digit hex-digit hex-digit hex-digit 不能实现多重转换。例如字符串数据“\u005Cu005C”与 “\u005C” rather than “\\”是相等的。 (Unicode数值\u005C是字符 “\”。) 示例: class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) Console.WriteLine(c.ToString()); } } 介绍了许多\u0066 的使用,它是字母“f”的字符转意序列。这个程序等价于 class Class1 { static void Test(bool f) { char c = 'f'; if (f) Console.WriteLine(c.ToString()); } } -------思多雅[天行健]版权所有,首发太平洋论论坛,转载请注明------- 三、学习建议 学习这一章,有三个好的建议: 自己根据本专题后续的的一些实例自己体验一下; 到图书馆找些《编译原理》的书,多看一下; ![]() 实再不然,先知道有这么一回事,然后回过头再来学习这一部分。
天行健,君子以自强不息!我的博客:http://blog.pconline.com.cn/lq013
思多雅源自中华民族源远流长的质朴哲学与古希腊思辨哲学的结合,代表着严密与严谨的思想与行动。 |
1. 词法分析
任务:分析由字符组成的源程序,把它们识别成为一个一个的具有独立意义的“单词”,并识别出与其有关的属性(如标识符、数等),再转换成某种统一的内部形式,以供其它部分使用。 2. 语法分析 任务:在词法分析的基础上,根据语言的语法规则,把单词序列分解成各类语法单位,如程序、语句、表达式等。 3.语义处理 根据语法结构分析其含义,审查源程序有无语义错误,并为代码生成收集类型信息。 例如:类型检查,设x为整型,则 x:=10.5 {语义错} 又如,x:1..8; y:real; A:integer; A:=10; x:=A; {语义正确?} y:=A; 4. 中间代码生成 任务:将源程序变成一种内部表示形式。这种内部形式叫中间代码。例如三元式、4元式等。(初步的翻译) 中间代码的选择原则: ①容易生成 ②容易翻译成目标机代码(与目标机体系结构较一致) ③下一步优化要方便 5. 优化 任务:对前阶段产生的中间代码进行变换或改造,使生成的目标代码更为高效(时/空节省)。 6.目标代码生成 任务:把优化过的中间代码变换成特定机器上的绝对指令代码或汇编代码。(本阶段和机器指令打交道多,比较复杂)。 J1--------------------------------------------------------------------j1 【例】假定有如下程序段。 begin var sum,first,count:real; sum:=first+count*10 end. (1)词分 单词(内部形式) 属性 单词(内部形式) 属性 beginsy 保留字 varsy 保留字 sum(id1) 标识符 comma 分界符 first(id2) 标识符 comma 界限符 count(id3) 标识符 colon 界限符 realcon 类型符 semicolon 界限符 sum 标识符 becomes 运算符 first 标识符 plus 运算符 count 标识符 times 运算符 10 数字 endsy 保留字 period 界限符 空格的作用应注意; 字符b、e、g、i、n构成了单词begin。 :和=构成单词:= 上述赋值语句可表示为:id1:=id2+id3*10 内部形式为: id1 becomes id2 plus id3 times 10 (2)语分 怎么知道这个程序段是正确的?必须知道这个赋值语句是语法上正确的!编译程序要依据文法规则来考察: <tu1.4.doc> 能推出一棵分析树便为正确的。 上述分析树(Parse tree)也能够表示成语法树(Syntax tree): <id1.doc> (3)语义处理 上述赋值语句中sum、first、count均为实型,但10为整型,在进行count*10的分析时,就要进行类型判断,结果是要将10转成实型。故在语法分析树上增加一语义处理结点。即: id1-id2doc <id1-id2.doc> (4)中间代码生成。 四元式:(操作,运算分量1,运算分量2,结果) 则,sum:=first+count*10可生成以下四元式序列: ① (inttoreal 10 - t1) ② (* id3 t1 t2) ③ (+ id2 t2 t3) ④ (:= t3 - id1) (5)优化 上述可优化成: ① (* id3 10.0 t1) ② (+ id2 t1 id1) 注意: 10在编译阶段转成了10.0; t3可略,id2+t1直接存放在id1中即可; t2也可略。 指令和临时单元都减少了! (6)代码生成(使用有2个寄存器的机器) ① MOVF id3 R2 ② MULF #10.0 R2 ③ MOVF id2 R1 ④ ADDF R1 R2 ⑤ MOV R2 id1 对汇编目标代码也可以进行优化,如: (A+B)*(C+D) 4元组为: (+, A, B, T1) (+,C, D, T2) (*, T1,T2, T3) 用单地址、单累加器计算机的汇编语言,根据4元式生成以下代码: LDA A ADD B STO T1 LDA C ADD D STO T2 LDA T1 MUL T2 STO T3 优化成: LDA A ADD B STO T1 LDA C ADD D MUL T1 STO T2 四、编译阶段及其组合 上述6个部分的工作可以分为6个阶段来完成,一个部分对应一个阶段。这种划分是编译程序的逻辑组织。有时6个部分又缺了一、二个部分(合起来了),也就减少了遍(pass)。 如果把上述每个阶段都生成一个中间形式,则需要对源程序或其中间形式扫视6遍。这有时是十分不经济的。因为每一遍都要从外存上调入上一遍的结果,加工后再形成新的结果存于外存。但这样做逻辑关系清楚一些。 人们往往把几个阶段合在一起进行处理。比如,只要一次扫视源程序就得到等价的目标程序--一遍扫描。 又如,对源程序进行一遍扫描,生成一种中间形式,再对中间程序扫视一遍生成目标代码--二遍扫描。(前端+后端) 现代编译程序往往为虚拟机生成代码,然后在各个不同的具体机器上去执行这种代码,如Java(JVM),C#(MSIL)等。这有利于可移植性。亦称半编译半解释。(以后我们看到,在PL/0编译中就是这样做的。) 有时一遍很难达到目的,如ALGOL68允许名字先使用后定义,这往往就需要至少扫视2遍。 引用嘉宾:aizibin的聊天记录,与大家一起分享
天行健,君子以自强不息!我的博客:http://blog.pconline.com.cn/lq013
思多雅源自中华民族源远流长的质朴哲学与古希腊思辨哲学的结合,代表着严密与严谨的思想与行动。 |