6 Estrutura lexical
6.1 Programas
Um programa C# consiste em um ou mais arquivos de origem, conhecidos formalmente como unidades de compilação (§14.2). Embora uma unidade de compilação possa ter uma correspondência um-para-um com um arquivo em um sistema de arquivos, essa correspondência não é necessária.
Conceitualmente falando, um programa é compilado usando três etapas:
- Transformação, que converte um arquivo de um determinado repertório de caracteres e esquema de codificação em uma sequência de caracteres Unicode.
- Análise lexical, que traduz um fluxo de caracteres de entrada Unicode em um fluxo de tokens.
- Análise sintática, que traduz o fluxo de tokens em código executável.
As implementações conformes devem aceitar unidades de compilação Unicode codificadas com o formulário de codificação UTF-8 (conforme definido pelo padrão Unicode) e transformá-las em uma sequência de caracteres Unicode. As implementações podem optar por aceitar e transformar esquemas adicionais de codificação de caracteres (como UTF-16, UTF-32 ou mapeamentos de caracteres não-Unicode).
Nota: A manipulação do caractere Unicode NULL (U+0000) é definida pela implementação. É altamente recomendável que os desenvolvedores evitem usar esse caractere em seu código-fonte, por uma questão de portabilidade e legibilidade. Quando o caractere é necessário dentro de um literal de caractere ou cadeia de caracteres, as sequências de
\0
escape ou\u0000
podem ser usadas em vez disso. Nota final
Nota: Está além do escopo desta especificação definir como um arquivo usando uma representação de caracteres diferente de Unicode pode ser transformado em uma sequência de caracteres Unicode. Durante essa transformação, no entanto, recomenda-se que o caractere (ou sequência) de separação de linha usual no outro conjunto de caracteres seja traduzido para a sequência de dois caracteres que consiste no caractere de retorno de carro Unicode (U+000D) seguido pelo caractere de alimentação de linha Unicode (U+000A). Na maior parte dos casos, esta transformação não terá efeitos visíveis; no entanto, afetará a interpretação dos tokens literais de cadeia de caracteres literais (§6.4.5.6). O objetivo desta recomendação é permitir que um literal de cadeia de caracteres literal produza a mesma sequência de caracteres quando sua unidade de compilação é movida entre sistemas que suportam diferentes conjuntos de caracteres não-Unicode, em particular, aqueles que usam sequências de caracteres diferentes para separação de linhas. Nota final
6.2 Gramáticas
6.2.1 Generalidades
Esta especificação apresenta a sintaxe da linguagem de programação C# usando duas gramáticas. A gramática lexical (§6.2.3) define como os caracteres Unicode são combinados para formar terminadores de linha, espaço em branco, comentários, tokens e diretivas de pré-processamento. A gramática sintática (§6.2.4) define como os tokens resultantes da gramática lexical são combinados para formar programas C#.
Todos os caracteres terminais devem ser entendidos como o caractere Unicode apropriado do intervalo U+0020 a U+007F, em oposição a quaisquer caracteres de aparência semelhante de outros intervalos de caracteres Unicode.
6.2.2 Notação gramatical
As gramáticas lexical e sintática são apresentadas na forma Extended Backus-Naur da ferramenta de gramática ANTLR.
Embora a notação ANTLR seja usada, esta especificação não apresenta uma "gramática de referência" completa pronta para ANTLR para C#; escrever um léxico e um analisador, à mão ou usando uma ferramenta como ANTLR, está fora do escopo de uma especificação de linguagem. Com essa qualificação, esta especificação tenta minimizar a lacuna entre a gramática especificada e a necessária para construir um léxico e analisador em ANTLR.
ANTLR distingue entre lexical e sintático, denominado analisador por ANTLR, gramáticas em sua notação iniciando regras lexicais com uma letra maiúscula e regras do analisador com uma letra minúscula.
Nota: A gramática lexical C# (§6.2.3) e a gramática sintática (§6.2.4) não correspondem exatamente com a divisão ANTLR em grammers lexicais e analisadores. Essa pequena incompatibilidade significa que algumas regras do analisador ANTLR são usadas ao especificar a gramática lexical C#. Nota final
6.2.3 Gramática lexical
A gramática lexical do C# é apresentada nos §6.3, §6.4 e §6.5. Os símbolos terminais da gramática lexical são os caracteres do conjunto de caracteres Unicode, e a gramática lexical especifica como os caracteres são combinados para formar tokens (§6.4), espaço em branco (§6.3.4), comentários (§6.3.3) e diretivas de pré-processamento (§6.5).
Muitos dos símbolos terminais da gramática sintática não são definidos explicitamente como símbolos na gramática lexical. Em vez disso, aproveita-se o comportamento ANTLR de que cadeias literais na gramática são extraídas como tokens lexicais implícitos; Isso permite que palavras-chave, operadores, etc. sejam representados na gramática por sua representação literal em vez de um nome simbólico.
Cada unidade de compilação num programa C# deve estar em conformidade com a produção de entrada da gramática lexical (§6.3.1).
6.2.4 Gramática sintática
A gramática sintática de C# é apresentada nas cláusulas, subcláusulas e anexos que seguem esta subcláusula. Os símbolos terminais da gramática sintática são os símbolos definidos explicitamente pela gramática lexical e implicitamente por cadeias literais na própria gramática (§6.2.3). A gramática sintática especifica como os tokens são combinados para formar programas C#.
Cada unidade de compilação num programa C# deve estar em conformidade com a produção compilation_unit (§14.2) da gramática sintática.
6.2.5 Ambiguidades gramaticais
As produções para simple_name (§12.8.4) e member_access (§12.8.7) podem dar origem a ambiguidades na gramática das expressões.
Exemplo: A instrução:
F(G<A, B>(7));
pode ser interpretado como um apelo com
F
dois argumentos,G < A
eB > (7)
. Alternativamente, pode ser interpretado como uma chamada paraF
com um argumento, que é uma chamada para um métodoG
genérico com dois argumentos de tipo e um argumento regular.Exemplo final
Se uma sequência de tokens puder ser analisada (no contexto) como um simple_name (§12.8.4), member_access (§12.8.7) ou pointer_member_access (§23.6.3) terminando com um type_argument_list (§8.4.2), o token imediatamente após o token de fechamento é examinado, para ver se é>
- Um dos
( ) ] } : ; , . ? == != | ^ && || & [
; ou - Um dos operadores
< <= >= is as
relacionais; - Uma palavra-chave de consulta contextual que aparece dentro de uma expressão de consulta; quer
- Em determinados contextos, o identificador é tratado como um token desambiguante. Esses contextos são onde a sequência de tokens que estão sendo desambiguados é imediatamente precedida por uma das palavras-chave
is
,case
ouout
, ou surge durante a análise do primeiro elemento de um literal de tupla (caso em que os tokens são precedidos por(
ou:
e o identificador é seguido por um,
) ou um elemento subsequente de um literal de tupla.
Se o token a seguir estiver entre essa lista, ou um identificador em tal contexto, o type_argument_list será mantido como parte do acesso simple_name, member_access ou pointer_member e qualquer outra análise possível da sequência de tokens será descartada. Caso contrário, o type_argument_list não é considerado parte do simple_name, member_access ou pointer_member_access, mesmo que não haja outra análise possível da sequência de tokens.
Nota: Estas regras não são aplicadas ao analisar um type_argument_list num namespace_or_type_name (§7.8). Nota final
Exemplo: A instrução:
F(G<A, B>(7));
será, de acordo com esta regra, interpretado como uma chamada para
F
com um argumento, que é uma chamada para um métodoG
genérico com dois argumentos de tipo e um argumento regular. As declaraçõesF(G<A, B>7); F(G<A, B>>7);
cada um será interpretado como um apelo com
F
dois argumentos. A declaraçãox = F<A> + y;
será interpretado como um operador menor, maior que operador e operador unário-mais, como se a instrução tivesse sido escrita
x = (F < A) > (+y)
, em vez de como um simple_name com um type_argument_list seguido por um operador binário-mais. No comunicadox = y is C<T> && z;
Os tokens
C<T>
são interpretados como uma namespace_or_type_name com um type_argument_list devido à presença do token&&
desambiguante após o type_argument_list.A expressão
(A < B, C > D)
é uma tupla com dois elementos, cada um uma comparação.A expressão
(A<B,C> D, E)
é uma tupla com dois elementos, o primeiro dos quais é uma expressão de declaração.A invocação
M(A < B, C > D, E)
tem três argumentos.A invocação
M(out A<B,C> D, E)
tem dois argumentos, o primeiro dos quais é umaout
declaração.A expressão
e is A<B> C
usa um padrão de declaração.O rótulo do caso
case A<B> C:
usa um padrão de declaração.Exemplo final
Ao reconhecer um relational_expression (§12.12.1is
6.3 Análise lexical
6.3.1 Generalidades
Por conveniência, a gramática lexical define e faz referência aos seguintes tokens lexer nomeados:
DEFAULT : 'default' ;
NULL : 'null' ;
TRUE : 'true' ;
FALSE : 'false' ;
ASTERISK : '*' ;
SLASH : '/' ;
Embora estas sejam regras léxicas, esses nomes são escritos em letras maiúsculas para distingui-los dos nomes de regras lexer comuns.
Nota: Essas regras de conveniência são exceções à prática usual de não fornecer nomes de token explícitos para tokens definidos por cadeias de caracteres literais. Nota final
A produção de entrada define a estrutura lexical de uma unidade de compilação C#.
input
: input_section?
;
input_section
: input_section_part+
;
input_section_part
: input_element* New_Line
| PP_Directive
;
input_element
: Whitespace
| Comment
| token
;
Nota: A gramática acima é descrita por regras de análise ANTLR, ela define a estrutura lexical de uma unidade de compilação C# e não tokens lexicais. Nota final
Cinco elementos básicos compõem a estrutura lexical de uma unidade de compilação C#: terminadores de linha (§6.3.2), espaço em branco (§6.3.4), comentários (§6.3.3), tokens (§6.4) e diretivas de pré-processamento (§6.5). Desses elementos básicos, apenas tokens são significativos na gramática sintática de um programa C# (§6.2.4).
O processamento lexical de uma unidade de compilação C# consiste em reduzir o arquivo em uma sequência de tokens que se torna a entrada para a análise sintática. Terminadores de linha, espaço em branco e comentários podem servir para separar tokens, e diretivas de pré-processamento podem fazer com que seções da unidade de compilação sejam ignoradas, mas, caso contrário, esses elementos lexicais não têm impacto na estrutura sintática de um programa C#.
Quando várias produções gramaticais lexicais correspondem a uma sequência de caracteres em uma unidade de compilação, o processamento lexical sempre forma o elemento lexical mais longo possível.
Exemplo: A sequência
//
de caracteres é processada como o início de um comentário de linha única porque esse elemento lexical é maior do que um único/
token. Exemplo final
Alguns tokens são definidos por um conjunto de regras lexicais; uma regra principal e uma ou mais subregras. Estes últimos são marcados na gramática para fragment
indicar que a regra define parte de outro token. As regras de fragmento não são consideradas na ordenação de cima para baixo das regras lexicais.
Nota: Em ANTLR
fragment
é uma palavra-chave que produz o mesmo comportamento definido aqui. Nota final
6.3.2 Terminadores de linha
Os terminadores de linha dividem os caracteres de uma unidade de compilação C# em linhas.
New_Line
: New_Line_Character
| '\u000D\u000A' // carriage return, line feed
;
Para compatibilidade com ferramentas de edição de código-fonte que adicionam marcadores de fim de arquivo e para permitir que uma unidade de compilação seja vista como uma sequência de linhas terminadas corretamente, as seguintes transformações são aplicadas, em ordem, a cada unidade de compilação em um programa C#:
- Se o último caractere da unidade de compilação for um caractere Control-Z (U+001A), esse caractere será excluído.
- Um caractere de retorno de carro (U+000D) é adicionado ao final da unidade de compilação se essa unidade de compilação não estiver vazia e se o último caractere da unidade de compilação não for um retorno de carro (U+000D), uma alimentação de linha (U+000A), um caractere de linha seguinte (U+0085), um separador de linha (U+2028) ou um separador de parágrafo (U+2029).
Nota: O retorno de carro adicional permite que um programa termine em um PP_Directive (§6.5) que não tenha um New_Line de encerramento. Nota final
6.3.3 Comentários
São suportadas duas formas de comentários: comentários delimitados e comentários de linha única.
Um comentário delimitado começa com os caracteres /*
e termina com os caracteres */
. Comentários delimitados podem ocupar uma parte de uma linha, uma única linha ou várias linhas.
Exemplo: O exemplo
/* Hello, world program This program writes "hello, world" to the console */ class Hello { static void Main() { System.Console.WriteLine("hello, world"); } }
inclui um comentário delimitado.
Exemplo final
Um comentário de linha única começa com os caracteres //
e se estende até o final da linha.
Exemplo: O exemplo
// 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"); } }
mostra vários comentários de linha única.
Exemplo final
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
;
Os comentários não aninham. As sequências de carateres /*
e */
não têm um significado especial dentro de um comentário de linha única e as sequências de carateres //
e /*
não têm um significado especial dentro de um comentário delimitado.
Os comentários não são processados em literais de caracteres e cadeias de caracteres.
Nota: Estas regras devem ser interpretadas cuidadosamente. Por exemplo, no exemplo abaixo, o comentário delimitado que começa antes
A
de terminar entreB
eC()
. A razão é que// B */ C();
não é, na verdade, um comentário de linha única, uma vez que
//
não tem nenhum significado especial dentro de um comentário delimitado, e assim*/
tem o seu significado especial habitual nessa linha.Da mesma forma, o comentário delimitado começando antes
D
de terminar antes deE
. A razão é que não é realmente uma cadeia literal, uma vez que"D */ "
o caractere inicial de aspas duplas aparece dentro de um comentário delimitado.Uma consequência útil de e
/*
não tendo nenhum significado especial dentro de*/
um comentário de linha única é que um bloco de linhas de código-fonte pode ser comentado colocando//
no início de cada linha. Em geral, não funciona colocar/*
antes dessas linhas e*/
depois delas, pois isso não encapsula adequadamente os comentários delimitados no bloco e, em geral, pode alterar completamente a estrutura desses comentários delimitados.Código de exemplo:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
Nota final
Single_Line_Comment s e Delimited_Comments com formatos específicos podem ser usados como comentários de documentação, conforme descrito no §D.
6.3.4 Espaço em branco
Espaço em branco é definido como qualquer caractere com classe Unicode Zs (que inclui o caractere de espaço), bem como o caractere de tabulação horizontal, o caractere de tabulação vertical e o caractere de alimentação de formulário.
Whitespace
: [\p{Zs}] // any character with Unicode class Zs
| '\u0009' // horizontal tab
| '\u000B' // vertical tab
| '\u000C' // form feed
;
6.4 Fichas
6.4.1 Generalidades
Existem vários tipos de tokens: identificadores, palavras-chave, literais, operadores e pontuadores. Espaço em branco e comentários não são tokens, embora atuem como separadores para tokens.
token
: identifier
| keyword
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| operator_or_punctuator
;
Nota: Esta é uma regra de analisador ANTLR, não define um token lexical, mas sim a coleção de tipos de token. Nota final
6.4.2 Sequências de escape de caracteres Unicode
Uma sequência de escape Unicode representa um ponto de código Unicode. As sequências de escape Unicode são processadas em identificadores (§6.4.3), literais de caracteres (§6.4.5.5), literais de cadeia regular (§6.4.5.6) e expressões de cadeia regular interpoladas (§12.8.3). Uma sequência de escape Unicode não é processada em nenhum outro local (por exemplo, para formar um operador, pontuador ou palavra-chave).
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
;
Uma sequência de escape de caracteres Unicode representa o único ponto de código Unicode formado pelo número hexadecimal após os caracteres "\u" ou "\U". Como o C# usa uma codificação de 16 bits de pontos de código Unicode em valores de caracteres e cadeias de caracteres, um ponto de código Unicode no intervalo U+10000
para U+10FFFF
é representado usando duas unidades de código substituto Unicode. Os pontos de código Unicode acima U+FFFF
não são permitidos em literais de caracteres. Os pontos de código Unicode acima U+10FFFF
são inválidos e não são suportados.
Não são realizadas traduções múltiplas. Por exemplo, a cadeia de caracteres literal "\u005Cu005C"
é equivalente a "\u005C"
em vez de "\"
.
Nota: O valor
\u005C
Unicode é o caractere "\
". Nota final
Exemplo: O exemplo
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) { System.Console.WriteLine(c.ToString()); } } }
mostra vários usos de , que é a sequência de
\u0066
escape para a letra "f
". O programa é equivalente aclass Class1 { static void Test(bool f) { char c = 'f'; if (f) { System.Console.WriteLine(c.ToString()); } } }
Exemplo final
6.4.3 Identificadores
As regras para identificadores dadas nesta subcláusula correspondem exatamente às recomendadas pelo Unicode Standard Annex 15, exceto que sublinhado é permitido como um caractere inicial (como é tradicional na linguagem de programação C), sequências de escape Unicode são permitidas em identificadores e o caractere "@
" é permitido como um prefixo para permitir que palavras-chave sejam usadas como identificadores.
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
;
Nota:
- Para obter informações sobre as classes de caracteres Unicode mencionadas acima, consulte O padrão Unicode.
- O fragmento Available_Identifier requer a exclusão de palavras-chave e palavras-chave contextuais. Se a gramática nesta especificação é processada com ANTLR, então esta exclusão é tratada automaticamente pela semântica de ANTLR:
- Palavras-chave e palavras-chave contextuais ocorrem na gramática como cadeias literais.
- ANTLR cria regras de token lexical implícitas são criadas a partir dessas cadeias de caracteres literais.
- A ANTLR considera essas regras implícitas antes das regras lexicais explícitas na gramática.
- Portanto, o fragmento Available_Identifier não corresponderá a palavras-chave ou palavras-chave contextuais como as regras lexicais para aqueles o precedem.
- O Escaped_Identifier de fragmentos inclui palavras-chave escapadas e palavras-chave contextuais, uma vez que fazem parte do token mais longo, começando com um e o processamento lexical sempre forma o elemento lexical mais longo possível (
@
).- Como uma implementação impõe as restrições aos valores de Unicode_Escape_Sequence permitidos é um problema de implementação.
Nota final
Exemplo: Exemplos de identificadores válidos são
identifier1
,_identifier2
e@if
. Exemplo final
Um identificador em um programa conforme deve estar no formato canônico definido pelo Formulário de Normalização Unicode C, conforme definido pelo Anexo 15 da Norma Unicode. O comportamento ao encontrar um identificador que não está no Formulário de Normalização C é definido pela implementação; no entanto, não é necessário um diagnóstico.
O prefixo "@
" permite o uso de palavras-chave como identificadores, o que é útil na interface com outras linguagens de programação. O caractere @
não é realmente parte do identificador, então o identificador pode ser visto em outros idiomas como um identificador normal, sem o prefixo. Um identificador com um @
prefixo é chamado de identificador literal.
Nota: O uso do prefixo
@
para identificadores que não são palavras-chave é permitido, mas fortemente desencorajado por uma questão de estilo. Nota final
Exemplo: O exemplo:
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); } }
define uma classe chamada "
class
" com um método estático chamado "static
" que usa um parâmetro chamado "bool
". Observe que, como escapes Unicode não são permitidos em palavras-chave, o token "cl\u0061ss
" é um identificador e é o mesmo identificador que "@class
".Exemplo final
Dois identificadores são considerados iguais se forem idênticos após a aplicação das seguintes transformações, pela ordem:
- O prefixo "
@
", se usado, é removido. - Cada Unicode_Escape_Sequence é transformado em seu caractere Unicode correspondente.
- Todos os Formatting_Characters são removidos.
A semântica de um identificador nomeado _
depende do contexto em que aparece:
- Ele pode denotar um elemento de programa nomeado, como uma variável, classe ou método, ou
- Pode indicar uma eliminação (§9.2.9.2).
Identificadores contendo dois caracteres de sublinhado consecutivos (U+005F
) são reservados para uso pela implementação, no entanto, nenhum diagnóstico é necessário se tal identificador for definido.
Nota: Por exemplo, uma implementação pode fornecer palavras-chave estendidas que começam com dois sublinhados. Nota final
6.4.4 Palavras-chave
Uma palavra-chave é uma sequência de caracteres semelhante a um identificador que é reservada e não pode ser usada como um identificador, exceto quando precedida @
pelo caractere.
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'
;
Uma palavra-chave contextual é uma sequência de caracteres semelhante a um identificador que tem um significado especial em determinados contextos, mas não é reservada, e pode ser usada como um identificador fora desses contextos, bem como quando prefaciada @
pelo caractere.
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'
;
Nota: As regras palavra-chave e contextual_keyword são regras de analisador, pois não introduzem novos tipos de token. Todas as palavras-chave e palavras-chave contextuais são definidas por regras lexicais implícitas à medida que ocorrem como cadeias literais na gramática (§6.2.3). Nota final
Na maioria dos casos, a localização sintática das palavras-chave contextuais é tal que elas nunca podem ser confundidas com o uso de identificadores comuns. Por exemplo, dentro de uma declaração de propriedade, os get
identificadores e set
têm um significado especial (§15.7.3). Um identificador diferente ou get
set
nunca permitido nesses locais, portanto, esse uso não entra em conflito com o uso dessas palavras como identificadores.
Em certos casos, a gramática não é suficiente para distinguir o uso de palavras-chave contextuais de identificadores. Em todos esses casos, será especificado como desambiguar entre os dois. Por exemplo, a palavra-chave var
contextual em declarações de variáveis locais digitadas implicitamente (§13.6.2) pode entrar em conflito com um tipo declarado chamado var
, caso em que o nome declarado tem precedência sobre o uso do identificador como palavra-chave contextual.
Outro exemplo dessa desambiguação é a palavra-chave await
contextual (§12.9.8.1), que é considerada uma palavra-chave apenas quando dentro de um método declarado async
, mas pode ser usada como identificador em outro lugar.
Assim como acontece com palavras-chave, palavras-chave contextuais podem ser usadas como identificadores comuns, prefixando-as com o @
caractere.
Nota: Quando usados como palavras-chave contextuais, esses identificadores não podem conter Unicode_Escape_Sequences. Nota final
6.4.5 Literais
6.4.5.1 Generalidades
Um literal (§12.8.2) é uma representação de código fonte de um valor.
literal
: boolean_literal
| Integer_Literal
| Real_Literal
| Character_Literal
| String_Literal
| null_literal
;
Nota: literal é uma regra de analisador, pois agrupa outros tipos de token e não introduz um novo tipo de token. Nota final
6.4.5.2 Literais booleanos
Existem dois valores literais booleanos: true
e false
.
boolean_literal
: TRUE
| FALSE
;
Nota: boolean_literal é uma regra de analisador, pois agrupa outros tipos de token e não introduz um novo tipo de token. Nota final
O tipo de boolean_literal é bool
.
6.4.5.3 Literais inteiros
Literais inteiros são usados para escrever valores dos tipos int
, uint
, long
, e ulong
. Os literais inteiros têm três formas possíveis: decimal, hexadecimal e binário.
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'
;
O tipo de um literal inteiro é determinado da seguinte forma:
- Se o literal não tem sufixo, ele tem o primeiro destes tipos em que seu valor pode ser representado:
int
,uint
,long
,ulong
. - Se o literal é sufixo por
U
ouu
, ele tem o primeiro destes tipos em que seu valor pode ser representado:uint
,ulong
. - Se o literal é sufixo por
L
oul
, ele tem o primeiro destes tipos em que seu valor pode ser representado:long
,ulong
. - Se o literal é sufixo por
UL
,Ul
,uL
,ul
,LU
,Lu
lU
, oulu
, é do tipoulong
.
Se o valor representado por um literal inteiro estiver fora do intervalo do tipo, ocorrerá um erro em tempo de ulong
compilação.
Nota: Por uma questão de estilo, sugere-se que "
L
" seja usado em vez de "l
" ao escrever literais do tipolong
, uma vez que é fácil confundir a letra "l
" com o dígito "1
". Nota final
Para permitir que o menor possível int
e long
os valores sejam escritos como literais inteiros, existem as duas regras a seguir:
- Quando um Integer_Literal representando o valor
2147483648
(2³¹) e nenhum Integer_Type_Suffix aparece como o token imediatamente após um token de operador menos unário (§12.9.3), o resultado (de ambos os tokens) é uma constante do tipo int com o valor−2147483648
(−2³¹). Em todas as outras situações, tal Integer_Literal é do tipouint
. - Quando um Integer_Literal representa o valor
9223372036854775808
(2⁶³) e nenhum Integer_Type_Suffix ou o Integer_Type_SuffixL
oul
aparece como o token imediatamente após um token de operador menos unário (§12.9.3), o resultado (de ambos os tokens) é uma constante do tipolong
com o valor−9223372036854775808
(−2⁶³). Em todas as outras situações, tal Integer_Literal é do tipoulong
.
Exemplo:
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
Exemplo final
6.4.5.4 Literais reais
Literais reais são usados para escrever valores dos tipos float
, double
, e 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'
;
Se nenhum Real_Type_Suffix for especificado, o tipo de Real_Literal será double
. Caso contrário, o Real_Type_Suffix determina o tipo do literal real, da seguinte forma:
- Um sufixo literal real por
F
ouf
é do tipofloat
.Exemplo: Os literais
1f
,1.5f
,1e10f
, e123.456F
são todos do tipofloat
. Exemplo final - Um sufixo literal real por
D
oud
é do tipodouble
.Exemplo: Os literais
1d
,1.5d
,1e10d
, e123.456D
são todos do tipodouble
. Exemplo final - Um sufixo literal real por
M
oum
é do tipodecimal
.Exemplo: Os literais
1m
,1.5m
,1e10m
, e123.456M
são todos do tipodecimal
. Exemplo final
Este literal é convertido numdecimal
valor tomando o valor exato e, se necessário, arredondando para o valor representável mais próximo usando o arredondamento do banqueiro (§8.3.8). Qualquer escala aparente no literal é preservada, a menos que o valor seja arredondado. Nota: Assim, o literal2.900m
será analisado para formar odecimal
com sinal0
, coeficiente2900
e escala3
. Nota final
Se a magnitude do literal especificado for muito grande para ser representada no tipo indicado, ocorrerá um erro em tempo de compilação.
Nota: Em particular, um Real_Literal nunca produzirá um infinito de ponto flutuante. Um Real_Literal diferente de zero pode, no entanto, ser arredondado para zero. Nota final
O valor de um literal real do tipo float
ou double
é determinado usando o modo IEC 60559 "redondo para mais próximo" com laços quebrados para "par" (um valor com o zero de bits menos significativo), e todos os dígitos considerados significativos.
Nota: Em um literal real, dígitos decimais são sempre necessários após o ponto decimal. Por exemplo, é um verdadeiro literal,
1.3F
mas1.F
não é. Nota finalExemplo:
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
Exemplo final
6.4.5.5 Literais de caracteres
Um literal de caractere representa um único caractere e consiste em um caractere entre aspas, como em '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?
;
Nota: Um caractere que segue um caractere de barra invertida (
\
) em um caractere deve ser um dos seguintes caracteres:'
,"
,\
,0
,a
b
f
n
r
t
u
U
x
v
. Caso contrário, ocorrerá um erro em tempo de compilação. Nota final
Nota: O uso da produção de
\x
Hexadecimal_Escape_Sequence pode ser propenso a erros e difícil de ler devido ao número variável de dígitos hexadecimais após o\x
. Por exemplo, no código:string good = "\x9Good text"; string bad = "\x9Bad text";
Pode parecer a princípio que o caractere principal é o mesmo (
U+0009
, um caractere de tabulação) em ambas as cadeias de caracteres. Na verdade, a segunda string começa comU+9BAD
como todas as três letras na palavra "Bad" são dígitos hexadecimais válidos. Por uma questão de estilo, recomenda-se que\x
se evite em favor de sequências de escape específicas (\t
neste exemplo) ou da sequência de escape de comprimento\u
fixo.Nota final
Uma sequência de escape hexadecimal representa uma única unidade de código Unicode UTF-16, com o valor formado pelo número hexadecimal após "\x
".
Se o valor representado por um literal de caractere for maior que U+FFFF
, ocorrerá um erro em tempo de compilação.
Uma sequência de escape Unicode (§6.4.2) num literal de carateres deve estar no intervalo U+0000
de U+FFFF
.
Uma sequência de escape simples representa um caractere Unicode, conforme descrito na tabela abaixo.
Sequência de fuga | Nome do personagem | Ponto de código Unicode |
---|---|---|
\' |
Citação única | U+0027 |
\" |
Citação dupla | U+0022 |
\\ |
Barra inversa | U+005C |
\0 |
Nulo | U+0000 |
\a |
Alerta | U+0007 |
\b |
Espaço traseiro | U+0008 |
\f |
Feed de formulários | U+000C |
\n |
Nova linha | U+000A |
\r |
Retorno de carro | U+000D |
\t |
Separador horizontal | U+0009 |
\v |
Separador vertical | U+000B |
O tipo de Character_Literal é char
.
6.4.5.6 Literais de cadeia de caracteres
C# suporta duas formas de literais de cadeia de caracteres: literais de cadeia regular e literais de cadeia de caracteres literais. Um literal de cadeia de caracteres regular consiste em zero ou mais caracteres entre aspas duplas, como em "hello"
, e pode incluir sequências de escape simples (como \t
para o caractere de tabulação) e sequências de escape hexadecimais e Unicode.
Um literal de cadeia de caracteres literal consiste em um @
caractere seguido por um caractere de aspas duplas, zero ou mais caracteres e um caractere de aspas duplas de fechamento.
Exemplo: Um exemplo simples é
@"hello"
. Exemplo final
Em uma string literal literal, os caracteres entre os delimitadores são interpretados textualmente, com a única exceção sendo um Quote_Escape_Sequence, que representa um caractere de aspas duplas. Em particular, sequências de escape simples e sequências de escape hexadecimais e Unicode não são processadas em literais de cadeia de caracteres literais. Um literal de cadeia de caracteres literal pode abranger várias linhas.
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
: '""'
;
Exemplo: O exemplo
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";
mostra uma variedade de literais de cadeia de caracteres. O último literal de cadeia de caracteres,
j
, é um literal de cadeia de caracteres literal que abrange várias linhas. Os caracteres entre aspas, incluindo espaços em branco, como novos caracteres de linha, são preservados literalmente, e cada par de caracteres de aspas duplas é substituído por um desses caracteres.Exemplo final
Nota: Qualquer quebra de linha dentro de literais de cadeia de caracteres literais faz parte da cadeia de caracteres resultante. Se os caracteres exatos usados para formar quebras de linha forem semanticamente relevantes para um aplicativo, quaisquer ferramentas que traduzam quebras de linha no código-fonte para formatos diferentes (entre "
\n
" e "\r\n
", por exemplo) alterarão o comportamento do aplicativo. Os desenvolvedores devem ter cuidado em tais situações. Nota final
Nota: Como uma sequência de escape hexadecimal pode ter um número variável de dígitos hexadecimais, o literal
"\x123"
da cadeia de caracteres contém um único caractere com valor123
hexadecimal. Para criar uma cadeia de caracteres contendo o caractere com valor12
hexadecimal seguido pelo caractere3
, pode-se escrever"\x00123"
ou"\x12"
+"3"
em vez disso. Nota final
O tipo de String_Literal é string
.
Cada literal de cadeia de caracteres não resulta necessariamente em uma nova ocorrência de cadeia de caracteres. Quando dois ou mais literais de cadeia de caracteres que são equivalentes de acordo com o operador de igualdade de cadeia de caracteres (§12.12.8), aparecem no mesmo assembly, esses literais de cadeia de caracteres referem-se à mesma ocorrência de cadeia de caracteres.
Exemplo: Por exemplo, a produção produzida por
class Test { static void Main() { object a = "hello"; object b = "hello"; System.Console.WriteLine(a == b); } }
é
True
porque os dois literais se referem à mesma ocorrência de cadeia de caracteres.Exemplo final
6.4.5.7 O literal nulo
null_literal
: NULL
;
Nota: null_literal é uma regra de analisador, pois não introduz um novo tipo de token. Nota final
Um null_literal representa um null
valor. Não tem um tipo, mas pode ser convertido em qualquer tipo de referência ou tipo de valor anulável através de uma conversão literal nula (§10.2.7).
6.4.6 Operadores e pontuadores
Existem vários tipos de operadores e pontuadores. Os operadores são usados em expressões para descrever operações que envolvem um ou mais operandos.
Exemplo: A expressão
a + b
usa o+
operador para adicionar os dois operandosa
eb
. Exemplo final
Os pontuadores servem para agrupar e separar.
operator_or_punctuator
: '{' | '}' | '[' | ']' | '(' | ')' | '.' | ',' | ':' | ';'
| '+' | '-' | ASTERISK | SLASH | '%' | '&' | '|' | '^' | '!' | '~'
| '=' | '<' | '>' | '?' | '??' | '::' | '++' | '--' | '&&' | '||'
| '->' | '==' | '!=' | '<=' | '>=' | '+=' | '-=' | '*=' | '/=' | '%='
| '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
;
right_shift
: '>' '>'
;
right_shift_assignment
: '>' '>='
;
Nota: right_shift e right_shift_assignment são regras de analisador, pois não introduzem um novo tipo de token, mas representam uma sequência de dois tokens. A regra operator_or_punctuator existe apenas para fins descritivos e não é usada em nenhum outro lugar da gramática. Nota final
right_shift é composto pelos dois tokens >
e >
. Da mesma forma, right_shift_assignment é composto pelos dois tokens >
e >=
. Ao contrário de outras produções na gramática sintática, nenhum caractere de qualquer tipo (nem mesmo espaço em branco) é permitido entre os dois tokens em cada uma dessas produções. Estas produções são tratadas especialmente para permitir o manuseamento correto dos type_parameter_lists (§15.2.3).
Nota: Antes da adição de genéricos ao C#,
>>
ambos>>=
eram tokens únicos. No entanto, a sintaxe para genéricos usa os<
caracteres e>
para delimitar parâmetros de tipo e argumentos de tipo. Muitas vezes, é desejável usar tipos construídos aninhados, comoList<Dictionary<string, int>>
. Em vez de exigir que o programador separasse o>
e>
por um espaço, a definição dos dois operator_or_punctuators foi alterada. Nota final
6.5 Diretivas de pré-processamento
6.5.1 Generalidades
As diretivas de pré-processamento fornecem a capacidade de ignorar condicionalmente seções de unidades de compilação, relatar condições de erro e aviso, delinear regiões distintas do código-fonte e definir o contexto anulável.
Nota: O termo "diretivas de pré-processamento" é usado apenas para consistência com as linguagens de programação C e C++. Em C#, não há nenhuma etapa de pré-processamento separada; As diretivas de pré-processamento são processadas como parte da fase de análise lexical. Nota final
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
;
Nota:
- A gramática do pré-processador define um único token
PP_Directive
lexical usado para todas as diretivas de pré-processamento. A semântica de cada uma das diretivas de pré-processamento é definida nesta especificação de linguagem, mas não como implementá-las.- O
PP_Start
fragmento só deve ser reconhecido no início de uma linha, o predicadogetCharPositionInLine() == 0
lexical da ANTLR acima sugere uma maneira pela qual isso pode ser alcançado e é apenas informativo, uma implementação pode usar uma estratégia diferente.Nota final
Estão disponíveis as seguintes diretivas de pré-processamento:
-
#define
e#undef
, que são utilizados para definir e não definir, respetivamente, símbolos de compilação condicional (§6.5.4). -
#if
,#elif
,#else
, e#endif
, que são utilizados para ignorar condicionalmente secções do código-fonte (§6.5.5). -
#line
, que é utilizado para controlar os números de linha emitidos em caso de erros e avisos (§6.5.8). -
#error
, que é utilizado para emitir erros (§6.5.6). -
#region
e#endregion
, que são utilizados para marcar explicitamente secções do código-fonte (§6.5.7). -
#nullable
, que é utilizado para especificar o contexto anulável (§6.5.9). -
#pragma
, que é usado para especificar informações contextuais opcionais para um compilador (§6.5.10).
Uma diretiva de pré-processamento sempre ocupa uma linha separada de código-fonte e sempre começa com um #
caractere e um nome de diretiva de pré-processamento. Espaço em branco pode ocorrer antes do #
caractere e entre o #
caractere e o nome da diretiva.
Uma linha de origem contendo um #define
, #undef
, #if
, #elif
, #else
, #endif
#line
#endregion
, ou #nullable
diretiva pode terminar com um comentário de linha única. Comentários delimitados (o /* */
estilo dos comentários) não são permitidos em linhas-fonte que contenham diretivas de pré-processamento.
As diretivas de pré-processamento não fazem parte da gramática sintática do C#. No entanto, as diretivas de pré-processamento podem ser usadas para incluir ou excluir sequências de tokens e, dessa forma, podem afetar o significado de um programa em C#.
Exemplo: Quando compilado, o programa
#define A #undef B class C { #if A void F() {} #else void G() {} #endif #if B void H() {} #else void I() {} #endif }
resulta exatamente na mesma sequência de tokens que o programa
class C { void F() {} void I() {} }
Assim, enquanto lexicamente, os dois programas são bastante diferentes, sintaticamente, eles são idênticos.
Exemplo final
6.5.2 Símbolos de compilação condicional
A funcionalidade de compilação condicional fornecida pelas #if
diretivas , #elif
, #else
, e #endif
é controlada através de expressões de pré-processamento (§6.5.3) e símbolos de compilação condicional.
fragment PP_Conditional_Symbol
// Must not be equal to tokens TRUE or FALSE. See note below.
: Basic_Identifier
;
Observação Como uma implementação impõe a restrição nos valores de Basic_Identifier permitidos é um problema de implementação. Nota final
Dois símbolos de compilação condicional são considerados iguais se forem idênticos após as seguintes transformações serem aplicadas, em ordem:
- Cada Unicode_Escape_Sequence é transformado em seu caractere Unicode correspondente.
- Todos os Formatting_Characters são removidos.
Um símbolo de compilação condicional tem dois estados possíveis: definido ou indefinido. No início do processamento lexical de uma unidade de compilação, um símbolo de compilação condicional é indefinido, a menos que tenha sido explicitamente definido por um mecanismo externo (como uma opção de compilador de linha de comando). Quando uma #define
diretiva é processada, o símbolo de compilação condicional nomeado nessa diretiva torna-se definido nessa unidade de compilação. O símbolo permanece definido até que uma #undef
diretiva para esse mesmo símbolo seja processada, ou até que o final da unidade de compilação seja alcançado. Uma implicação disso é que #define
e #undef
diretivas em uma unidade de compilação não têm efeito sobre outras unidades de compilação no mesmo programa.
Quando referenciado em uma expressão de pré-processamento (§6.5.3), um símbolo de compilação condicional definido tem o valor true
booleano , e um símbolo de compilação condicional indefinido tem o valor false
booleano . Não há nenhum requisito de que os símbolos de compilação condicional sejam explicitamente declarados antes de serem referenciados em expressões de pré-processamento. Em vez disso, símbolos não declarados são simplesmente indefinidos e, portanto, têm o valor false
.
O namespace para símbolos de compilação condicional é distinto e separado de todas as outras entidades nomeadas em um programa C#. Os símbolos de compilação condicional só podem ser referenciados em #define
diretivas e #undef
em expressões de pré-processamento.
6.5.3 Expressões de pré-processamento
Expressões de pré-processamento podem ocorrer em #if
e #elif
diretivas. Os operadores !
(somente negação lógica de prefixo), ==
, !=
, &&
, e ||
são permitidos em expressões de pré-processamento, e parênteses podem ser usados para agrupamento.
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? ')'
;
Quando referenciado em uma expressão de pré-processamento, um símbolo de compilação condicional definido tem o valor true
booleano e um símbolo de compilação condicional indefinido tem o valor false
booleano.
A avaliação de uma expressão de pré-processamento sempre produz um valor booleano. As regras de avaliação para uma expressão de pré-processamento são as mesmas que para uma expressão constante (§12.23), exceto que as únicas entidades definidas pelo usuário que podem ser referenciadas são símbolos de compilação condicional.
6.5.4 Diretivas de definição
As diretivas de definição são usadas para definir ou desdefinir símbolos de compilação condicional.
fragment PP_Declaration
: 'define' PP_Whitespace PP_Conditional_Symbol
| 'undef' PP_Whitespace PP_Conditional_Symbol
;
O processamento de uma #define
diretiva faz com que o símbolo de compilação condicional dado se torne definido, começando com a linha de origem que segue a diretiva. Da mesma forma, o processamento de uma #undef
diretiva faz com que o símbolo de compilação condicional dado se torne indefinido, começando com a linha de origem que segue a diretiva.
Quaisquer #define
diretivas em #undef
uma unidade de compilação devem ocorrer antes do primeiro token (§6.4) na unidade de compilação, caso contrário, ocorre um erro em tempo de compilação. Em termos intuitivos, #define
e #undef
as diretivas devem preceder qualquer "código real" na unidade de compilação.
Exemplo: O exemplo:
#define Enterprise #if Professional || Enterprise #define Advanced #endif namespace Megacorp.Data { #if Advanced class PivotTable {...} #endif }
é válido porque as
#define
diretivas precedem o primeiro token (anamespace
palavra-chave) na unidade de compilação.Exemplo final
Exemplo: O exemplo a seguir resulta em um erro em tempo de compilação porque um #define segue código real:
#define A namespace N { #define B #if B class Class1 {} #endif }
Exemplo final
A #define
pode definir um símbolo de compilação condicional que já está definido, sem que haja qualquer intervenção #undef
para esse símbolo.
Exemplo: O exemplo abaixo define um símbolo de compilação condicional A e, em seguida, define-o novamente.
#define A #define A
Para compiladores que permitem que símbolos de compilação condicional sejam definidos como opções de compilação, uma maneira alternativa para que essa redefinição ocorra é definir o símbolo como uma opção de compilador, bem como no código-fonte.
Exemplo final
A #undef
pode "undefine" um símbolo de compilação condicional que não está definido.
Exemplo: O exemplo abaixo define um símbolo
A
de compilação condicional e depois o desdefine duas vezes, embora a segunda#undef
não tenha efeito, ainda é válida.#define A #undef A #undef A
Exemplo final
6.5.5 Diretivas de compilação condicional
As diretivas de compilação condicional são usadas para condicionalmente incluir ou excluir partes de uma unidade de compilação.
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'
;
As diretivas de compilação condicional devem ser escritas em grupos constituídos, pela ordem, por uma #if
diretiva, zero ou mais #elif
diretivas, zero ou uma #else
diretiva e uma #endif
diretiva. Entre as diretivas estão seções condicionais do código-fonte. Cada secção é controlada pela diretiva imediatamente anterior. Uma seção condicional pode conter diretivas de compilação condicional aninhadas, desde que essas diretivas formem grupos completos.
No máximo, uma das seções condicionais contidas é selecionada para processamento lexical normal:
- As PP_Expressions das
#if
e#elif
diretivas são avaliadas por ordem até se cedertrue
. Se uma expressão produzirtrue
, a seção condicional após a diretiva correspondente será selecionada. - Se todos os
#else
diretiva será selecionada. - Caso contrário, nenhuma seção condicional será selecionada.
A seção condicional selecionada, se houver, é processada como um input_section normal: o código-fonte contido na seção deve aderir à gramática lexical, os tokens são gerados a partir do código-fonte na seção e as diretivas de pré-processamento na seção têm os efeitos prescritos.
Todas as seções condicionais restantes são ignoradas e nenhum token, exceto aqueles para diretivas de pré-processamento, é gerado a partir do código-fonte. Portanto, o código-fonte ignorado, exceto as diretivas de pré-processamento, pode ser lexicamente incorreto. As diretivas de pré-processamento ignoradas devem ser lexicamente corretas, mas não são processadas de outra forma. Dentro de uma seção condicional que está sendo ignorada, todas as seções condicionais aninhadas (contidas em construções aninhadas #if...#endif
) também são ignoradas.
Nota: A gramática acima não capta a permissão de que as seções condicionais entre as diretivas de pré-processamento podem ser malformadas lexicamente. Portanto, a gramática não está pronta para ANTLR, pois suporta apenas a entrada lexicamente correta. Nota final
Exemplo: O exemplo a seguir ilustra como as diretivas de compilação condicional podem aninhar:
#define Debug // Debugging on #undef Trace // Tracing off class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #if Trace WriteToLog(this.ToString()); #endif #endif CommitHelper(); } ... }
Exceto para diretivas de pré-processamento, o código-fonte ignorado não está sujeito à análise lexical. Por exemplo, o seguinte é válido apesar do comentário não terminado na
#else
seção:#define Debug // Debugging on class PurchaseTransaction { void Commit() { #if Debug CheckConsistency(); #else /* Do something else #endif } ... }
Note, no entanto, que as diretivas de pré-processamento devem ser lexicamente corretas, mesmo em seções ignoradas do código-fonte.
As diretivas de pré-processamento não são processadas quando aparecem dentro de elementos de entrada de várias linhas. Por exemplo, o programa:
class Hello { static void Main() { System.Console.WriteLine(@"hello, #if Debug world #else Nebraska #endif "); } }
Resultados na saída:
hello, #if Debug world #else Nebraska #endif
Em casos peculiares, o conjunto de diretivas de pré-processamento que é processado pode depender da avaliação do pp_expression. O exemplo:
#if X /* #else /* */ class Q { } #endif
sempre produz o mesmo fluxo de token (
class
Q
{
}
), independentemente de estar ou nãoX
definido. SeX
for definido, as únicas diretivas processadas são#if
e#endif
, devido ao comentário de várias linhas. SeX
estiver indefinida, então três diretivas (#if
,#else
,#endif
) fazem parte do conjunto de diretivas.Exemplo final
6.5.6 Diretivas de diagnóstico
As diretivas de diagnóstico são usadas para gerar explicitamente mensagens de erro e aviso que são relatadas da mesma forma que outros erros e avisos em tempo de compilação.
fragment PP_Diagnostic
: 'error' PP_Message?
| 'warning' PP_Message?
;
fragment PP_Message
: PP_Whitespace Input_Character*
;
Exemplo: O exemplo
#if Debug && Retail #error A build can't be both debug and retail #endif class Test {...}
produz um erro em tempo de compilação ("Uma compilação não pode ser depuração e varejo") se os símbolos
Debug
de compilação condicional eRetail
ambos estiverem definidos. Observe que um PP_Message pode conter texto arbitrário, especificamente, ele não precisa conter tokens bem formados, como mostra a única citação na palavracan't
.Exemplo final
6.5.7 Diretivas regionais
As diretivas de região são usadas para marcar explicitamente regiões do código-fonte.
fragment PP_Region
: PP_Start_Region
| PP_End_Region
;
fragment PP_Start_Region
: 'region' PP_Message?
;
fragment PP_End_Region
: 'endregion' PP_Message?
;
Nenhum significado semântico é atribuído a uma região; As regiões destinam-se a ser utilizadas pelo programador ou por ferramentas automatizadas para marcar uma secção do código-fonte. Haverá uma #endregion
diretiva correspondente a cada #region
diretiva. A mensagem especificada em uma #region
ou #endregion
diretiva também não tem significado semântico, apenas serve para identificar a região. A correspondência #region
e #endregion
as diretivas podem ter diferentes PP_Messages.
O processamento lexical de uma região:
#region
...
#endregion
corresponde exatamente ao processamento lexical de uma diretiva de compilação condicional da forma:
#if true
...
#endif
Nota: Isso significa que uma região pode incluir um ou mais
#if
/.../#endif
, ou estar contida com uma seção condicional dentro de um#if
/.../#endif
, mas uma região não pode se sobrepor a uma apenas parte de um#if
/.../#endif
, ou iniciar e terminar em diferentes seções condicionais. Nota final
6.5.8 Diretivas de linha
As diretivas de linha podem ser usadas para alterar os números de linha e nomes de unidades de compilação que são relatados por um compilador na saída, como avisos e erros. Esses valores também são usados pelos atributos caller-info (§22.5.6).
Nota: As diretivas de linha são mais comumente usadas em ferramentas de metaprogramação que geram código-fonte C# a partir de alguma outra entrada de texto. Nota final
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' | '"')
;
Quando não há diretivas #line
presentes, um compilador reporta números de linha verdadeiros e nomes de unidades de compilação na sua saída. Ao processar uma diretiva #line
que inclui um PP_Line_Indicator que não é default
, um compilador trata a linha após diretiva como tendo o número de linha fornecido (e nome da unidade de compilação, se especificado).
O valor máximo permitido é definido pela Decimal_Digit+
implementação.
Uma #line default
diretiva anula o efeito de todas as diretivas anteriores #line
. Um compilador relata informações de linha precisas para linhas subsequentes, precisamente como se nenhuma diretiva #line
tivesse sido processada.
Uma #line hidden
diretiva não tem qualquer efeito sobre a unidade de compilação e os números de linha comunicados em mensagens de erro ou produzidos pela utilização de CallerLineNumberAttribute
(§22.5.6.2). Destina-se a afetar as ferramentas de depuração no nível do código-fonte para que, durante a depuração, todas as linhas entre uma #line hidden
diretiva e a diretiva subsequente #line
(que não #line hidden
é) não tenham informações de número de linha e sejam ignoradas inteiramente ao percorrer o código.
Nota: Embora um PP_Compilation_Unit_Name possa conter texto que se parece com uma sequência de fuga, tal texto não é uma sequência de fuga, neste contexto, um caractere '' simplesmente designa um caractere de
\
barra invertida comum. Nota final
6.5.9 Diretiva anulável
A diretiva anulável controla o contexto anulável, conforme descrito abaixo.
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'
;
Uma diretiva anulável define os sinalizadores disponíveis para linhas de código subsequentes, até que outra diretiva anulável a substitua, ou até que o final do _unit de compilação seja alcançado. O contexto anulável contém dois sinalizadores: anotações e avisos. O efeito de cada forma de diretiva anulável é o seguinte:
-
#nullable disable
: Desabilita as anotações anuláveis e os sinalizadores de avisos anuláveis. -
#nullable enable
: Permite anotações anuláveis e sinalizadores de avisos anuláveis. -
#nullable restore
: Restaura as anotações e os sinalizadores de avisos para o estado especificado pelo mecanismo externo, se houver. -
#nullable disable annotations
: Desabilita o sinalizador de anotações anuláveis. O sinalizador de avisos anuláveis não é afetado. -
#nullable enable annotations
: Habilita o sinalizador de anotações anuláveis. O sinalizador de avisos anuláveis não é afetado. -
#nullable restore annotations
: Restaura o sinalizador de anotações anuláveis para o estado especificado pelo mecanismo externo, se houver. O sinalizador de avisos anuláveis não é afetado. -
#nullable disable warnings
: Desativa o sinalizador de avisos anuláveis. O sinalizador de anotações anuláveis não é afetado. -
#nullable enable warnings
: Habilita o sinalizador de avisos anuláveis. O sinalizador de anotações anuláveis não é afetado. -
#nullable restore warnings
: Restaura o sinalizador de avisos anuláveis para o estado especificado pelo mecanismo externo, se houver. O sinalizador de anotações anuláveis não é afetado.
O estado anulável das expressões é rastreado em todos os momentos. O estado do sinalizador de anotação e a presença ou ausência de uma anotação anulável, ?
, determina o estado nulo inicial de uma declaração variável. Os avisos só são emitidos quando o sinalizador de avisos está ativado.
Exemplo: O exemplo
#nullable disable string x = null; string y = ""; #nullable enable Console.WriteLine(x.Length); // Warning Console.WriteLine(y.Length);
produz um aviso em tempo de compilação ("como
x
estánull
"). O estado nulo de é rastreadox
em todos os lugares. Um aviso é emitido quando o sinalizador de avisos está ativado.Exemplo final
6.5.10 Diretivas Pragma
A #pragma
diretiva de pré-processamento é usada para especificar informações contextuais para um compilador.
Nota: Por exemplo, um compilador pode fornecer
#pragma
diretivas que
- Habilite ou desative mensagens de aviso específicas ao compilar o código subsequente.
- Especifique quais otimizações devem ser aplicadas ao código subsequente.
- Especifique as informações a serem usadas por um depurador.
Nota final
fragment PP_Pragma
: 'pragma' PP_Pragma_Text?
;
fragment PP_Pragma_Text
: PP_Whitespace Input_Character*
;
Os Input_Characters no PP_Pragma_Text são interpretados por um compilador de uma maneira definida pela implementação. As informações fornecidas numa #pragma
diretiva não devem alterar a semântica do programa. Uma #pragma
diretiva só deve alterar o comportamento do compilador que esteja fora do escopo desta especificação de linguagem. Se um compilador não pode interpretar o Input_Characters, um compilador pode produzir um aviso; no entanto, não deve produzir um erro em tempo de compilação.
Nota: PP_Pragma_Text pode conter texto arbitrário, especificamente, não precisa conter tokens bem formados. Nota final
ECMA C# draft specification