为有牺牲多壮志,敢教日月换新天。

C#6.0语言规范(二) 词法结构

热烈欢迎,请直接点击!!!

进入博主App Store主页,下载使用各个作品!!!

注:博主将坚持每月上线一个新app!!!

程式

AC#程序由一个或多个源文件组成,正式称为编译单元编译单元)。源文件是Unicode字符的有序序列。源文件通常与文件系统中的文件一一对应,但不需要此对应关系。为了获得最大的可移植性,建议使用UTF-8编码对文件系统中的文件进行编码。

从概念上讲,程序是使用三个步骤编译的:

  1. 转换,将文件从特定字符库和编码方案转换为Unicode字符序列。
  2. 词法分析,将Unicode输入字符流转换为标记流。
  3. 句法分析,将令牌流转换为可执行代码。

文法

本规范使用两个语法介绍了C#编程语言的语法。词法词法)定义Unicode字符如何组合以形成行结束,空白,注释,标记和预处理指令。句法语法句法语法)限定从词法文法产生的标记如何被组合以形成C#程序。

语法符号

使用ANTLR语法工具的符号以Backus-Naur形式呈现词法和句法语法。

词汇语法

C#的词汇语法在词法分析标记预处理指令中给出词法语法的终端符号是Unicode字符集的字符,词法语法指定字符如何组合形成标记(标记),空格(白色空格),注释(注释)和预处理指令(预处理指令)。

C#程序中的每个源文件都必须符合词法语法输入生成(词法分析)。

句法语法

C#的句法语法在本章后面的章节和附录中介绍。句法语法的终端符号是由词法语法定义的标记,语法语法指定如何组合标记以形成C#程序。

C#程序中的每个源文件都必须符合语法语法(编译单元compilation_unit生成

词法分析

输入产生式定义C#源文件的词法结构。C#程序中的每个源文件都必须符合此词法语法生成。

 1 input
 2     : input_section?
 3     ;
 4 
 5 input_section
 6     : input_section_part+
 7     ;
 8 
 9 input_section_part
10     : input_element* new_line
11     | pp_directive
12     ;
13 
14 input_element
15     : whitespace
16     | comment
17     | token
18     ;

五个基本元素构成了C#源文件的词法结构:行终止符(行终止符),空格(空格),注释(注释),标记(标记)和预处理指令(预处理指令)。在这些基本元素中,只有令牌在C#程序(语法语法的句法语法中具有重要意义

C#源文件的词法处理包括将文件缩减为一系列令牌,这些令牌成为句法分析的输入。行终止符,空格和注释可用于分隔标记,预处理指令可以导致跳过源文件的各个部分,但这些词法元素对C#程序的语法结构没有影响。

在插值字符串文字(插值字符串文字的情况下,单个标记最初由词法分析产生,但被分解为若干输入元素,这些输入元素重复进行词法分析,直到所有插值字符串文字都已解析为止。然后,所得到的标记用作句法分析的输入。

当几个词法语法产生与源文件中的一系列字符匹配时,词法处理总是形成最长的词法元素。例如,字符序列//被处理为单行注释的开头,因为该词法元素比单个/标记长。

行终止符

行终止符将C#源文件的字符分成行。

 1 input
 2     : input_section?
 3     ;
 4 
 5 input_section
 6     : input_section_part+
 7     ;
 8 
 9 input_section_part
10     : input_element* new_line
11     | pp_directive
12     ;
13 
14 input_element
15     : whitespace
16     | comment
17     | token
18     ;

为了与添加文件结束标记的源代码编辑工具兼容,并使源文件可以被视为一系列正确终止的行,以下转换按顺序应用于C#程序中的每个源文件:

  • 如果源文件的最后一个字符是Control-Z字符(U+001A),则删除此字符。
  • U+000D如果源文件非空并且源文件的最后一个字符不是回车符(U+000D),换行符(U+000A),行分隔符,则将回车符()添加到源文件的末尾U+2028)或段落分隔符(U+2029)。

注释

支持两种形式的注释:单行注释和分隔注释。单行注释以字符开头,//并延伸到源行的末尾。分隔的注释以字符开头,以字符/*结尾*/定界注释可能跨越多行。

 1 comment
 2     : single_line_comment
 3     | delimited_comment
 4     ;
 5 
 6 single_line_comment
 7     : '//' input_character*
 8     ;
 9 
10 input_character
11     : '<Any Unicode character except a new_line_character>'
12     ;
13 
14 new_line_character
15     : '<Carriage return character (U+000D)>'
16     | '<Line feed character (U+000A)>'
17     | '<Next line character (U+0085)>'
18     | '<Line separator character (U+2028)>'
19     | '<Paragraph separator character (U+2029)>'
20     ;
21 
22 delimited_comment
23     : '/*' delimited_comment_section* asterisk* '/'
24     ;
25 
26 delimited_comment_section
27     : '/'
28     | asterisk* not_slash_or_asterisk
29     ;
30 
31 asterisk
32     : '*'
33     ;
34 
35 not_slash_or_asterisk
36     : '<Any Unicode character except / or *>'
37     ;

评论不嵌套。字符序列/*,并*/有中没有任何特殊含义//的评论和字符序列//,并/*有一个分隔符的注释中没有任何特殊含义。

不在字符和字符串文字中处理注释。

这个例子

1 /* Hello, world program
2    This program writes "hello, world" to the console
3 */
4 class Hello
5 {
6     static void Main() {
7         System.Console.WriteLine("hello, world");
8     }
9 }

包括分隔的评论。

这个例子

1 // Hello, world program
2 // This program writes "hello, world" to the console
3 //
4 class Hello // any name will do for this class
5 {
6     static void Main() { // this method must be named "Main"
7         System.Console.WriteLine("hello, world");
8     }
9 }

显示了几个单行注释。

白色空间

空格被定义为具有Unicode类Zs(包括空格字符)的任何字符,以及水平制表符,垂直制表符和换页符。

1 whitespace
2     : '<Any character with Unicode class Zs>'
3     | '<Horizontal tab character (U+0009)>'
4     | '<Vertical tab character (U+000B)>'
5     | '<Form feed character (U+000C)>'
6     ;

令牌

有几种标记:标识符,关键字,文字,运算符和标点符号。空格和注释不是令牌,尽管它们充当令牌的分隔符。

 1 token
 2     : identifier
 3     | keyword
 4     | integer_literal
 5     | real_literal
 6     | character_literal
 7     | string_literal
 8     | interpolated_string_literal
 9     | operator_or_punctuator
10     ;

Unicode字符转义序列

Unicode字符转义序列表示Unicode字符。Unicode字符转义序列在标识符(标识符),字符文字(字符文字)和常规字符串文字(字符串文字)中处理。Unicode字符转义不会在任何其他位置处理(例如,形成运算符,标点符号或关键字)。

1 unicode_escape_sequence
2     : '\\u' hex_digit hex_digit hex_digit hex_digit
3     | '\\U' hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit
4     ;

Unicode转义序列表示由“ \u”或“ \U”字符后面的十六进制数字形成的单个Unicode 字符。由于C#在字符和字符串值中使用Unicode代码点的16位编码,因此字符文字中不允许使用U + 10000到U + 10FFFF范围内的Unicode字符,并使用字符串文字中的Unicode代理项对来表示。不支持代码点高于0x10FFFF的Unicode字符。

不执行多个翻译。例如,字符串文字“ \u005Cu005C”等同于“ \u005C”而不是“ \”。Unicode值\u005C是字符“ \”。

这个例子

1 class Class1
2 {
3     static void Test(bool \u0066) {
4         char c = '\u0066';
5         if (\u0066)
6             System.Console.WriteLine(c.ToString());
7     }        
8 }

显示了几个用法\u0066,它是字母“ f” 的转义序列该计划相当于

1 class Class1
2 {
3     static void Test(bool f) {
4         char c = 'f';
5         if (f)
6             System.Console.WriteLine(c.ToString());
7     }        
8 }

身份标识

本节中给出的标识符规则完全符合Unicode标准附件31推荐的规则,但允许下划线作为初始字符(在C编程语言中是传统的),标识符中允许使用Unicode转义序列,@允许使用“ ”字符作为前缀,以使关键字可用作标识符。

 1 identifier
 2     : available_identifier
 3     | '@' identifier_or_keyword
 4     ;
 5 
 6 available_identifier
 7     : '<An identifier_or_keyword that is not a keyword>'
 8     ;
 9 
10 identifier_or_keyword
11     : identifier_start_character identifier_part_character*
12     ;
13 
14 identifier_start_character
15     : letter_character
16     | '_'
17     ;
18 
19 identifier_part_character
20     : letter_character
21     | decimal_digit_character
22     | connecting_character
23     | combining_character
24     | formatting_character
25     ;
26 
27 letter_character
28     : '<A Unicode character of classes Lu, Ll, Lt, Lm, Lo, or Nl>'
29     | '<A unicode_escape_sequence representing a character of classes Lu, Ll, Lt, Lm, Lo, or Nl>'
30     ;
31 
32 combining_character
33     : '<A Unicode character of classes Mn or Mc>'
34     | '<A unicode_escape_sequence representing a character of classes Mn or Mc>'
35     ;
36 
37 decimal_digit_character
38     : '<A Unicode character of the class Nd>'
39     | '<A unicode_escape_sequence representing a character of the class Nd>'
40     ;
41 
42 connecting_character
43     : '<A Unicode character of the class Pc>'
44     | '<A unicode_escape_sequence representing a character of the class Pc>'
45     ;
46 
47 formatting_character
48     : '<A Unicode character of the class Cf>'
49     | '<A unicode_escape_sequence representing a character of the class Cf>'
50     ;

有关上述Unicode字符类的信息,请参阅Unicode标准版本3.0,第4.5节。

有效标识符的示例包括“ identifier1”,“ _identifier2”和“ @if”。

符合程序中的标识符必须采用Unicode标准化表C定义的规范格式,如Unicode标准附件15所定义。遇到不在标准化表单C中的标识符时的行为是实现定义的; 但是,不需要诊断。

前缀“ @”允许使用关键字作为标识符,这在与其他编程语言交互时很有用。该字符@实际上不是标识符的一部分,因此标识符可能在其他语言中看作普通标识符,没有前缀。带有@前缀的标识符称为逐字标识符允许使用@非关键字标识符前缀,但强烈建议不要使用样式。

这个例子:

 1 class @class
 2 {
 3     public static void @static(bool @bool) {
 4         if (@bool)
 5             System.Console.WriteLine("true");
 6         else
 7             System.Console.WriteLine("false");
 8     }    
 9 }
10 
11 class Class1
12 {
13     static void M() {
14         cl\u0061ss.st\u0061tic(true);
15     }
16 }

使用名为“ class”的静态方法定义名为“ ” 的类,该方法采用名为“ static”的参数bool请注意,由于关键字中不允许使用Unicode转义,因此标记“ cl\u0061ss”是标识符,与“ ”的标识符相同@class

如果在应用以下转换后它们相同,则认为两个标识符相同,顺序如下:

  • @如果使用前缀“ ”,则将其删除。
  • 每个unicode_escape_sequence都会转换为相应的Unicode字符。
  • 删除任何formatting_character

包含两个连续下划线字符(U+005F)的标识符保留供实现使用。例如,实现可能会提供以两个下划线开头的扩展关键字。

关键词

关键字是保留的字符的标识符样序列,并且不能被用作当由除了开头的识别符@的字符。

 1 keyword
 2     : 'abstract' | 'as'       | 'base'       | 'bool'      | 'break'
 3     | 'byte'     | 'case'     | 'catch'      | 'char'      | 'checked'
 4     | 'class'    | 'const'    | 'continue'   | 'decimal'   | 'default'
 5     | 'delegate' | 'do'       | 'double'     | 'else'      | 'enum'
 6     | 'event'    | 'explicit' | 'extern'     | 'false'     | 'finally'
 7     | 'fixed'    | 'float'    | 'for'        | 'foreach'   | 'goto'
 8     | 'if'       | 'implicit' | 'in'         | 'int'       | 'interface'
 9     | 'internal' | 'is'       | 'lock'       | 'long'      | 'namespace'
10     | 'new'      | 'null'     | 'object'     | 'operator'  | 'out'
11     | 'override' | 'params'   | 'private'    | 'protected' | 'public'
12     | 'readonly' | 'ref'      | 'return'     | 'sbyte'     | 'sealed'
13     | 'short'    | 'sizeof'   | 'stackalloc' | 'static'    | 'string'
14     | 'struct'   | 'switch'   | 'this'       | 'throw'     | 'true'
15     | 'try'      | 'typeof'   | 'uint'       | 'ulong'     | 'unchecked'
16     | 'unsafe'   | 'ushort'   | 'using'      | 'virtual'   | 'void'
17     | 'volatile' | 'while'
18     ;

在语法的某些地方,特定标识符具有特殊含义,但不是关键字。这种标识符有时被称为“上下文关键字”。例如,在属性声明中,“ get”和“ set”标识符具有特殊含义(Accessors)。在这些位置以外的标识符getset从不允许的标识符,因此这种使用不会与使用这些单词作为标识符冲突。在其他情况下,例如var在隐式类型的局部变量声明(局部变量声明)中使用标识符“ ” ,上下文关键字可能与声明的名称冲突。在这种情况下,声明的名称优先于使用标识符作为上下文关键字。

字面

文字是一个值的一个源代码表示。

1 literal
2     : boolean_literal
3     | integer_literal
4     | real_literal
5     | character_literal
6     | string_literal
7     | null_literal
8     ;

布尔文字

有两个布尔文字值:truefalse

1 boolean_literal
2     : 'true'
3     | 'false'
4     ;

boolean_literal的类型bool

整数文字

整型数用于编写类型intuintlong,和ulong整数文字有两种可能的形式:十进制和十六进制。

 1 integer_literal
 2     : decimal_integer_literal
 3     | hexadecimal_integer_literal
 4     ;
 5 
 6 decimal_integer_literal
 7     : decimal_digit+ integer_type_suffix?
 8     ;
 9 
10 decimal_digit
11     : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
12     ;
13 
14 integer_type_suffix
15     : 'U' | 'u' | 'L' | 'l' | 'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu'
16     ;
17 
18 hexadecimal_integer_literal
19     : '0x' hex_digit+ integer_type_suffix?
20     | '0X' hex_digit+ integer_type_suffix?
21     ;
22 
23 hex_digit
24     : '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
25     | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f';

整数文字的类型确定如下:

  • 如果文字没有后缀,它具有第一这些类型的,其中它的值可以表示的:intuintlongulong
  • 如果文字后缀为Uu,则它具有这些类型中的第一个,其值可以表示为:uintulong
  • 如果文字后缀为Ll,则它具有这些类型中的第一个,其值可以表示为:longulong
  • 如果文字是由后缀ULUluLulLULulU,或lu,它的类型的ulong

如果整数文字表示的值超出ulong类型范围,则会发生编译时错误。

作为一种风格问题,建议在编写类型的文字时L使用“ ”代替“ l” long,因为很容易将字母“ l”与数字“ 1” 混淆

为了允许尽可能小的intlong值写为十进制整数文字,存在以下两个规则:

  • decimal_integer_literal具有值2147483648(2 ^ 31)和没有integer_type_suffix显示为紧接在一元负运算符标记(以下令牌元负运算),结果是类型的常量int用值-2147483648(-2 ^ 31) 。在所有其他情况下,这样的decimal_integer_literal属于类型uint
  • decimal_integer_literal的值为9223372036854775808(2 ^ 63)并且没有integer_type_suffixinteger_type_suffix L或者l在一元减号运算符令牌(一元减号运算符)之后立即显示为令牌时,结果是类型的常量,long其值为-9223372036854775808( - 2 ^ 63)。在所有其他情况下,这样的decimal_integer_literal属于类型ulong

实数

实数用于编写类型值floatdoubledecimal

 1 real_literal
 2     : decimal_digit+ '.' decimal_digit+ exponent_part? real_type_suffix?
 3     | '.' decimal_digit+ exponent_part? real_type_suffix?
 4     | decimal_digit+ exponent_part real_type_suffix?
 5     | decimal_digit+ real_type_suffix
 6     ;
 7 
 8 exponent_part
 9     : 'e' sign? decimal_digit+
10     | 'E' sign? decimal_digit+
11     ;
12 
13 sign
14     : '+'
15     | '-'
16     ;
17 
18 real_type_suffix
19     : 'F' | 'f' | 'D' | 'd' | 'M' | 'm'
20     ;

如果未指定real_type_suffix,则实数的类型为double否则,实际类型后缀确定实际文字的类型,如下所示:

  • 真实的文字后缀Ff类型float例如,文字1f1.5f1e10f,和123.456F都是类型的float
  • 真实的文字后缀Dd类型double例如,文字1d1.5d1e10d,和123.456D都是类型的double
  • 真实的文字后缀Mm类型decimal例如,文字1m1.5m1e10m,和123.456M都是类型的decimaldecimal通过获取精确值将此文字转换为值,并在必要时使用银行家的舍入(小数类型舍入到最接近的可表示值除非值被舍入或值为零,否则将保留文字中明显的任何比例(在后一种情况下,符号和比例将为0)。因此,2.900m将解析文字以形成带符号0,系数2900和比例的小数3

如果指定的文字无法以指示的类型表示,则会发生编译时错误。

类型的真实文字的值floatdouble通过使用IEEE“舍入到最近”模式来确定。

请注意,在实际文字中,小数点后总是需要十进制数字。例如,1.3F是一个真正的文字,但1.F不是。

字符文字

字符文字表示单个字符,通常由引号中的字符组成,如'a'

注意:ANTLR语法表示法令人困惑!在ANTLR中,当你写\'它时代表单引号'当你写\\它代表一个反斜杠\因此,字符文字的第一个规则意味着它以单引号开头,然后是字符,然后是单引号。而11个简单的转义序列\'\"\\\0\a\b\f\n\r\t\v

 1 character_literal
 2     : '\'' character '\''
 3     ;
 4 
 5 character
 6     : single_character
 7     | simple_escape_sequence
 8     | hexadecimal_escape_sequence
 9     | unicode_escape_sequence
10     ;
11 
12 single_character
13     : '<Any character except \' (U+0027), \\ (U+005C), and new_line_character>'
14     ;
15 
16 simple_escape_sequence
17     : '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' | '\\f' | '\\n' | '\\r' | '\\t' | '\\v'
18     ;
19 
20 hexadecimal_escape_sequence
21     : '\\x' hex_digit hex_digit? hex_digit? hex_digit?;

下面反斜线字符的字符(\)是一个字符必须是以下字符中的一个:'"\0abfnrtuUxv否则,发生编译时错误。

十六进制转义序列表示单个Unicode字符,其值由“ \x” 后面的十六进制数字组成

如果字符文字表示的值大于U+FFFF,则发生编译时错误。

Unicode字符转义序列(Unicode字符转义序列中的字符)文本必须在范围U+0000U+FFFF

简单的转义序列表示Unicode字符编码,如下表所述。

逃脱序列角色名字Unicode编码
\' 单引号 0x0027
\" 双引号 0x0022
\\ 反斜杠 0x005C
\0 空值 0x0000
\a 警报 0x0007
\b 退格 0x0008
\f 表格饲料 0x000C
\n 新队 0x000A
\r 回程 0x000D
\t 水平标签 0x0009
\v 垂直标签 0x000B

character_literal的类型char

字符串文字

C#支持两种形式的字符串文字:常规字符串文字逐字字符串文字

常规字符串文字由用双引号括起来的零个或多个字符组成,例如"hello",可以包括简单转义序列(例如\t制表符),以及十六进制和Unicode转义序列。

逐字字符串文字由一个@字符后跟一个双引号字符,零个或多个字符以及一个结束双引号字符组成。一个简单的例子是@"hello"在逐字字符串文字中,分隔符之间的字符是逐字解释的,唯一的例外是quote_escape_sequence特别是,简单的转义序列,十六进制和Unicode转义序列不会在逐字字符串文字中处理。逐字字符串文字可以跨越多行。

 1 string_literal
 2     : regular_string_literal
 3     | verbatim_string_literal
 4     ;
 5 
 6 regular_string_literal
 7     : '"' regular_string_literal_character* '"'
 8     ;
 9 
10 regular_string_literal_character
11     : single_regular_string_literal_character
12     | simple_escape_sequence
13     | hexadecimal_escape_sequence
14     | unicode_escape_sequence
15     ;
16 
17 single_regular_string_literal_character
18     : '<Any character except " (U+0022), \\ (U+005C), and new_line_character>'
19     ;
20 
21 verbatim_string_literal
22     : '@"' verbatim_string_literal_character* '"'
23     ;
24 
25 verbatim_string_literal_character
26     : single_verbatim_string_literal_character
27     | quote_escape_sequence
28     ;
29 
30 single_verbatim_string_literal_character
31     : '<any character except ">'
32     ;
33 
34 quote_escape_sequence
35     : '""'
36     ;

下面反斜线字符的字符(\)是一个regular_string_literal_character必须是以下字符中的一个:'"\0abfnrtuUxv否则,发生编译时错误。

这个例子

 1 string a = "hello, world";                   // hello, world
 2 string b = @"hello, world";                  // hello, world
 3 
 4 string c = "hello \t world";                 // hello      world
 5 string d = @"hello \t world";                // hello \t world
 6 
 7 string e = "Joe said \"Hello\" to me";       // Joe said "Hello" to me
 8 string f = @"Joe said ""Hello"" to me";      // Joe said "Hello" to me
 9 
10 string g = "\\\\server\\share\\file.txt";    // \\server\share\file.txt
11 string h = @"\\server\share\file.txt";       // \\server\share\file.txt
12 
13 string i = "one\r\ntwo\r\nthree";
14 string j = @"one
15 two
16 three";

显示各种字符串文字。最后一个字符串文字j是一个跨越多行的逐字字符串文字。引号之间的字符(包括新行字符等空格)将逐字保留。

由于十六进制转义序列可以具有可变数量的十六进制数字,因此字符串文字"\x123"包含具有十六进制值123的单个字符。要创建包含十六进制值12后跟字符3的字符的字符串,可以写入"\x00123""\x12" + "3"替换。

string_literal的类型string

每个字符串文字不一定会产生新的字符串实例。当两个或多个根据字符串相等运算符(字符串相等运算符等效的字符串文字出现在同一程序中时,这些字符串文字引用相同的字符串实例。例如,产生的输出

1 class Test
2 {
3     static void Main() {
4         object a = "hello";
5         object b = "hello";
6         System.Console.WriteLine(a == b);
7     }
8 }

True因为两个文字引用相同的字符串实例。

插值字符串文字

插值字符串文字类似于字符串文字,但包含由{and 分隔的孔},其中可以出现表达式。在运行时,表达式的计算目的是将其文本形式替换为出现孔的位置的字符串。字符串插值的语法和语义在章节(插值字符串中描述

与字符串文字一样,插值字符串文字可以是常规字符串也可以是逐字字符串。插值的常规字符串文字由$"分隔",插值的逐字字符串文字由$@"分隔"

与其他文字一样,插值字符串文字的词法分析最初会产生单个标记,如下面的语法所示。然而,在句法分析之前,插值字符串文字的单个标记被分成几个标记,用于包围空洞的字符串部分,并且再次对词汇中出现的输入元素进行词法分析。这可能反过来产生更多内插的字符串文字进行处理,但是,如果词汇上的正确,最终会导致一系列令牌进行句法分析处理。

  1 interpolated_string_literal
  2     : '$' interpolated_regular_string_literal
  3     | '$' interpolated_verbatim_string_literal
  4     ;
  5 
  6 interpolated_regular_string_literal
  7     : interpolated_regular_string_whole
  8     | interpolated_regular_string_start  interpolated_regular_string_literal_body interpolated_regular_string_end
  9     ;
 10 
 11 interpolated_regular_string_literal_body
 12     : regular_balanced_text
 13     | interpolated_regular_string_literal_body interpolated_regular_string_mid regular_balanced_text
 14     ;
 15 
 16 interpolated_regular_string_whole
 17     : '"' interpolated_regular_string_character* '"'
 18     ;
 19 
 20 interpolated_regular_string_start
 21     : '"' interpolated_regular_string_character* '{'
 22     ;
 23 
 24 interpolated_regular_string_mid
 25     : interpolation_format? '}' interpolated_regular_string_characters_after_brace? '{'
 26     ;
 27 
 28 interpolated_regular_string_end
 29     : interpolation_format? '}' interpolated_regular_string_characters_after_brace? '"'
 30     ;
 31 
 32 interpolated_regular_string_characters_after_brace
 33     : interpolated_regular_string_character_no_brace
 34     | interpolated_regular_string_characters_after_brace interpolated_regular_string_character
 35     ;
 36 
 37 interpolated_regular_string_character
 38     : single_interpolated_regular_string_character
 39     | simple_escape_sequence
 40     | hexadecimal_escape_sequence
 41     | unicode_escape_sequence
 42     | open_brace_escape_sequence
 43     | close_brace_escape_sequence
 44     ;
 45 
 46 interpolated_regular_string_character_no_brace
 47     : '<Any interpolated_regular_string_character except close_brace_escape_sequence and any hexadecimal_escape_sequence or unicode_escape_sequence designating } (U+007D)>'
 48     ;
 49 
 50 single_interpolated_regular_string_character
 51     : '<Any character except \" (U+0022), \\ (U+005C), { (U+007B), } (U+007D), and new_line_character>'
 52     ;
 53 
 54 open_brace_escape_sequence
 55     : '{{'
 56     ;
 57 
 58     close_brace_escape_sequence
 59     : '}}'
 60     ;
 61 
 62 regular_balanced_text
 63     : regular_balanced_text_part+
 64     ;
 65 
 66 regular_balanced_text_part
 67     : single_regular_balanced_text_character
 68     | delimited_comment
 69     | '@' identifier_or_keyword
 70     | string_literal
 71     | interpolated_string_literal
 72     | '(' regular_balanced_text ')'
 73     | '[' regular_balanced_text ']'
 74     | '{' regular_balanced_text '}'
 75     ;
 76 
 77 single_regular_balanced_text_character
 78     : '<Any character except / (U+002F), @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B), } (U+007D) and new_line_character>'
 79     | '</ (U+002F), if not directly followed by / (U+002F) or * (U+002A)>'
 80     ;
 81 
 82 interpolation_format
 83     : interpolation_format_character+
 84     ;
 85 
 86 interpolation_format_character
 87     : '<Any character except \" (U+0022), : (U+003A), { (U+007B) and } (U+007D)>'
 88     ;
 89 
 90 interpolated_verbatim_string_literal
 91     : interpolated_verbatim_string_whole
 92     | interpolated_verbatim_string_start interpolated_verbatim_string_literal_body interpolated_verbatim_string_end
 93     ;
 94 
 95 interpolated_verbatim_string_literal_body
 96     : verbatim_balanced_text
 97     | interpolated_verbatim_string_literal_body interpolated_verbatim_string_mid verbatim_balanced_text
 98     ;
 99 
100 interpolated_verbatim_string_whole
101     : '@"' interpolated_verbatim_string_character* '"'
102     ;
103 
104 interpolated_verbatim_string_start
105     : '@"' interpolated_verbatim_string_character* '{'
106     ;
107 
108 interpolated_verbatim_string_mid
109     : interpolation_format? '}' interpolated_verbatim_string_characters_after_brace? '{'
110     ;
111 
112 interpolated_verbatim_string_end
113     : interpolation_format? '}' interpolated_verbatim_string_characters_after_brace? '"'
114     ;
115 
116 interpolated_verbatim_string_characters_after_brace
117     : interpolated_verbatim_string_character_no_brace
118     | interpolated_verbatim_string_characters_after_brace interpolated_verbatim_string_character
119     ;
120 
121 interpolated_verbatim_string_character
122     : single_interpolated_verbatim_string_character
123     | quote_escape_sequence
124     | open_brace_escape_sequence
125     | close_brace_escape_sequence
126     ;
127 
128 interpolated_verbatim_string_character_no_brace
129     : '<Any interpolated_verbatim_string_character except close_brace_escape_sequence>'
130     ;
131 
132 single_interpolated_verbatim_string_character
133     : '<Any character except \" (U+0022), { (U+007B) and } (U+007D)>'
134     ;
135 
136 verbatim_balanced_text
137     : verbatim_balanced_text_part+
138     ;
139 
140 verbatim_balanced_text_part
141     : single_verbatim_balanced_text_character
142     | comment
143     | '@' identifier_or_keyword
144     | string_literal
145     | interpolated_string_literal
146     | '(' verbatim_balanced_text ')'
147     | '[' verbatim_balanced_text ']'
148     | '{' verbatim_balanced_text '}'
149     ;
150 
151 single_verbatim_balanced_text_character
152     : '<Any character except / (U+002F), @ (U+0040), \" (U+0022), $ (U+0024), ( (U+0028), ) (U+0029), [ (U+005B), ] (U+005D), { (U+007B) and } (U+007D)>'
153     | '</ (U+002F), if not directly followed by / (U+002F) or * (U+002A)>'
154     ;

一个interpolated_string_literal令牌被重新解释为多个令牌和其他输入元件如下,在发生在顺序interpolated_string_literal

  • 下面的出现被重新解释为单独的单个令牌:前导$符号,interpolated_regular_string_wholeinterpolated_regular_string_startinterpolated_regular_string_midinterpolated_regular_string_endinterpolated_verbatim_string_wholeinterpolated_verbatim_string_startinterpolated_verbatim_string_midinterpolated_verbatim_string_end
  • 它们之间出现的regular_balanced_textverbatim_balanced_text被重新处理为input_section词法分析),并被重新解释为输入元素的结果序列。这些又可以包括要重新解释的内插字符串文字标记。

句法分析将令牌重新组合为interpolated_string_expression插值字符串)。

示例ALL

null文字

1 null_literal
2     : 'null'
3     ;

的 null_literal可以隐式转换为引用类型或空类型。

运营商和标点符号

有几种运算符和标点符号。表达式中使用运算符来描述涉及一个或多个操作数的操作。例如,表达式a + b使用+运算符添加两个操作数ab标点符号用于分组和分隔。

 1 operator_or_punctuator
 2     : '{'  | '}'  | '['  | ']'  | '('   | ')'  | '.'  | ','  | ':'  | ';'
 3     | '+'  | '-'  | '*'  | '/'  | '%'   | '&'  | '|'  | '^'  | '!'  | '~'
 4     | '='  | '<'  | '>'  | '?'  | '??'  | '::' | '++' | '--' | '&&' | '||'
 5     | '->' | '==' | '!=' | '<=' | '>='  | '+=' | '-=' | '*=' | '/=' | '%='
 6     | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
 7     ;
 8 
 9 right_shift
10     : '>>'
11     ;
12 
13 right_shift_assignment
14     : '>>='
15     ;

right_shiftright_shift_assignment产生中的竖线用于表示,与语法语法中的其他产品不同,令牌之间不允许任何类型的字符(甚至不是空格)。这些产品经过特殊处理,以便能够正确处理type_parameter_list类型参数)。

预处理指令

预处理指令提供了有条件地跳过源文件部分,报告错误和警告条件以及描述源代码的不同区域的能力。术语“预处理指令”仅用于与C和C ++编程语言的一致性。在C#中,没有单独的预处理步骤; 预处理指令作为词法分析阶段的一部分进行处理。

1 pp_directive
2     : pp_declaration
3     | pp_conditional
4     | pp_line
5     | pp_diagnostic
6     | pp_region
7     | pp_pragma
8     ;

可以使用以下预处理指令:

  • #define#undef,分别用于定义和取消定义条件编译符号(声明指令)。
  • #if#elif#else,和#endif,其用于有条件跳过的源代码(第条件编译指令)。
  • #line,用于控制为错误和警告发出的行号(行指令)。
  • #error并且#warning,它们分别用于发出错误和警告(诊断指令)。
  • #region#endregion,用于显式标记源代码的部分(Region指令)。
  • #pragma,用于为编译器指定可选的上下文信息(Pragma指令)。

预处理指令始终占用单独的源代码行,并始终以#字符和预处理指令名称开头#字符前面以及#字符和指令名称之间可能出现空格

含有源极线#define#undef#if#elif#else#endif#line,或#endregion指令可以用单个行注释结束。/* */包含预处理指令的源代码行不允许使用分隔注释(注释样式)。

预处理指令不是令牌,也不是C#语法语法的一部分。但是,预处理指令可用于包含或排除令牌序列,并且可以以这种方式影响C#程序的含义。例如,编译时,程序:

 1 #define A
 2 #undef B
 3 
 4 class C
 5 {
 6 #if A
 7     void F() {}
 8 #else
 9     void G() {}
10 #endif
11 
12 #if B
13     void H() {}
14 #else
15     void I() {}
16 #endif
17 }

导致与程序完全相同的令牌序列:

1 class C
2 {
3     void F() {}
4     void I() {}
5 }

因此,而词汇上,两个程序是完全不同的,从语法上讲,它们是相同的。

条件编译符号

通过所提供的条件编译功能#if#elif#else,和#endif指令是通过预处理表达式(受控预处理表达式)和条件编译符号。

1 conditional_symbol
2     : '<Any identifier_or_keyword except true or false>'
3     ;

条件编译符号有两种可能的状态:已定义未定义在源文件的词法处理开始时,条件编译符号是未定义的,除非它已由外部机制(例如命令行编译器选项)显式定义。#define处理指令时,该指令中指定的条件编译符号将在该源文件中定义。符号保持定义,直到#undef处理该相同符号指令,或者直到达到源文件的末尾。这方面的一个含义是,#define#undef在一个源文件中的指令在同一程序中的其他源文件没有任何影响。

在预处理表达式中引用时,定义的条件编译符号具有布尔值true,未定义的条件编译符号具有布尔值false在预处理表达式中引用条件编译符号之前,不要求显式声明它们。相反,未声明的符号只是未定义的,因此具有价值false

条件编译符号的名称空间是不同的,并且与C#程序中的所有其他命名实体分开。条件编译符号只能在#define#undef指令以及预处理表达式中引用

预处理表达式

预处理表达式可以出现在#if#elif指令中。运营商!==!=&&||被允许在预处理表达式,并且可以使用用于分组括号。

 1 pp_expression
 2     : whitespace? pp_or_expression whitespace?
 3     ;
 4 
 5 pp_or_expression
 6     : pp_and_expression
 7     | pp_or_expression whitespace? '||' whitespace? pp_and_expression
 8     ;
 9 
10 pp_and_expression
11     : pp_equality_expression
12     | pp_and_expression whitespace? '&&' whitespace? pp_equality_expression
13     ;
14 
15 pp_equality_expression
16     : pp_unary_expression
17     | pp_equality_expression whitespace? '==' whitespace? pp_unary_expression
18     | pp_equality_expression whitespace? '!=' whitespace? pp_unary_expression
19     ;
20 
21 pp_unary_expression
22     : pp_primary_expression
23     | '!' whitespace? pp_unary_expression
24     ;
25 
26 pp_primary_expression
27     : 'true'
28     | 'false'
29     | conditional_symbol
30     | '(' whitespace? pp_expression whitespace? ')'
31     ;

在预处理表达式中引用时,定义的条件编译符号具有布尔值true,未定义的条件编译符号具有布尔值false

预处理表达式的评估总是产生布尔值。预处理表达式的评估规则与常量表达式(常量表达式的评估规则相同,只是可以引用的唯一用户定义实体是条件编译符号。

声明指令

声明指令用于定义或取消定义条件编译符号。

1 pp_declaration
2     : whitespace? '#' whitespace? 'define' whitespace conditional_symbol pp_new_line
3     | whitespace? '#' whitespace? 'undef' whitespace conditional_symbol pp_new_line
4     ;
5 
6 pp_new_line
7     : whitespace? single_line_comment? new_line
8     ;

#define指令的处理导致给定的条件编译符号被定义,从指令后面的源代码行开始。同样,#undef指令的处理会导致给定的条件编译符号变为未定义,从指令后面的源代码行开始。

源文件中的任何#define#undef指令必须在源文件中的第一个标记标记之前发生否则会发生编译时错误。直观地讲,#define#undef指令必须位于源文件中所有“实代码”。

这个例子:

 1 #define Enterprise
 2 
 3 #if Professional || Enterprise
 4     #define Advanced
 5 #endif
 6 
 7 namespace Megacorp.Data
 8 {
 9     #if Advanced
10     class PivotTable {...}
11     #endif
12 }

是有效的,因为#define指令在namespace源文件中的第一个标记(关键字)之前

以下示例导致编译时错误,因为#define以下实际代码:

1 #define A
2 namespace N
3 {
4     #define B
5     #if B
6     class Class1 {}
7     #endif
8 }

#define可以定义已经定义的条件编译符号,而不#undef对该符号进行任何干预下面的示例定义了条件编译符号A,然后再次定义它。

1 #define A
2 #define A

#undef可以“取消定义”未定义的条件编译符号。下面的示例定义了条件编译符号A,然后将其取消定义两次; 虽然第二个#undef没有效果,但它仍然有效。

1 #define A
2 #undef A
3 #undef A

条件编译指令

条件编译指令用于有条件地包含或排除源文件的各个部分。

 1 pp_conditional
 2     : pp_if_section pp_elif_section* pp_else_section? pp_endif
 3     ;
 4 
 5 pp_if_section
 6     : whitespace? '#' whitespace? 'if' whitespace pp_expression pp_new_line conditional_section?
 7     ;
 8 
 9 pp_elif_section
10     : whitespace? '#' whitespace? 'elif' whitespace pp_expression pp_new_line conditional_section?
11     ;
12 
13 pp_else_section:
14     | whitespace? '#' whitespace? 'else' pp_new_line conditional_section?
15     ;
16 
17 pp_endif
18     : whitespace? '#' whitespace? 'endif' pp_new_line
19     ;
20 
21 conditional_section
22     : input_section
23     | skipped_section
24     ;
25 
26 skipped_section
27     : skipped_section_part+
28     ;
29 
30 skipped_section_part
31     : skipped_characters? new_line
32     | pp_directive
33     ;
34 
35 skipped_characters
36     : whitespace? not_number_sign input_character*
37     ;
38 
39 not_number_sign
40     : '<Any input_character except #>'
41     ;

如语法所示,条件编译指令必须按顺序编写,包括#if指令,零个或多个#elif指令,零个或一个#else指令以及#endif指令。指令之间是源代码的条件部分。每个部分由前一个指令控制。条件部分本身可以包含嵌套的条件编译指令,前提是这些指令形成完整集。

pp_conditional选择含有的至多一个conditional_section S表示正常词法处理:

  • 所述pp_expression的s- #if#elif指令,以便进行计算,直到一个产量true如果表达式产生true则选择相应指令conditional_section
  • 如果所有pp_expression都产生false,并且如果#else存在指令,则选择指令conditional_section#else
  • 否则,不选择conditional_section

选定的conditional_section(如果有)将作为普通input_section处理:该部分中包含的源代码必须符合词法语法; 令牌是从该部分的源代码生成的; 本节中的预处理指令具有规定的效果。

剩下的conditional_section(如果有的话)被处理为skipped_section:除了预处理指令之外,本节中的源代码不需要遵循词法语法; 该部分的源代码不生成令牌; 并且该部分中的预处理指令必须在词法上正确,但不以其他方式处理。内的conditional_section正被处理为skipped_section,任何嵌套conditional_section(包含在嵌套小号#if... #endif#region... #endregion构建体)也被处理为skipped_section秒。

以下示例说明了条件编译指令如何嵌套:

 1 #define Debug       // Debugging on
 2 #undef Trace        // Tracing off
 3 
 4 class PurchaseTransaction
 5 {
 6     void Commit() {
 7         #if Debug
 8             CheckConsistency();
 9             #if Trace
10                 WriteToLog(this.ToString());
11             #endif
12         #endif
13         CommitHelper();
14     }
15 }

除预处理指令外,跳过的源代码不受词法分析的限制。例如,尽管该#else部分中有未确定的评论,但以下内容仍然有效

 1 #define Debug        // Debugging on
 2 
 3 class PurchaseTransaction
 4 {
 5     void Commit() {
 6         #if Debug
 7             CheckConsistency();
 8         #else
 9             /* Do something else
10         #endif
11     }
12 }

但请注意,即使在源代码的跳过部分中,预处理指令也必须在词法上正确。

当预处理指令出现在多行输入元素中时,不会处理它们。例如,程序:

 1 class Hello
 2 {
 3     static void Main() {
 4         System.Console.WriteLine(@"hello, 
 5 #if Debug
 6         world
 7 #else
 8         Nebraska
 9 #endif
10         ");
11     }
12 }

结果输出:

1 hello,
2 #if Debug
3         world
4 #else
5         Nebraska
6 #endif

在特殊情况下,处理的预处理指令集可能取决于pp_expression的评估这个例子:

1 #if X
2     /*
3 #else
4     /* */ class Q { }
5 #endif

class Q { }无论是否X定义始终生成相同的令牌流(如果X已定义,则由于多行注释,唯一处理的指令是#if#endif如果X是不确定的,则这三个指令(#if#else#endif)是指令集的组成部分。

诊断指令

诊断指令用于显式生成以与其他编译时错误和警告相同的方式报告的错误和警告消息。

1 pp_diagnostic
2     : whitespace? '#' whitespace? 'error' pp_message
3     | whitespace? '#' whitespace? 'warning' pp_message
4     ;
5 
6 pp_message
7     : new_line
8     | whitespace input_character* new_line
9     ;

这个例子:

1 #warning Code review needed before check-in
2 
3 #if Debug && Retail
4     #error A build can't be both debug and retail
5 #endif
6 
7 class Test {...}

总是产生一个警告(“登记前需要进行代码审查”),并且如果条件符号DebugRetail两者都被定义,则产生编译时错误(“构建不能同时是调试和零售”)请注意,pp_message可以包含任意文本; 具体而言,它不需要包含格式良好的标记,如单词中的单引号所示can't

区域指令

region指令用于显式标记源代码区域。

 1 pp_region
 2     : pp_start_region conditional_section? pp_end_region
 3     ;
 4 
 5 pp_start_region
 6     : whitespace? '#' whitespace? 'region' pp_message
 7     ;
 8 
 9 pp_end_region
10     : whitespace? '#' whitespace? 'endregion' pp_message
11     ;

区域没有语义含义; 区域旨在供程序员或自动化工具用于标记源代码的一部分。#region#endregion指令中指定的消息同样没有语义含义; 它只是用来识别该地区。匹配#region#endregion指令可能有不同的pp_message

一个地区的词汇处理:

1 #region
2 ...
3 #endregion

完全对应于表单的条件编译指令的词法处理:

1 #if true
2 ...
3 #endif

行指令

行指令可用于更改编译器在输出中报告的行号和源文件名,例如警告和错误,以及调用者信息属性(调用者信息属性)使用的行号。

行指令最常用于从其他文本输入生成C#源代码的元编程工具。

 1 pp_line
 2     : whitespace? '#' whitespace? 'line' whitespace line_indicator pp_new_line
 3     ;
 4 
 5 line_indicator
 6     : decimal_digit+ whitespace file_name
 7     | decimal_digit+
 8     | 'default'
 9     | 'hidden'
10     ;
11 
12 file_name
13     : '"' file_name_character+ '"'
14     ;
15 
16 file_name_character
17     : '<Any input_character except ">'
18     ;

如果没有#line指令,编译器会在其输出中报告真行号和源文件名。当处理#line指令,其包括一个line_indicator不是default,编译器的指令之后的行视为具有给定行号(和文件名,如果指定)。

一个#line default指令消除前面所有#line指令的影响。编译器报告后续行的真#line实行信息,就像没有处理任何指令一样。

一个#line hidden指令对错误信息中报告的文件和行号没有影响,但对源代码级调试。在调试时,#line hidden指令和后续#line指令(不是#line hidden之间的所有行都没有行号信息。在调试器中单步执行代码时,将完全跳过这些行。

请注意,file_name与常规字符串文字的不同之处在于不处理转义字符; “ \”字符只是在file_name中指定一个普通的反斜杠字符

Pragma指令

#pragma预处理指令用于指定可选的上下文信息的编译器。#pragma指令中提供的信息永远不会改变程序语义。

1 pp_pragma
2     : whitespace? '#' whitespace? 'pragma' whitespace pragma_body pp_new_line
3     ;
4 
5 pragma_body
6     : pragma_warning_body
7     ;

C#提供#pragma了控制编译器警告的指令。该语言的未来版本可能包括其他#pragma指令。为确保与其他C#编译器的互操作性,Microsoft C#编译器不会为未知#pragma指令发出编译错误但是,这样的指令会产生警告。

语用警告

#pragma warning指令用于在编译后续程序文本期间禁用或恢复所有或一组特定的警告消息。

 1 pragma_warning_body
 2     : 'warning' whitespace warning_action
 3     | 'warning' whitespace warning_action whitespace warning_list
 4     ;
 5 
 6 warning_action
 7     : 'disable'
 8     | 'restore'
 9     ;
10 
11 warning_list
12     : decimal_digit+ (whitespace? ',' whitespace? decimal_digit+)*
13     ;

一个#pragma warning省略了警告列表中的指令将影响所有警告。一个#pragma warning指令中包含警告列表只影响在列表中指定的警告。

一个#pragma warning disable指令禁止所有或给定的警告。

一个#pragma warning restore指令恢复所有或给定的警告,这是有效的编译单元开始的状态。请注意,如果在外部禁用了特定警告,则#pragma warning restore无论是对于所有警告还是特定警告,都不会重新启用该警告。

以下示例显示了使用#pragma warningMicrosoft C#编译器中的警告编号临时禁用引用过时成员时报告的警告。

 1 using System;
 2 
 3 class Program
 4 {
 5     [Obsolete]
 6     static void Foo() {}
 7 
 8     static void Main() {
 9 #pragma warning disable 612
10     Foo();
11 #pragma warning restore 612
12     }
13 }

 

posted @ 2018-09-28 20:11  为敢技术  阅读(519)  评论(0编辑  收藏  举报