6 Lexikální struktura
6.1 Programy
Program jazyka C# se skládá z jednoho nebo více zdrojových souborů, označovaných formálně jako kompilační jednotky (§14.2). I když kompilační jednotka může mít 1:1 korespondenci se souborem v systému souborů, taková korespondence se nevyžaduje.
Koncepčně řečeno, program je zkompilován pomocí tří kroků:
- Transformace, která převádí soubor z určitého znakového schématu a schématu kódování na posloupnost znaků Unicode.
- Lexikální analýza, která překládá datový proud vstupních znaků Unicode do datového proudu tokenů.
- Syntaktická analýza, která překládá datový proud tokenů do spustitelného kódu.
Odpovídající implementace přijímají kompilační jednotky Unicode kódované pomocí formátu kódování UTF-8 (jak je definováno standardem Unicode) a transformují je do posloupnosti znaků Unicode. Implementace se můžou rozhodnout přijímat a transformovat další schémata kódování znaků (například UTF-16, UTF-32 nebo jiná mapování znaků než Unicode).
Poznámka: Zpracování znaku Unicode NULL (U+0000) je definováno implementací. Důrazně doporučujeme, aby vývojáři nepoužívat tento znak ve zdrojovém kódu kvůli přenositelnosti i čitelnosti. Pokud je znak požadován v rámci znaku nebo řetězcového literálu, řídicí sekvence
\0
nebo\u0000
mohou být použity místo toho. koncová poznámka
Poznámka: Je nad rámec této specifikace definovat, jak může být soubor používající jinou reprezentaci znaků než Unicode transformován do posloupnosti znaků Unicode. Během takové transformace se však doporučuje přeložit obvyklý znak oddělující řádek (nebo sekvenci) v jiné znakové sadě na dvouznakovou sekvenci skládající se ze znaku U+000D (Unicode) a znaku u+000A. Ve většině případů tato transformace nebude mít žádné viditelné účinky; bude však mít vliv na interpretaci doslovných řetězcových literálů (§6.4.5.6). Účelem tohoto doporučení je umožnit doslovné řetězcové literály vytvořit stejnou posloupnost znaků, když se jeho kompilační jednotka přesune mezi systémy, které podporují různé znakové sady, zejména ty, které používají různé sekvence znaků pro oddělení řádků. koncová poznámka
6.2 Gramatiky
6.2.1 Obecné
Tato specifikace představuje syntaxi programovacího jazyka C# pomocí dvou gramatik. Lexikální gramatika (§6.2.3) definuje, jak se znaky Unicode kombinují a tvoří ukončovací znaky řádků, prázdné znaky, komentáře, tokeny a direktivy předběžného zpracování. Syntaktická gramatika (§6.2.4) definuje, jak se tokeny vyplývající z lexikální gramatiky kombinují a tvoří programy jazyka C#.
Všechny terminálové znaky se považují za odpovídající znak Unicode z rozsahu U+0020 až U+007F, a ne jako všechny podobné znaky z jiných oblastí znaků Unicode.
6.2.2 Zápis gramatiky
Lexikální a syntaktické gramatiky jsou uvedeny ve formě Extended Backus-Naur nástroje ANTLR.
I když se používá zápis ANTLR, tato specifikace neobsahuje úplnou "referenční gramatiku" připravenou pro ANTLR pro jazyk C#; psaní lexeru a analyzátoru, ať už ručně, nebo pomocí nástroje, jako je ANTLR, je mimo rozsah specifikace jazyka. Při této kvalifikaci se tato specifikace pokusí minimalizovat mezeru mezi zadanou gramatikou a požadovanou k vytvoření lexeru a analyzátoru v ANTLR.
ANTLR rozlišuje mezi lexikálním a syntaktickým analyzátorem anTLR, gramatikami ve svém zápisu tím, že začíná lexikálními pravidly velkým písmenem a analyzátorem pravidel s malým písmenem.
Poznámka: Lexikální gramatika jazyka C# (§6.2.3) a syntaktická gramatika (§6.2.4) nejsou přesně v souladu s rozdělením ANTLR do lexikálních a parserových grammerů. Tato malá neshoda znamená, že se při zadávání lexikální gramatiky jazyka C# používají některá pravidla analyzátoru ANTLR. koncová poznámka
6.2.3 Lexikální gramatika
Lexikální gramatika jazyka C# je uvedena v §6.3, §6.4 a §6.5. Terminálové symboly lexikální gramatiky jsou znaky znakové sady Unicode a lexikální gramatika určuje, jak se znaky kombinují s tvarem tokenů (§6.4), prázdné znaky (§6.3.4), komentáře (§6.3.3) a direktivy předběžného zpracování (§6.5).
Mnoho symbolů terminálu syntaktické gramatiky není definováno explicitně jako tokeny v lexikální gramatikě. Spíše je výhodou chování ANTLR, že literální řetězce v gramatikě jsou extrahovány jako implicitní lexikální tokeny; to umožňuje, aby klíčová slova, operátory atd. byly reprezentovány v gramatikě jejich literálovým vyjádřením, nikoli názvem tokenu.
Každá kompilační jednotka v programu jazyka C# musí odpovídat vstupní výrobě lexikální gramatiky (§6.3.1).
6.2.4 Syntaktická gramatika
Syntaktická gramatika jazyka C# je uvedena v klauzulích, dílčích jazycích a přílohách, které následují za tímto podklíčem. Symboly terminálu syntaktické gramatiky jsou tokeny definované explicitně lexikální gramatikou a implicitně literálními řetězci v samotné gramatikě (§6.2.3). Syntaktická gramatika určuje, jak se tokeny kombinují do formulářů programů jazyka C#.
Každá kompilační jednotka v programu jazyka C# musí odpovídat compilation_unit produkce (§14.2) syntaktické gramatiky.
6.2.5 Gramatické nejednoznačnosti
Produkce pro simple_name (§12.8.4) a member_access (§12.8.7) mohou vést k nejednoznačnostem gramatiky pro výrazy.
Příklad: Příkaz:
F(G<A, B>(7));
lze interpretovat jako volání
F
se dvěma argumentyG < A
aB > (7)
. Alternativně je možné ji interpretovat jako voláníF
s jedním argumentem, což je volání obecné metodyG
se dvěma argumenty typu a jedním pravidelným argumentem.end example
Je-li možné analyzovat posloupnost tokenů (v kontextu) jako simple_name (§12.8.4), member_access (§12.8.7) nebo pointer_member_access (§23.6.3) končící na type_argument_list (§8.4.2), je token bezprostředně po závěrečném tokenu prozkoumán, aby se zjistilo, zda je>
- jeden z
( ) ] } : ; , . ? == != | ^ && || & [
; nebo - Jeden z relačních operátorů
< <= >= is as
; nebo - Kontextové klíčové slovo dotazu zobrazené uvnitř výrazu dotazu; nebo
- V určitých kontextech se identifikátor považuje za nejednoznačný token. Tyto kontexty jsou tam, kde posloupnost tokenů, které jsou nejednoznačné, bezprostředně předchází některému z klíčových slov
is
,case
neboout
dojde při analýze prvního prvku literálu řazené kolekce členů (v takovém případě jsou tokeny předcházející(
nebo:
a identifikátor následuje,
) nebo další prvek literálu řazené kolekce členů.
Pokud je mezi tímto seznamem nebo identifikátorem v tomto kontextu následující token, type_argument_list se zachovají jako součást simple_name, member_access nebo pointer_member-access a všechny další možné analýzy posloupnosti tokenů se zahodí. V opačném případě se type_argument_list nepovažuje za součást simple_name, member_access nebo pointer_member_access, i když neexistuje žádná další možná analýza posloupnosti tokenů.
Poznámka: Tato pravidla se nepoužijí při analýze type_argument_list v namespace_or_type_name (§7.8). koncová poznámka
Příklad: Příkaz:
F(G<A, B>(7));
bude podle tohoto pravidla interpretována jako volání
F
s jedním argumentem, což je volání obecné metodyG
se dvěma argumenty typu a jedním pravidelným argumentem. PříkazyF(G<A, B>7); F(G<A, B>>7);
každý z nich bude interpretován jako volání
F
se dvěma argumenty. Příkazx = F<A> + y;
bude interpretována jako operátor menší než, operátor větší než a unární operátor plus, jako by byl příkaz napsán
x = (F < A) > (+y)
, místo jako simple_name s type_argument_list následovaný binárním operátorem plus. V příkazux = y is C<T> && z;
tokeny
C<T>
se interpretují jako namespace_or_type_name s type_argument_list kvůli přítomnosti nejednoznačného tokenu&&
za type_argument_list.Výraz
(A < B, C > D)
je řazená kolekce členů se dvěma prvky, z nichž každý porovnává.Výraz
(A<B,C> D, E)
je řazená kolekce členů se dvěma prvky, z nichž první je výraz deklarace.Vyvolání
M(A < B, C > D, E)
má tři argumenty.Vyvolání
M(out A<B,C> D, E)
má dva argumenty, z nichž první jeout
deklarace.
e is A<B> C
Výraz používá vzor deklarace.Popisek
case A<B> C:
případu používá vzor deklarace.end example
Při uznání relational_expression (§12.12.1is
6.3 Lexikální analýza
6.3.1 Obecné
Z důvodu usnadnění definuje lexikální gramatika a odkazuje na následující pojmenované tokeny lexeru:
DEFAULT : 'default' ;
NULL : 'null' ;
TRUE : 'true' ;
FALSE : 'false' ;
ASTERISK : '*' ;
SLASH : '/' ;
I když se jedná o pravidla lexeru, jsou tyto názvy napsané ve všech velkých písmenech, aby se odlišily od běžných názvů pravidel lexeru.
Poznámka: Tato pravidla pohodlí jsou výjimky z obvyklé praxe nezadávat explicitní názvy tokenů pro tokeny definované literálovými řetězci. koncová poznámka
Vstupní produkce definuje lexikální strukturu kompilační jednotky jazyka C#.
input
: input_section?
;
input_section
: input_section_part+
;
input_section_part
: input_element* New_Line
| PP_Directive
;
input_element
: Whitespace
| Comment
| token
;
Poznámka: Výše uvedená gramatika je popsaná pravidly analýzy ANTLR, definuje lexikální strukturu kompilační jednotky jazyka C# a ne lexikální tokeny. koncová poznámka
Pět základních prvků tvoří lexikální strukturu kompilační jednotky jazyka C#: Ukončovací čáry (§6.3.2), prázdné znaky (§6.3.4), komentáře (§6.3.3), tokeny (§6.4) a direktivy předběžného zpracování (§6.5). Z těchto základních prvků jsou v syntaktické gramatikě programu jazyka C# významné pouze tokeny (§6.2.4).
Lexikální zpracování kompilační jednotky jazyka C# se skládá z omezení souboru do posloupnosti tokenů, které se stanou vstupem syntaktické analýzy. Ukončovací znaky, prázdné znaky a komentáře mohou sloužit k oddělení tokenů a direktivy předběžného zpracování mohou způsobit vynechání oddílů kompilační jednotky, ale jinak tyto lexikální prvky nemají žádný vliv na syntaktickou strukturu programu jazyka C#.
Když několik lexikálních gramatických produkce odpovídá sekvenci znaků v kompilační jednotce, lexikální zpracování vždy tvoří nejdelší možný lexikální prvek.
Příklad: Sekvence
//
znaků je zpracována jako začátek jednořádkového komentáře, protože lexikální prvek je delší než jeden/
token. end example
Některé tokeny jsou definovány sadou lexikálních pravidel; hlavní pravidlo a jedno nebo více dílčích pravidel. Druhá se označí v gramatikě fragment
, která označuje, že pravidlo definuje část jiného tokenu. Pravidla fragmentu nejsou považována za řazení lexikálních pravidel shora dolů.
Poznámka: V ANTLR
fragment
je klíčové slovo, které vytváří stejné chování definované zde. koncová poznámka
6.3.2 Ukončovací čáry
Ukončovací znaky rozdělují znaky kompilační jednotky jazyka C# na řádky.
New_Line
: New_Line_Character
| '\u000D\u000A' // carriage return, line feed
;
Kvůli kompatibilitě s nástroji pro úpravy zdrojového kódu, které přidávají značky koncového souboru a umožňují zobrazení kompilační jednotky jako posloupnost správně ukončených řádků, se pro každou kompilační jednotku v programu jazyka C# použijí následující transformace:
- Pokud je posledním znakem kompilační jednotky znak Control-Z (U+001A), odstraní se tento znak.
- Znak návratu na začátek řádku (U+000D) se přidá na konec kompilační jednotky, pokud tato jednotka kompilace není prázdná a pokud poslední znak kompilační jednotky není návrat na začátek řádku (U+000D), odřádkování (U+000A), znak dalšího řádku (U+0085), oddělovač řádků (U+2028) nebo oddělovač odstavců (U+2029).
Poznámka: Dodatečný návrat na začátek umožňuje programu ukončit PP_Directive (§6.5), který nemá koncovou New_Line. koncová poznámka
6.3.3 Komentáře
Podporují se dvě formy komentářů: komentáře s oddělovači a jednořádkové komentáře.
Komentář s oddělovači začíná znaky /*
a končí znaky .*/
Komentáře s oddělovači můžou zabírat část řádku, jeden řádek nebo více řádků.
Příklad: Příklad
/* Hello, world program This program writes "hello, world" to the console */ class Hello { static void Main() { System.Console.WriteLine("hello, world"); } }
obsahuje komentář s oddělovači.
end example
Jednořádkový komentář začíná znaky //
a rozšiřuje se na konec řádku.
Příklad: Příklad
// 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" { System.Console.WriteLine("hello, world"); } }
zobrazuje několik jednořádkových komentářů.
end example
Comment
: Single_Line_Comment
| Delimited_Comment
;
fragment Single_Line_Comment
: '//' Input_Character*
;
fragment Input_Character
// anything but New_Line_Character
: ~('\u000D' | '\u000A' | '\u0085' | '\u2028' | '\u2029')
;
fragment New_Line_Character
: '\u000D' // carriage return
| '\u000A' // line feed
| '\u0085' // next line
| '\u2028' // line separator
| '\u2029' // paragraph separator
;
fragment Delimited_Comment
: '/*' Delimited_Comment_Section* ASTERISK+ '/'
;
fragment Delimited_Comment_Section
: SLASH
| ASTERISK* Not_Slash_Or_Asterisk
;
fragment Not_Slash_Or_Asterisk
: ~('/' | '*') // Any except SLASH or ASTERISK
;
Komentáře nejsou vnořené. Sekvence znaků /*
a */
nemá v rámci jednořádkových komentářů žádný speciální význam. To stejné platí se sekvencemi //
a /*
v komentářích s oddělovači.
Komentáře se nezpracují ve znakových a řetězcových literálech.
Poznámka: Tato pravidla musí být interpretována pečlivě. Například v následujícím příkladu je komentář s oddělovači, který začíná před
A
koncem meziB
aC()
. Důvodem je, že// B */ C();
není ve skutečnosti jednořádkový komentář, protože
//
nemá žádný zvláštní význam v rámci odděleného komentáře, a tak*/
má jeho obvyklý zvláštní význam v daném řádku.Stejně tak oddělovaný komentář začínající před
D
koncem předE
. Důvodem je, že"D */ "
ve skutečnosti není řetězcový literál, protože počáteční znak dvojité uvozovky se zobrazí uvnitř komentáře s oddělovači.Užitečným důsledkem
/*
a*/
bez zvláštního významu v rámci jednořádkového komentáře je, že blok řádků zdrojového kódu lze okomentovat tak, že umístíte//
na začátek každého řádku. Obecně platí, že nefunguje umístit/*
před tyto řádky a*/
za ně, protože to správně zapouzdřuje oddělené komentáře v bloku, a obecně může zcela změnit strukturu těchto oddělených komentářů.Příklad kódu:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
koncová poznámka
Single_Line_Comment s a Delimited_Commentmohou být použity jako komentáře k dokumentaci, jak je popsáno v §D.
6.3.4 Prázdné znaky
Prázdné znaky jsou definovány jako libovolný znak se třídou Unicode Zs (která obsahuje znak mezery) a také vodorovný znak tabulátoru, svislý znak tabulátoru a znak informačního kanálu formuláře.
Whitespace
: [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
;
6.4 Tokeny
6.4.1 Obecné
Existuje několik druhů tokenů: identifikátory, klíčová slova, literály, operátory a interpunkční znaky. Prázdné znaky a komentáře nejsou tokeny, ale fungují jako oddělovače tokenů.
token
: identifier
| keyword
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| operator_or_punctuator
;
Poznámka: Toto je pravidlo analyzátoru ANTLR, nedefinuje lexikální token, ale spíše kolekci druhů tokenů. koncová poznámka
Řídicí sekvence znaků Unicode 6.4.2
Řídicí sekvence Unicode představuje bod kódu Unicode. Řídicí sekvence Unicode se zpracovávají v identifikátorech (§6.4.3), literály znaků (§6.4.5.5), normální řetězcové literály (§6.4.5.6) a interpolované regulární řetězcové výrazy (§12.8.3). Řídicí sekvence Unicode není zpracována v žádném jiném umístění (například pro vytvoření operátoru, interpunkční znaménečku nebo klíčového slova).
fragment Unicode_Escape_Sequence
: '\\u' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
| '\\U' Hex_Digit Hex_Digit Hex_Digit Hex_Digit
Hex_Digit Hex_Digit Hex_Digit Hex_Digit
;
Řídicí sekvence znaků Unicode představuje jediný bod kódu Unicode, který je tvořen šestnáctkovým číslem za znaky \u nebo \U. Vzhledem k tomu, že jazyk C# používá 16bitové kódování bodů kódu Unicode ve znakových a řetězcových hodnotách, je bod kódu Unicode v rozsahu U+10000
U+10FFFF
reprezentován pomocí dvou náhradních jednotek kódu Unicode. Výše uvedené U+FFFF
body kódu Unicode nejsou povoleny ve znakových literálech. Výše uvedené U+10FFFF
body kódu Unicode jsou neplatné a nejsou podporovány.
Neprovádí se více překladů. Například řetězcový literál "\u005Cu005C"
je ekvivalentní "\u005C"
spíše než "\"
.
Poznámka: Hodnota
\u005C
Unicode je znak "\
". koncová poznámka
Příklad: Příklad
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) { System.Console.WriteLine(c.ToString()); } } }
zobrazuje několik použití
\u0066
, což je řídicí sekvence písmena "f
". Program je ekvivalentníclass Class1 { static void Test(bool f) { char c = 'f'; if (f) { System.Console.WriteLine(c.ToString()); } } }
end example
6.4.3 Identifikátory
Pravidla pro identifikátory uvedené v tomto dílčím seznamu přesně odpovídají těm, které doporučuje standardní příloha Unicode 15 s tím rozdílem, že podtržítko je povoleno jako počáteční znak (jak je tradiční v programovacím jazyce C), řídicí sekvence Unicode jsou povoleny v identifikátorech a znak "@
" je povolen jako předpona, která umožní použití klíčových slov jako identifikátorů.
identifier
: Simple_Identifier
| contextual_keyword
;
Simple_Identifier
: Available_Identifier
| Escaped_Identifier
;
fragment Available_Identifier
// excluding keywords or contextual keywords, see note below
: Basic_Identifier
;
fragment Escaped_Identifier
// Includes keywords and contextual keywords prefixed by '@'.
// See note below.
: '@' Basic_Identifier
;
fragment Basic_Identifier
: Identifier_Start_Character Identifier_Part_Character*
;
fragment Identifier_Start_Character
: Letter_Character
| Underscore_Character
;
fragment Underscore_Character
: '_' // underscore
| '\\u005' [fF] // Unicode_Escape_Sequence for underscore
| '\\U0000005' [fF] // Unicode_Escape_Sequence for underscore
;
fragment Identifier_Part_Character
: Letter_Character
| Decimal_Digit_Character
| Connecting_Character
| Combining_Character
| Formatting_Character
;
fragment Letter_Character
// Category Letter, all subcategories; category Number, subcategory letter.
: [\p{L}\p{Nl}]
// Only escapes for categories L & Nl allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Combining_Character
// Category Mark, subcategories non-spacing and spacing combining.
: [\p{Mn}\p{Mc}]
// Only escapes for categories Mn & Mc allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Decimal_Digit_Character
// Category Number, subcategory decimal digit.
: [\p{Nd}]
// Only escapes for category Nd allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Connecting_Character
// Category Punctuation, subcategory connector.
: [\p{Pc}]
// Only escapes for category Pc allowed. See note below.
| Unicode_Escape_Sequence
;
fragment Formatting_Character
// Category Other, subcategory format.
: [\p{Cf}]
// Only escapes for category Cf allowed, see note below.
| Unicode_Escape_Sequence
;
Poznámka:
- Informace o výše uvedených třídách znaků Unicode naleznete v tématu Standard Unicode.
- Fragment Available_Identifier vyžaduje vyloučení klíčových slov a kontextových klíčových slov. Pokud se gramatika v této specifikaci zpracuje pomocí ANTLR, bude toto vyloučení automaticky zpracováno sémantikou ANTLR:
- Klíčová slova a kontextová klíčová slova se vyskytují v gramatikě jako literálové řetězce.
- ANTLR vytvoří implicitní pravidla lexikálního tokenu, která se vytvoří z těchto literálových řetězců.
- ANTLR považuje tato implicitní pravidla před explicitními lexikálními pravidly v gramatikě.
- Fragment Available_Identifier proto nebude odpovídat klíčovým slovům ani kontextovým klíčovým slovům jako lexikálním pravidlům, která jsou před nimi uvedena.
- Fragment Escaped_Identifier zahrnuje řídicí klíčová slova a kontextová klíčová slova, protože jsou součástí delšího tokenu počínaje
@
a lexikálním zpracováním vždy tvoří nejdelší možný lexikální prvek (§6.3.1).- Jak implementace vynucuje omezení povolených Unicode_Escape_Sequence hodnot je problém implementace.
koncová poznámka
Příklad: Příklady platných identifikátorů jsou
identifier1
,_identifier2
a@if
. end example
Identifikátor v vyhovujícím programu musí být v kanonickém formátu definovaném normalizačním formulářem Unicode C, jak je definováno standardní přílohou Unicode 15. Chování při výskytu identifikátoru, který není v normalizačním formuláři C, je definováno implementací; diagnostika však není povinná.
Předpona "@
" umožňuje použití klíčových slov jako identifikátorů, což je užitečné při propojení s jinými programovacími jazyky. Znak @
není ve skutečnosti součástí identifikátoru, takže identifikátor se může zobrazit v jiných jazycích jako normální identifikátor bez předpony. Identifikátor s předponou @
se nazývá doslovný identifikátor.
Poznámka: Použití
@
předpony pro identifikátory, které nejsou klíčovými slovy, je povoleno, ale důrazně nedoporučujeme v rámci stylu. koncová poznámka
Příklad: Příklad:
class @class { public static void @static(bool @bool) { if (@bool) { System.Console.WriteLine("true"); } else { System.Console.WriteLine("false"); } } } class Class1 { static void M() { cl\u0061ss.st\u0061tic(true); } }
definuje třídu s názvem "
class
" statickou metodou s názvem "static
", která přebírá parametr s názvem "bool
". Všimněte si, že vzhledem k tomu, že řídicí znaky Unicode nejsou povoleny v klíčových slovech, token "cl\u0061ss
" je identifikátor a je stejný identifikátor jako "@class
".end example
Dva identifikátory jsou považovány za stejné, pokud jsou stejné po použití následujících transformací v pořadí:
- Předpona "
@
", pokud je použita, je odebrána. - Každý Unicode_Escape_Sequence se transformuje na odpovídající znak Unicode.
- Odeberou se všechny Formatting_Character.
Sémantika pojmenovaného _
identifikátoru závisí na kontextu, ve kterém se zobrazuje:
- Může označit pojmenovaný prvek programu, například proměnnou, třídu nebo metodu nebo
- Může znamenat zahození (§ 9.2.9.2).
Identifikátory obsahující dva po sobě jdoucí podtržítka (U+005F
) jsou vyhrazeny pro použití implementací. Pokud je tento identifikátor definovaný, nevyžaduje se žádná diagnostika.
Poznámka: Například implementace může poskytovat rozšířená klíčová slova, která začínají dvěma podtržítky. koncová poznámka
6.4.4 Klíčová slova
Klíčové slovo je posloupnost znaků, která je vyhrazena jako identifikátor, a nelze ji použít jako identifikátor s výjimkou případů, kdy znak předčítá @
.
keyword
: 'abstract' | 'as' | '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' | 'stackalloc' | 'static' | 'string'
| 'struct' | 'switch' | 'this' | 'throw' | TRUE
| 'try' | 'typeof' | 'uint' | 'ulong' | 'unchecked'
| 'unsafe' | 'ushort' | 'using' | 'virtual' | 'void'
| 'volatile' | 'while'
;
Kontextové klíčové slovo@
contextual_keyword
: 'add' | 'alias' | 'ascending' | 'async' | 'await'
| 'by' | 'descending' | 'dynamic' | 'equals' | 'from'
| 'get' | 'global' | 'group' | 'into' | 'join'
| 'let' | 'nameof' | 'on' | 'orderby' | 'partial'
| 'remove' | 'select' | 'set' | 'unmanaged' | 'value'
| 'var' | 'when' | 'where' | 'yield'
;
Poznámka: Klíčové slovo pravidla a contextual_keyword jsou pravidla analyzátoru, protože nezavádějí nové typy tokenů. Všechna klíčová slova a kontextová klíčová slova jsou definována implicitními lexikálními pravidly tak, jak se vyskytují jako literální řetězce v gramatikě (§6.2.3). koncová poznámka
Ve většině případů je syntaktické umístění kontextových klíčových slov takové, že je nelze zaměňovat s běžným použitím identifikátoru. Například v deklaraci get
vlastnosti mají identifikátory set
zvláštní význam (§15.7.3). Identifikátor jiný než get
nebo set
nikdy není v těchto umístěních povolen, takže toto použití není v konfliktu s použitím těchto slov jako identifikátorů.
V některých případech gramatika nestačí k rozlišení použití kontextového klíčového slova od identifikátorů. Ve všech takových případech bude určeno, jak mezi nimi nejednoznačit. Například kontextové klíčové slovo var
v implicitně zadaných deklarací místních proměnných (§13.6.2) může kolidovat s deklarovaným typem volaným var
, v takovém případě má deklarovaný název přednost před použitím identifikátoru jako kontextové klíčové slovo.
Dalším příkladem takové nejednoznačnosti je kontextové klíčové slovo await
(§12.9.8.1), které je považováno za klíčové slovo pouze v případě, že uvnitř metody deklarované async
, ale lze jej použít jako identifikátor jinde.
Stejně jako u klíčových slov lze kontextová klíčová slova použít jako běžná identifikátory tak, že je předponou znaku @
.
Poznámka: Při použití jako kontextová klíčová slova nemohou tyto identifikátory obsahovat Unicode_Escape_Sequences. koncová poznámka
6.4.5 Literály
6.4.5.1 Obecné
Literál (§12.8.2) je reprezentace hodnoty ve zdrojovém kódu.
literal
: boolean_literal
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| null_literal
;
Poznámka: Literál je pravidlo analyzátoru, protože seskupuje jiné druhy tokenů a nezavádí nový druh tokenu. koncová poznámka
6.4.5.2 Logické literály
Existují dvě logické hodnoty literálů: true
a false
.
boolean_literal
: TRUE
| FALSE
;
Poznámka: boolean_literal je pravidlo analyzátoru, protože seskupuje jiné druhy tokenů a nezavádí nový druh tokenu. koncová poznámka
Typ boolean_literal je bool
.
6.4.5.3 Celočíselné literály
Celočíselné literály se používají k zápisu hodnot typů int
, uint
, long
, a ulong
. Celočíselné literály mají tři možné formy: desítkové, šestnáctkové a binární.
Integer_Literal
: Decimal_Integer_Literal
| Hexadecimal_Integer_Literal
| Binary_Integer_Literal
;
fragment Decimal_Integer_Literal
: Decimal_Digit Decorated_Decimal_Digit* Integer_Type_Suffix?
;
fragment Decorated_Decimal_Digit
: '_'* Decimal_Digit
;
fragment Decimal_Digit
: '0'..'9'
;
fragment Integer_Type_Suffix
: 'U' | 'u' | 'L' | 'l' |
'UL' | 'Ul' | 'uL' | 'ul' | 'LU' | 'Lu' | 'lU' | 'lu'
;
fragment Hexadecimal_Integer_Literal
: ('0x' | '0X') Decorated_Hex_Digit+ Integer_Type_Suffix?
;
fragment Decorated_Hex_Digit
: '_'* Hex_Digit
;
fragment Hex_Digit
: '0'..'9' | 'A'..'F' | 'a'..'f'
;
fragment Binary_Integer_Literal
: ('0b' | '0B') Decorated_Binary_Digit+ Integer_Type_Suffix?
;
fragment Decorated_Binary_Digit
: '_'* Binary_Digit
;
fragment Binary_Digit
: '0' | '1'
;
Typ celočíselného literálu je určen takto:
- Pokud literál nemá žádnou příponu, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:
int
, ,uint
long
,ulong
. - Pokud je literál příponou
U
nebou
, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:uint
,ulong
. - Pokud je literál příponou
L
nebol
, má první z těchto typů, ve kterých může být jeho hodnota reprezentována:long
,ulong
. - Pokud je literál příponou
UL
, ,Ul
,uL
ul
,LU
,Lu
lU
, nebolu
, je typuulong
.
Pokud je hodnota reprezentovaná celočíselnou literálem mimo rozsah ulong
typu, dojde k chybě v době kompilace.
Poznámka: Ve stylu je doporučeno, aby
L
se při psaní literálů typul
"" použil místo "long
", protože je snadné zmást písmeno "l
" s číslicí "1
". koncová poznámka
Aby bylo možné zapisovat nejmenší možné int
hodnoty long
jako celočíselné literály, existují následující dvě pravidla:
-
Když Integer_Literal představující hodnotu
2147483648
(2³¹) a žádný Integer_Type_Suffix se nezobrazí jako token bezprostředně za tokenem unárního operátoru minus (§12.9.3), je výsledkem (obou tokenů) konstanta typu int s hodnotou−2147483648
(−2³¹). Ve všech ostatních situacích je takový Integer_Literal typuuint
. -
Pokud Integer_Literal představující hodnotu
9223372036854775808
(2⁶³) a žádný Integer_Type_Suffix nebo Integer_Type_SuffixL
nebol
se zobrazí jako token bezprostředně za tokenem unárního operátoru minus (§12,9.3), je výsledek (obou tokenů) konstantou typulong
s hodnotou−9223372036854775808
(−2⁶³). Ve všech ostatních situacích je takový Integer_Literal typuulong
.
Příklad:
123 // decimal, int 10_543_765Lu // decimal, ulong 1_2__3___4____5 // decimal, int _123 // not a numeric literal; identifier due to leading _ 123_ // invalid; no trailing _allowed 0xFf // hex, int 0X1b_a0_44_fEL // hex, long 0x1ade_3FE1_29AaUL // hex, ulong 0x_abc // hex, int _0x123 // not a numeric literal; identifier due to leading _ 0xabc_ // invalid; no trailing _ allowed 0b101 // binary, int 0B1001_1010u // binary, uint 0b1111_1111_0000UL // binary, ulong 0B__111 // binary, int __0B111 // not a numeric literal; identifier due to leading _ 0B111__ // invalid; no trailing _ allowed
end example
6.4.5.4 Skutečné literály
Skutečné literály se používají k zápisu hodnot typů float
, double
a decimal
.
Real_Literal
: Decimal_Digit Decorated_Decimal_Digit* '.'
Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
| '.' Decimal_Digit Decorated_Decimal_Digit* Exponent_Part? Real_Type_Suffix?
| Decimal_Digit Decorated_Decimal_Digit* Exponent_Part Real_Type_Suffix?
| Decimal_Digit Decorated_Decimal_Digit* Real_Type_Suffix
;
fragment Exponent_Part
: ('e' | 'E') Sign? Decimal_Digit Decorated_Decimal_Digit*
;
fragment Sign
: '+' | '-'
;
fragment Real_Type_Suffix
: 'F' | 'f' | 'D' | 'd' | 'M' | 'm'
;
Pokud není zadán žádný Real_Type_Suffix , typ Real_Literal je double
.
V opačném případě Real_Type_Suffix určuje typ skutečného literálu následujícím způsobem:
- Skutečný literál s příponou
F
typu nebof
je typufloat
.Příklad: Literály
1f
,1.5f
,1e10f
a123.456F
jsou všechny typufloat
. end example - Skutečný literál s příponou
D
typu nebod
je typudouble
.Příklad: Literály
1d
,1.5d
,1e10d
a123.456D
jsou všechny typudouble
. end example - Skutečný literál s příponou
M
typu nebom
je typudecimal
.Příklad: Literály
1m
,1.5m
,1e10m
a123.456M
jsou všechny typudecimal
. end example
Tento literál se převede nadecimal
hodnotu tak, že vezme přesnou hodnotu a v případě potřeby zaokrouhlí na nejbližší reprezentovanou hodnotu pomocí zaokrouhlení banky (§8.3.8). Jakékoli měřítko zjevné v literálu se zachová, pokud se hodnota nezaokrouhlí. Poznámka: Proto literál2.900m
bude analyzován tak, aby formovaldecimal
se znaménkem0
, koeficientem2900
a měřítkem3
. koncová poznámka
Pokud je velikost zadaného literálu příliš velká, aby byla reprezentována v zadaném typu, dojde k chybě v době kompilace.
Poznámka: Konkrétně Real_Literal nikdy nevygeneruje nekonečno s plovoucí desetinou čárkou. Nenulová Real_Literal však může být zaokrouhlená na nulu. koncová poznámka
Hodnota skutečného literálu typu float
nebo double
je určena pomocí režimu IEC 60559 "round to nearest" (zaokrouhlit na nejbližší) se zalomenými na "sudé" (hodnota s nejméně významnou bitovou nulou) a všechny číslice považovány za významné.
Poznámka: V reálném literálu jsou desetinná čísla vždy vyžadována za desetinnou čárkou. Jedná se například
1.3F
o skutečný literál, ale1.F
není. koncová poznámkaPříklad:
1.234_567 // double .3e5f // float 2_345E-2_0 // double 15D // double 19.73M // decimal 1.F // parsed as a member access of F due to non-digit after . 1_.2F // invalid; no trailing _ allowed in integer part 1._234 // parsed as a member access of _234 due to non-digit after . 1.234_ // invalid; no trailing _ allowed in fraction .3e_5F // invalid; no leading _ allowed in exponent .3e5_F // invalid; no trailing _ allowed in exponent
end example
6.4.5.5 Znakové literály
Literál znaku představuje jeden znak a skládá se z znaku v uvozovkách, jako v 'a'
.
Character_Literal
: '\'' Character '\''
;
fragment Character
: Single_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
;
fragment Single_Character
// anything but ', \, and New_Line_Character
: ~['\\\u000D\u000A\u0085\u2028\u2029]
;
fragment Simple_Escape_Sequence
: '\\\'' | '\\"' | '\\\\' | '\\0' | '\\a' | '\\b' |
'\\f' | '\\n' | '\\r' | '\\t' | '\\v'
;
fragment Hexadecimal_Escape_Sequence
: '\\x' Hex_Digit Hex_Digit? Hex_Digit? Hex_Digit?
;
Poznámka: Znak, který následuje za znakem zpětného lomítka (
\
) v znaku, musí být jedním z následujících znaků:'
,"
, ,\
,0
,a
,b
,f
.n
r
t
u
U
x
v
V opačném případě dojde k chybě kompilace. koncová poznámka
Poznámka: Použití
\x
Hexadecimal_Escape_Sequence produkce může být náchylné k chybám a obtížně čitelné z důvodu proměnlivého počtu šestnáctkových číslic za znakem\x
. Například v kódu:string good = "\x9Good text"; string bad = "\x9Bad text";
Může se nejprve zobrazit, že úvodní znak je stejný (
U+0009
znak tabulátoru) v obou řetězcích. Ve skutečnosti druhý řetězec začínáU+9BAD
jako všechny tři písmena ve slově "Bad" jsou platné šestnáctkové číslice. Ve stylu se doporučuje, aby\x
se zabránilo ve prospěch konkrétních řídicích sekvencí (\t
v tomto příkladu) nebo řídicí sekvence s pevnou délkou\u
.koncová poznámka
Šestnáctková řídicí sekvence představuje jednu jednotku kódu Unicode UTF-16 s hodnotou vytvořenou šestnáctkovým číslem za "\x
".
Pokud je hodnota reprezentovaná literálem znaku větší než U+FFFF
, dojde k chybě v době kompilace.
Řídicí sekvence Unicode (§6.4.2) v literálu znaků musí být v rozsahu U+0000
do U+FFFF
.
Jednoduchá řídicí sekvence představuje znak Unicode, jak je popsáno v tabulce níže.
Řídicí sekvence | Název znaku | Bod kódu Unicode |
---|---|---|
\' |
Jednoduchá uvozovka | U+0027 |
\" |
Dvojitá uvozovka | U+0022 |
\\ |
Zpětné lomítko | U+005C |
\0 |
Null | U+0000 |
\a |
Výstrahy | U+0007 |
\b |
Backspace | U+0008 |
\f |
Informační kanál formuláře | U+000C |
\n |
Nový řádek | U+000A |
\r |
Návrat na začátek řádku | U+000D |
\t |
Horizontální tabulátor | U+0009 |
\v |
Vertikální tabulátor | U+000B |
Typ Character_Literal je char
.
6.4.5.6 Řetězcové literály
Jazyk C# podporuje dvě formy řetězcových literálů: běžné řetězcové literály a doslovné řetězcové literály. Běžný řetězcový literál se skládá z nuly nebo více znaků uzavřených v dvojitých uvozovkách, stejně jako v "hello"
, a může obsahovat jak jednoduché řídicí sekvence (například \t
pro znak tabulátoru), tak šestnáctkové a unicode řídicí sekvence.
Doslovný řetězcový literál se skládá ze znaku @
následovaného znakem dvojité uvozovky, nuly nebo více znaků a pravou dvojitou uvozovkou.
Příklad: Jednoduchý příklad je
@"hello"
. end example
Ve doslovné řetězcové literálu se znaky mezi oddělovači interpretují doslovně, přičemž jedinou výjimkou je Quote_Escape_Sequence, která představuje jeden znak s dvojitou uvozovkou. Zejména jednoduché řídicí sekvence a hexadecimální a řídicí sekvence Unicode nejsou zpracovány ve doslovných řetězcových literálech. Doslovný řetězcový literál může obsahovat více řádků.
String_Literal
: Regular_String_Literal
| Verbatim_String_Literal
;
fragment Regular_String_Literal
: '"' Regular_String_Literal_Character* '"'
;
fragment Regular_String_Literal_Character
: Single_Regular_String_Literal_Character
| Simple_Escape_Sequence
| Hexadecimal_Escape_Sequence
| Unicode_Escape_Sequence
;
fragment Single_Regular_String_Literal_Character
// anything but ", \, and New_Line_Character
: ~["\\\u000D\u000A\u0085\u2028\u2029]
;
fragment Verbatim_String_Literal
: '@"' Verbatim_String_Literal_Character* '"'
;
fragment Verbatim_String_Literal_Character
: Single_Verbatim_String_Literal_Character
| Quote_Escape_Sequence
;
fragment Single_Verbatim_String_Literal_Character
: ~["] // anything but quotation mark (U+0022)
;
fragment Quote_Escape_Sequence
: '""'
;
Příklad: Příklad
string a = "Happy birthday, Joel"; // Happy birthday, Joel string b = @"Happy birthday, Joel"; // Happy birthday, Joel 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" to me string f = @"Joe said ""Hello"" to me"; // Joe said "Hello" to me string g = "\\\\server\\share\\file.txt"; // \\server\share\file.txt string h = @"\\server\share\file.txt"; // \\server\share\file.txt string i = "one\r\ntwo\r\nthree"; string j = @"one two three";
zobrazuje různé řetězcové literály. Poslední řetězcový literál ,
j
je doslovný řetězcový literál, který zahrnuje více řádků. Znaky mezi uvozovkami, včetně prázdných znaků, jako jsou znaky nového řádku, se zachovají doslovně a každý pár dvou uvozovek se nahradí jedním takovým znakem.end example
Poznámka: Všechny konce řádků v doslovných řetězcových literálech jsou součástí výsledného řetězce. Pokud jsou přesné znaky používané k vytvoření konců řádků sémanticky relevantní pro aplikaci, všechny nástroje, které překládají konce řádků ve zdrojovém kódu do různých formátů (například mezi "
\n
" a "\r\n
"), změní chování aplikace. Vývojáři by v takových situacích měli být opatrní. koncová poznámka
Poznámka: Protože šestnáctková řídicí sekvence může mít proměnlivý počet šestnáctkových číslic, řetězcový literál
"\x123"
obsahuje jeden znak s šestnáctkovou hodnotou123
. Chcete-li vytvořit řetězec obsahující znak s šestnáctkovou hodnotou12
následovanou znakem3
, mohl by jeden napsat"\x00123"
nebo"\x12"
+"3"
místo toho. koncová poznámka
Typ String_Literal je string
.
Každý řetězcový literál nemusí nutně vést k nové instanci řetězce. Pokud jsou dva nebo více řetězcových literálů, které jsou ekvivalentní podle operátoru rovnosti řetězce (§12.12.8), zobrazí se ve stejném sestavení, tyto řetězcové literály odkazují na stejnou instanci řetězce.
Příklad: Například výstup vytvořený pomocí
class Test { static void Main() { object a = "hello"; object b = "hello"; System.Console.WriteLine(a == b); } }
je
True
to proto, že dva literály odkazují na stejnou instanci řetězce.end example
6.4.5.7 Literál null
null_literal
: NULL
;
Poznámka: null_literal je pravidlo analyzátoru, protože nezavádí nový druh tokenu. koncová poznámka
Null_literal představuje null
hodnotu. Typ nemá, ale lze jej převést na jakýkoli typ odkazu nebo typ hodnoty null prostřednictvím literálového převodu null (§10.2.7).
6.4.6 Operátory a interpunkční znaky
Existuje několik druhů operátorů a interpunkčních znamétek. Operátory se používají ve výrazech k popisu operací zahrnujících jeden nebo více operandů.
Příklad: Výraz
a + b
používá+
operátor k přidání dvou operandůa
ab
. end example
Interpunkční znaky slouží k seskupení a oddělení.
operator_or_punctuator
: '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';'
| '+' | '-' | ASTERISK | SLASH | '%' | '&' | '|' | '^' | '!' | '~'
| '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||'
| '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
;
right_shift
: '>' '>'
;
right_shift_assignment
: '>' '>='
;
Poznámka: right_shift a right_shift_assignment jsou pravidla analyzátoru, protože nezavádějí nový druh tokenu, ale představují sekvenci dvou tokenů. Pravidlo operator_or_punctuator existuje pouze pro popisné účely a nepoužívá se jinde v gramatikě. koncová poznámka
right_shift se skládá ze dvou tokenů >
a >
.
Podobně se right_shift_assignment skládá ze dvou tokenů >
a >=
. Na rozdíl od jiných produkčních prostředí v syntaktické gramatikě nejsou mezi těmito dvěma tokeny v každém z těchto produkčních prostředí povoleny žádné znaky jakéhokoli druhu (ani prázdné znaky). Tyto výroby jsou zpracovávány speciálně za účelem zajištění správného zpracování type_parameter_lists (§15.2.3).
Poznámka: Před přidáním obecných typů do jazyka C#
>>
byly>>=
oba tokeny. Syntaxe obecných typů ale používá<
znaky k>
oddělovači parametrů typu a argumentů typu. Často je žádoucí používat vnořené konstruované typy, napříkladList<Dictionary<string, int>>
. Místo toho, aby programátor oddělil>
mezeru a>
mezeru, změnila se definice dvou operator_or_punctuators. koncová poznámka
6.5 Direktivy předběžného zpracování
6.5.1 Obecné
Direktivy předběžného zpracování poskytují možnost podmíněně přeskočit části jednotek kompilace, hlásit chybové a upozorňující podmínky, vypsat jedinečné oblasti zdrojového kódu a nastavit kontext s možnou hodnotou null.
Poznámka: Výraz "direktivy předběžného zpracování" se používá pouze pro konzistenci s programovacími jazyky C a C++. V jazyce C# neexistuje žádný samostatný krok předběžného zpracování; Direktivy předběžného zpracování se zpracovávají jako součást lexikální fáze analýzy. koncová poznámka
PP_Directive
: PP_Start PP_Kind PP_New_Line
;
fragment PP_Kind
: PP_Declaration
| PP_Conditional
| PP_Line
| PP_Diagnostic
| PP_Region
| PP_Pragma
| PP_Nullable
;
// Only recognised at the beginning of a line
fragment PP_Start
// See note below.
: { getCharPositionInLine() == 0 }? PP_Whitespace? '#' PP_Whitespace?
;
fragment PP_Whitespace
: ( [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
)+
;
fragment PP_New_Line
: PP_Whitespace? Single_Line_Comment? New_Line
;
Poznámka:
- Gramatika preprocesoru definuje jeden lexikální token
PP_Directive
používaný pro všechny direktivy předběžného zpracování. Sémantika každé direktivy předzpracování jsou definována v této specifikaci jazyka, ale ne v tom, jak je implementovat.- Fragment
PP_Start
musí být rozpoznáván pouze na začátku přímky,getCharPositionInLine() == 0
lexikální predikát ANTLR výše naznačuje jeden způsob, jak toho dosáhnout a je informativní pouze, implementace může použít jinou strategii.koncová poznámka
K dispozici jsou následující direktivy předběžného zpracování:
-
#define
a#undef
, které se používají k definování a nedefinu, v uvedeném pořadí, symboly podmíněné kompilace (§6.5.4). -
#if
,#elif
,#else
a#endif
, které se používají ke vynechání podmíněných oddílů zdrojového kódu (§6.5.5). -
#line
, který slouží k řízení čísel řádků vygenerovaných pro chyby a upozornění (§6.5.8). -
#error
, která se používá k vydávání chyb (§6.5.6). -
#region
a#endregion
, které se používají k explicitní označení částí zdrojového kódu (§6.5.7). -
#nullable
, který se používá k určení kontextu s možnou hodnotou null (§6.5.9). -
#pragma
, který slouží k určení volitelných kontextových informací kompilátoru (§6.5.10).
Direktiva předběžného zpracování vždy zabírá samostatný řádek zdrojového kódu a vždy začíná znakem #
a názvem direktivy předběžného zpracování. Před znakem #
a mezi znakem #
a názvem direktivy může dojít k prázdnému znaku.
Zdrojová čára obsahující #define
znak , #undef
, #if
#elif
#else
#endif
#line
, #endregion
, nebo #nullable
direktiva může končit jedním řádkem komentáře. Komentáře s oddělovači ( /* */
styl komentářů) nejsou povoleny na zdrojových řádcích obsahujících direktivy předběžného zpracování.
Direktivy předběžného zpracování nejsou součástí syntaktické gramatiky jazyka C#. Direktivy předběžného zpracování však lze použít k zahrnutí nebo vyloučení sekvencí tokenů a mohou tímto způsobem ovlivnit význam programu jazyka C#.
Příklad: Při kompilaci programu
#define A #undef B class C { #if A void F() {} #else void G() {} #endif #if B void H() {} #else void I() {} #endif }
výsledkem je stejná posloupnost tokenů jako program.
class C { void F() {} void I() {} }
Proto, zatímco lexiky jsou tyto dva programy poměrně odlišné, syntakticky, jsou identické.
end example
6.5.2 Symboly podmíněné kompilace
Funkce podmíněné kompilace poskytované #if
direktivami , #elif
, #else
a #endif
direktivy jsou řízeny výrazy předběžného zpracování (§6.5.3) a symboly podmíněné kompilace.
fragment PP_Conditional_Symbol
// Must not be equal to tokens TRUE or FALSE. See note below.
: Basic_Identifier
;
Poznámka: Jak implementace vynucuje omezení povolené Basic_Identifier hodnoty je problém implementace. koncová poznámka
Dva symboly podmíněné kompilace jsou považovány za stejné, pokud jsou stejné po použití následujících transformací v pořadí:
- Každý Unicode_Escape_Sequence se transformuje na odpovídající znak Unicode.
- Odeberou se všechny Formatting_Characters .
Symbol podmíněné kompilace má dva možné stavy: definované nebo nedefinované. Na začátku lexikálního zpracování kompilační jednotky není definován symbol podmíněné kompilace, pokud ho explicitně nedefinuje externí mechanismus (například možnost kompilátoru příkazového řádku).
#define
Při zpracování direktivy se v této jednotce kompilace definuje symbol podmíněné kompilace pojmenovaný v této direktivě. Symbol zůstane definován, dokud #undef
se nezpracuje direktiva pro stejný symbol nebo dokud se nedosáhne konce kompilační jednotky. Implikací je, že #define
direktivy #undef
v jedné jednotce kompilace nemají žádný vliv na jiné kompilační jednotky ve stejném programu.
Při odkazu ve výrazu předběžného zpracování (§6.5.3) má definovaný symbol podmíněné kompilace logickou hodnotu true
a nedefinovaný symbol podmíněné kompilace má logickou hodnotu false
. Před odkazem na symboly předběžného zpracování není nutné explicitně deklarovat symboly podmíněné kompilace. Místo toho jsou nedefinované symboly jednoduše nedefinované a mají tedy hodnotu false
.
Obor názvů pro symboly podmíněné kompilace je odlišný a oddělený od všech ostatních pojmenovaných entit v programu jazyka C#. Na symboly podmíněné kompilace lze odkazovat pouze ve #define
direktivách a #undef
ve výrazech před zpracováním.
6.5.3 Výrazy předběžného zpracování
Výrazy před zpracováním mohou nastat v #if
direktivách a #elif
direktivách.
!
Operátory (pouze předpona logická negace), ==
, !=
, &&
a ||
jsou povoleny v předzpracovacích výrazech a závorky lze použít pro seskupení.
fragment PP_Expression
: PP_Whitespace? PP_Or_Expression PP_Whitespace?
;
fragment PP_Or_Expression
: PP_And_Expression (PP_Whitespace? '||' PP_Whitespace? PP_And_Expression)*
;
fragment PP_And_Expression
: PP_Equality_Expression (PP_Whitespace? '&&' PP_Whitespace?
PP_Equality_Expression)*
;
fragment PP_Equality_Expression
: PP_Unary_Expression (PP_Whitespace? ('==' | '!=') PP_Whitespace?
PP_Unary_Expression)*
;
fragment PP_Unary_Expression
: PP_Primary_Expression
| '!' PP_Whitespace? PP_Unary_Expression
;
fragment PP_Primary_Expression
: TRUE
| FALSE
| PP_Conditional_Symbol
| '(' PP_Whitespace? PP_Expression PP_Whitespace? ')'
;
Při odkazu ve výrazu předběžného zpracování má definovaný symbol podmíněné kompilace logickou hodnotu true
a nedefinovaný symbol podmíněné kompilace má logickou hodnotu false
.
Vyhodnocení výrazu předběžného zpracování vždy vrátí logickou hodnotu. Pravidla vyhodnocení výrazu předběžného zpracování jsou stejná jako pravidla pro konstantní výraz (§12.23), s tím rozdílem, že jedinými uživatelem definovanými entitami, na které lze odkazovat, jsou symboly podmíněné kompilace.
6.5.4 Direktivy definice
Direktivy definice slouží k definování nebo nedefinování symbolů podmíněné kompilace.
fragment PP_Declaration
: 'define' PP_Whitespace PP_Conditional_Symbol
| 'undef' PP_Whitespace PP_Conditional_Symbol
;
Zpracování #define
direktivy způsobí, že se daný symbol podmíněné kompilace definuje, počínaje zdrojovým řádkem, který následuje za direktivou. Stejně tak zpracování #undef
direktivy způsobí, že se daný symbol podmíněné kompilace stane nedefinovaným, počínaje zdrojovým řádkem, který následuje za direktivou.
Všechny #define
a #undef
direktivy v kompilační jednotce se před prvním tokenem (§6.4) v kompilační jednotce; jinak dojde k chybě v době kompilace. Intuitivně #define
a #undef
direktivy musí předcházet jakémukoli "skutečnému kódu" v kompilační jednotce.
Příklad: Příklad:
#define Enterprise #if Professional || Enterprise #define Advanced #endif namespace Megacorp.Data { #if Advanced class PivotTable {...} #endif }
je platný, protože
#define
direktivy předchází prvnímu tokenu (namespace
klíčovému slovu) v jednotce kompilace.end example
Příklad: Následující příklad má za následek chybu v době kompilace, protože #define se řídí skutečným kódem:
#define A namespace N { #define B #if B class Class1 {} #endif }
end example
Znak #define
podmíněné kompilace, který je již definován, může definovat bez jakéhokoli zásahu #undef
pro tento symbol.
Příklad: Následující příklad definuje symbol podmíněné kompilace A a pak ho znovu definuje.
#define A #define A
U kompilátorů, které umožňují definovat symboly podmíněné kompilace jako možnosti kompilace, je alternativním způsobem, jak takové předefinování nastat, definovat symbol jako možnost kompilátoru i ve zdroji.
end example
Může #undef
"zrušit definici" symbol podmíněné kompilace, který není definován.
Příklad: Následující příklad definuje symbol
A
podmíněné kompilace a pak ho dvakrát nedefinuje, i když druhý#undef
nemá žádný vliv, je stále platný.#define A #undef A #undef A
end example
6.5.5 Direktivy podmíněné kompilace
Direktivy podmíněné kompilace slouží k podmíněnému zahrnutí nebo vyloučení částí kompilační jednotky.
fragment PP_Conditional
: PP_If_Section
| PP_Elif_Section
| PP_Else_Section
| PP_Endif
;
fragment PP_If_Section
: 'if' PP_Whitespace PP_Expression
;
fragment PP_Elif_Section
: 'elif' PP_Whitespace PP_Expression
;
fragment PP_Else_Section
: 'else'
;
fragment PP_Endif
: 'endif'
;
Direktivy podmíněné kompilace se zapisují ve skupinách skládajících se z pořadí, #if
směrnice, nuly nebo více #elif
direktiv, nuly nebo jedné #else
směrnice a #endif
směrnice. Mezi direktivami jsou podmíněné oddíly zdrojového kódu. Každá část je řízena bezprostředně předcházející direktivou. Podmíněný oddíl může sám obsahovat vnořené direktivy podmíněné kompilace za předpokladu, že tyto direktivy tvoří kompletní skupiny.
Pro normální lexikální zpracování je vybrána maximálně jedna z obsažených podmíněných oddílů:
- PP_Expression
#if
s direktiv a#elif
direktiv se vyhodnocují v pořadí, dokud se nezískátrue
. Pokud se výraz zobrazítrue
, je vybrán podmíněný oddíl za odpovídající direktivou. - Pokud jsou všechny
#else
. - Jinak není vybraný žádný podmíněný oddíl.
Vybraný podmíněný oddíl, pokud existuje, je zpracován jako normální input_section: zdrojový kód obsažený v oddílu se řídí lexikální gramatikou; tokeny jsou generovány ze zdrojového kódu v oddílu; a direktivy předběžného zpracování v oddílu mají předepsané účinky.
Všechny zbývající podmíněné oddíly se přeskočí a ze zdrojového kódu se negenerují žádné tokeny s výjimkou direktiv před zpracováním. Proto může být vynechán zdrojový kód s výjimkou direktiv před zpracováním, lexikální nesprávná. Přeskočené direktivy předběžného zpracování jsou lexicky správné, ale jinak se nezpracují. V rámci podmíněného oddílu, který se přeskočí všechny vnořené podmíněné oddíly (obsažené v vnořených #if...#endif
konstruktech), se také přeskočí.
Poznámka: Výše uvedená gramatika nezachycuje povolení, že podmíněné oddíly mezi direktivami předběžného zpracování mohou být špatně formátovány lexicky. Gramatika proto není připravená na ANTLR, protože podporuje pouze lexicky správný vstup. koncová poznámka
Příklad: Následující příklad ukazuje, jak mohou direktivy podmíněné kompilace vnořit:
#define Debug // Debugging on #undef Trace // Tracing off class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #if Trace WriteToLog(this.ToString()); #endif #endif CommitHelper(); } ... }
S výjimkou direktiv předběžného zpracování nepodléhá přeskočený zdrojový kód lexikální analýze. Toto je například platné navzdory neukončenému komentáři v oddílu
#else
:#define Debug // Debugging on class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #else /* Do something else #endif } ... }
Všimněte si však, že direktivy předběžného zpracování musí být lexicky správné i v přeskočených oddílech zdrojového kódu.
Direktivy předběžného zpracování se nezpracují, když se zobrazí uvnitř víceřádkových vstupních prvků. Například program:
class Hello { static void Main() { System.Console.WriteLine(@"hello, #if Debug world #else Nebraska #endif "); } }
výsledky ve výstupu:
hello, #if Debug world #else Nebraska #endif
Ve zvláštních případech může sada direktiv před zpracováním, které se zpracovávají, záviset na vyhodnocení pp_expression. Příklad:
#if X /* #else /* */ class Q { } #endif
vždy vytvoří stejný datový proud tokenu (
class
Q
{
}
), bez ohledu na to, zda je definován nebo neníX
definován. PokudX
je definována, jediné zpracované direktivy jsou#if
a#endif
vzhledem k víceřádkovému komentáři. PokudX
není definována, jsou tři direktivy (#if
,#else
,#endif
) součástí sady direktiv.end example
6.5.6 Diagnostické direktivy
Diagnostické direktivy slouží ke generování explicitně chybových a upozorňujících zpráv, které jsou hlášeny stejným způsobem jako jiné chyby a upozornění v době kompilace.
fragment PP_Diagnostic
: 'error' PP_Message?
| 'warning' PP_Message?
;
fragment PP_Message
: PP_Whitespace Input_Character*
;
Příklad: Příklad
#if Debug && Retail #error A build can't be both debug and retail #endif class Test {...}
vytvoří chybu v době kompilace (sestavení nemůže být laděné i maloobchodní), pokud jsou definované symboly
Debug
podmíněné kompilace.Retail
Všimněte si, že PP_Message může obsahovat libovolný text; konkrétně nemusí obsahovat správně formátované tokeny, jak je znázorněno jediným uvozovkou ve slověcan't
.end example
6.5.7 Direktivy oblastí
Direktivy oblasti se používají k označení explicitních oblastí zdrojového kódu.
fragment PP_Region
: PP_Start_Region
| PP_End_Region
;
fragment PP_Start_Region
: 'region' PP_Message?
;
fragment PP_End_Region
: 'endregion' PP_Message?
;
K oblasti není připojen žádný sémantický význam; oblasti jsou určeny pro použití programátorem nebo automatizovanými nástroji k označení části zdrojového kódu. Musí existovat jedna #endregion
směrnice odpovídající každé #region
směrnici. Zpráva zadaná v #region
nebo #endregion
direktivě podobně nemá žádný sémantický význam; slouží pouze k identifikaci oblasti. Shody #region
a #endregion
direktivy mohou mít různé PP_Messages.
Lexikální zpracování oblasti:
#region
...
#endregion
odpovídá přesně lexikálnímu zpracování direktivy podmíněné kompilace formuláře:
#if true
...
#endif
Poznámka: To znamená, že oblast může obsahovat jednu nebo více
#if
/.../#endif
, nebo může být obsažena s podmíněným oddílem v rámci#if
/.../#endif
; ale oblast se nemůže překrývat pouze s částí#if
/.../#endif
, nebo začít a končit v různých podmíněných oddílech. koncová poznámka
6.5.8 Direktivy řádků
Direktivy řádků lze použít ke změně čísel řádků a názvů jednotek kompilace hlášených kompilátorem ve výstupu, jako jsou upozornění a chyby. Tyto hodnoty se používají také atributy informací o volajícím (§22.5.6).
Poznámka: Direktivy řádků se nejčastěji používají v nástrojích pro metaprogramování, které generují zdrojový kód jazyka C# z jiného textového vstupu. koncová poznámka
fragment PP_Line
: 'line' PP_Whitespace PP_Line_Indicator
;
fragment PP_Line_Indicator
: Decimal_Digit+ PP_Whitespace PP_Compilation_Unit_Name
| Decimal_Digit+
| DEFAULT
| 'hidden'
;
fragment PP_Compilation_Unit_Name
: '"' PP_Compilation_Unit_Name_Character* '"'
;
fragment PP_Compilation_Unit_Name_Character
// Any Input_Character except "
: ~('\u000D' | '\u000A' | '\u0085' | '\u2028' | '\u2029' | '"')
;
Pokud nejsou k dispozici žádné direktivy #line
, kompilátor hlásí ve výstupu čísla řádků a názvy jednotek kompilace. Při zpracování #line
direktivy, která obsahuje PP_Line_Indicator, který není default
, zachází kompilátor s řádkem za direktivou tak, že má dané číslo řádku (a název kompilační jednotky, pokud je zadán).
Maximální povolená hodnota Decimal_Digit+
je definována implementací.
Direktiva #line default
vrátí účinek všech předchozích #line
direktiv. Kompilátor hlásí skutečné informace o řádcích pro následující řádky přesně tak, jako kdyby nebyly zpracovány žádné direktivy #line
.
Směrnice #line hidden
nemá žádný vliv na jednotku kompilace a čísla řádků hlášená v chybových zprávách ani na základě použití CallerLineNumberAttribute
§22.5.6.2). Má mít vliv na ladicí nástroje na úrovni zdroje, aby při ladění neměly všechny řádky mezi direktivou #line hidden
a následnou #line
direktivou (to není #line hidden
) žádné informace o čísle řádku a při procházení kódu se zcela přeskočí.
Poznámka: I když PP_Compilation_Unit_Name může obsahovat text, který vypadá jako řídicí sekvence, takový text není řídicí sekvencí. V tomto kontextu znak '
\
' jednoduše označí běžný znak zpětného lomítka. koncová poznámka
6.5.9 Direktiva s možnou hodnotou Null
Direktiva s možnou hodnotou null řídí kontext s možnou hodnotou null, jak je popsáno níže.
fragment PP_Nullable
: 'nullable' PP_Whitespace PP_Nullable_Action
(PP_Whitespace PP_Nullable_Target)?
;
fragment PP_Nullable_Action
: 'disable'
| 'enable'
| 'restore'
;
fragment PP_Nullable_Target
: 'warnings'
| 'annotations'
;
Direktiva s možnou hodnotou null nastaví dostupné příznaky pro další řádky kódu, dokud ji nepřepíše jiná direktiva s možnou hodnotou null nebo dokud se nedosáhne konce kompilačního _unit . Kontext s možnou hodnotou null obsahuje dva příznaky: poznámky a upozornění. Účinek každé formy direktivy s možnou hodnotou null je následující:
-
#nullable disable
: Zakáže jak poznámky s možnou hodnotou null, tak příznaky upozornění s možnou hodnotou null. -
#nullable enable
: Povolí příznaky upozornění s možnou hodnotou null i poznámek s možnou hodnotou null. -
#nullable restore
: Obnoví příznaky poznámek i upozornění do stavu určeného externím mechanismem( pokud existuje). -
#nullable disable annotations
: Zakáže příznak poznámek s možnou hodnotou null. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable enable annotations
: Povolí příznak poznámek s možnou hodnotou null. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable restore annotations
: Obnoví příznak poznámek s možnou hodnotou null do stavu určeného externím mechanismem, pokud existuje. Příznak upozornění s možnou hodnotou null nemá vliv. -
#nullable disable warnings
: Zakáže příznak upozornění s možnou hodnotou null. Příznak poznámek s možnou hodnotou null nemá vliv. -
#nullable enable warnings
: Povolí příznak upozornění s možnou hodnotou null. Příznak poznámek s možnou hodnotou null nemá vliv. -
#nullable restore warnings
: Obnoví příznak upozornění s možnou hodnotou null do stavu určeného externím mechanismem, pokud existuje. Příznak poznámek s možnou hodnotou null nemá vliv.
Stav výrazů s možnou hodnotou null se sleduje vždy. Stav příznaku anotace a přítomnost nebo absence poznámky s možnou hodnotou null určuje ?
počáteční stav null deklarace proměnné. Upozornění se vydávají pouze v případech, kdy je příznak upozornění povolený.
Příklad: Příklad
#nullable disable string x = null; string y = ""; #nullable enable Console.WriteLine(x.Length); // Warning Console.WriteLine(y.Length);
vytvoří upozornění v době kompilace ("tak, jak
x
jenull
"). Stav s možnoux
hodnotou null je sledován všude. Při povolení příznaku upozornění se zobrazí upozornění.end example
6.5.10 Direktivy Pragma
Direktiva #pragma
předběžného zpracování slouží k určení kontextových informací kompilátoru.
Poznámka: Kompilátor může například poskytnout
#pragma
direktivy, které
- Povolte nebo zakažte konkrétní zprávy upozornění při kompilaci dalšího kódu.
- Určete, které optimalizace se mají použít pro následující kód.
- Zadejte informace, které má ladicí program používat.
koncová poznámka
fragment PP_Pragma
: 'pragma' PP_Pragma_Text?
;
fragment PP_Pragma_Text
: PP_Whitespace Input_Character*
;
Input_Characterv PP_Pragma_Text interpretuje kompilátor způsobem definovaným implementací. Informace uvedené ve #pragma
směrnici nemění sémantiku programu. Směrnice #pragma
mění pouze chování kompilátoru, které je mimo rozsah této specifikace jazyka. Pokud kompilátor nemůže interpretovat Input_Characters, může kompilátor vytvořit upozornění; nesmí však vést k chybě v době kompilace.
Poznámka: PP_Pragma_Text může obsahovat libovolný text; konkrétně nemusí obsahovat správně formátované tokeny. koncová poznámka
ECMA C# draft specification