Compartilhar via


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:

  1. Transformação, que converte um arquivo de um repertório de caracteres específico e esquema de codificação em uma sequência de caracteres Unicode.
  2. Análise lexical, que converte um fluxo de caracteres de entrada Unicode em um fluxo de tokens.
  3. Análise sintática, que traduz o fluxo de tokens em código executável.

As implementações em conformidade devem aceitar unidades de compilação Unicode codificadas com a forma 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 de codificação de caracteres adicionais (como UTF-16, UTF-32 ou mapeamentos de caracteres não Unicode).

Observação: 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 em um caractere ou literal de cadeia de caracteres, as sequências de \0 escape ou \u0000 podem ser usadas em seu lugar. nota final

Nota: Está além do escopo desta especificação definir como um arquivo usando uma representação de caractere diferente de Unicode pode ser transformado em uma sequência de caracteres Unicode. Durante essa transformação, no entanto, recomenda-se que o caractere de separação de linha usual (ou sequência) no outro conjunto de caracteres seja convertido 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 maioria das vezes, essa transformação não terá efeitos visíveis; no entanto, isso afetará a interpretação de tokens literais de cadeia de caracteres textuais (§6.4.5.6). A finalidade dessa 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 dão suporte a 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 Geral

Essa 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 lexicais e sintáticas são apresentadas na forma Extended Backus-Naur da ferramenta de gramática ANTLR.

Embora a notação ANTLR seja usada, essa especificação não apresenta uma "gramática de referência" completa pronta para ANTLR para C#; escrever um léxico e um analisador, manualmente ou usando uma ferramenta como o 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 um analisador em ANTLR.

A ANTLR distingue entre gramáticas lexicais e sintáticas, denominadas por ANTLR, em sua notação, iniciando as regras lexicais com uma letra maiúscula e as regras do analisador sintático com uma letra minúscula.

Observação: a gramática lexical C# (§6.2.3) e a gramática sintática (§6.2.4) não estão em correspondência exata com a divisão ANTLR em gramáticas lexicais e analisadoras. 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 em §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 tokens na gramática lexical. Em vez disso, aproveita-se o comportamento ANTLR de que strings 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 de token.

Cada unidade de compilação em um 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 tokens definidos explicitamente pela gramática lexical e implicitamente por strings 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 em um 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 uma chamada para F com dois argumentos, G < A e B > (7). Como alternativa, pode ser interpretado como uma chamada para F com um argumento, que é uma chamada para um método G genérico com dois argumentos de tipo e um argumento regular.

exemplo de fim

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 > será examinado, para ver se é

  • Um dos ( ) ] } : ; , . ? == != | ^ && || & [; ou
  • Um dos operadores < <= >= is asrelacionais ; ou
  • Uma palavra-chave de consulta contextual que aparece dentro de uma expressão de consulta; ou
  • Em determinados contextos, o identificador é tratado como um token de desambiguação. Esses contextos são onde a sequência de tokens que está sendo desambiguada é imediatamente precedida por uma das palavras-chave is, case ou out, ou surge durante a análise do primeiro elemento de um literal de tupla (nesse caso, os tokens são precedidos por ( or : e o identificador é seguido por um ,) ou um elemento subsequente de um literal de tupla.

Se o token a seguir estiver nessa lista ou for um identificador nesse contexto, o type_argument_list será retido 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.

Observação: essas regras não são aplicadas ao analisar um type_argument_list em um namespace_or_type_name (§7.8). nota final

Exemplo: A instrução:

F(G<A, B>(7));

será, de acordo com essa regra, interpretada como uma chamada para F com um argumento, que é uma chamada para um método G genérico com dois argumentos de tipo e um argumento regular. As declarações

F(G<A, B>7);
F(G<A, B>>7);

cada um será interpretado como uma chamada para F com dois argumentos. Instrução

x = F<A> + y;

será interpretado como um operador menor que, um operador maior que e um 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. Na declaração

x = y is C<T> && z;

Os tokens C<T> são interpretados como um namespace_or_type_name com um type_argument_list devido à presença do token && de desambiguação 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 é uma out declaração.

A expressão e is A<B> C usa um padrão de declaração.

O rótulo de maiúsculas case A<B> C: e minúsculas usa um padrão de declaração.

exemplo de fim

Ao reconhecer um relational_expression (§12.12.1), se as alternativas "tipo relational_expression is " e "relational_expression is constant_pattern" forem aplicáveis e o tipo for resolvido para um tipo acessível, a alternativa "tipo relational_expressionis" deverá ser escolhida.

6.3 Análise lexical

6.3.1 Geral

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 essas sejam regras lexer, esses nomes são escritos em letras maiúsculas para distingui-los dos nomes comuns das regras lexer.

Observação: 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 strings 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
    ;

Observação: a gramática acima é descrita pelas regras de análise do 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 os 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 as 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 que um único / token. exemplo de fim

Alguns tokens são definidos por um conjunto de regras lexicais; uma regra principal e uma ou mais sub-regras. Estes últimos são marcados na gramática por fragment para 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 exibida 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) será 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), um avanço de linha (U+000A), um caractere de próxima linha (U+0085), um separador de linha (U+2028) ou um separador de parágrafo (U+2029).

Observação: o retorno de carro adicional permite que um programa termine em um PP_Directive (§6.5) que não tenha um New_Line de terminação. nota final

6.3.3 Comentários

Há suporte para 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 */. Os 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 de fim

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 de fim

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 podem ser aninhados. As sequências de caracteres /* e */ não têm nenhum significado especial dentro de um comentário de linha única, e as sequências de caracteres // e /* não têm nenhum significado especial dentro de um comentário delimitado.

Os comentários não são processados em literais de caractere e cadeia de caracteres.

Nota: Estas regras devem ser interpretadas com cuidado. Por exemplo, no exemplo abaixo, o comentário delimitado que começa antes A termina entre B e C(). A razão é que

// B */ C();

não é realmente um comentário de linha única, uma vez que // não tem nenhum significado especial dentro de um comentário delimitado e, portanto */ , tem seu significado especial usual nessa linha.

Da mesma forma, o comentário delimitado que começa antes D termina antes de E. O motivo é que não é realmente um literal de string, já que "D */ " o caractere inicial de aspas duplas aparece dentro de um comentário delimitado.

Uma consequência útil e /* */ não tendo nenhum significado especial em 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 e */ depois dessas linhas, pois isso não encapsula adequadamente os comentários delimitados no bloco e, em geral, pode alterar completamente a estrutura de tais comentários delimitados.

Exemplo de código:

static void Main()
{
    /* A
    // B */ C();
    Console.WriteLine(/* "D */ "E");
}

nota final

Single_Line_Comment 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

O 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 Tokens

6.4.1 Geral

Existem vários tipos de tokens: identificadores, palavras-chave, literais, operadores e pontuadores. O espaço em branco e os comentários não são tokens, embora atuem como separadores de tokens.

token
    : identifier
    | keyword
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | operator_or_punctuator
    ;

Observação: esta é uma regra do analisador ANTLR, ela 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 de caracteres regulares (§6.4.5.6) e expressões de cadeia de caracteres regulares 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 caractere e cadeia 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 têm suporte.

Não são realizadas várias traduções. Por exemplo, o literal "\u005Cu005C" de cadeia de caracteres é 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 \u0066escape para a letra "f". O programa é equivalente a

class Class1
{
    static void Test(bool f)
    {
        char c = 'f';
        if (f)
        {
            System.Console.WriteLine(c.ToString());
        }
    }
}

exemplo de fim

6.4.3 Identificadores

As regras para identificadores fornecidas nesta subcláusula correspondem exatamente àquelas recomendadas pelo Anexo Padrão Unicode 15, exceto que o sublinhado é permitido como um caractere inicial (como é tradicional na linguagem de programação C), as 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
    ;

Observação:

  • 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 for processada com ANTLR, essa exclusão será tratada automaticamente pela semântica de ANTLR:
    • Palavras-chave e palavras-chave contextuais ocorrem na gramática como strings literais.
    • O ANTLR cria regras de token lexical implícitas 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 que o precedem.
  • O fragmento Escaped_Identifier inclui palavras-chave de escape e palavras-chave contextuais, pois elas fazem parte do token mais longo começando com um @ e o processamento lexical sempre forma o elemento lexical mais longo possível (§6.3.1).
  • Como uma implementação impõe as restrições aos valores de Unicode_Escape_Sequence permitidos é uma questão de implementação.

nota final

Exemplo: Exemplos de identificadores válidos são identifier1, _identifier2, e @if. exemplo de fim

Um identificador em um programa em conformidade deve estar no formato canônico definido pelo Formulário de Normalização Unicode C, conforme definido pelo Anexo Padrão Unicode 15. O comportamento ao encontrar um identificador que não está no Formulário de Normalização C é definido pela implementação; no entanto, um diagnóstico não é necessário.

O prefixo "@" permite o uso de palavras-chave como identificadores, o que é útil ao fazer interface com outras linguagens de programação. O caractere @ não é realmente parte do identificador, portanto, 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 recebe 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 de fim

Dois identificadores serão considerados iguais se forem idênticos após a aplicação das seguintes transformações, em ordem:

  • O prefixo "@", se usado, é removido.
  • Cada Unicode_Escape_Sequence é transformado em seu caractere Unicode correspondente.
  • Todos os Formatting_Charactersão removidos.

A semântica de um identificador nomeado _ depende do contexto em que ele aparece:

  • Ele pode denotar um elemento de programa nomeado, como uma variável, classe ou método, ou
  • Pode denotar um descarte (§9.2.9.1).

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 esse identificador for definido.

Observação: 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 precedida @ 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'
    ;

Observação: a palavra-chave e o contextual_keyword rules são regras do 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, pois ocorrem como strings 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 comum do identificador. Por exemplo, em uma declaração de propriedade, os get identificadores e set têm um significado especial (§15.7.3). Um identificador diferente get ou 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 dos 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 uma palavra-chave contextual.

Outro exemplo de desambiguação é a palavra-chave await contextual (§12.9.8.1), que é considerada uma palavra-chave somente quando dentro de um método declarado async, mas pode ser usada como um identificador em outro lugar.

Assim como acontece com as palavras-chave, as palavras-chave contextuais podem ser usadas como identificadores comuns, prefixando-as com o @ caractere.

Observação: 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 Geral

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 do analisador, pois agrupa outros tipos de token e não introduz um novo tipo de token. nota final

6.4.5.2 Literais booleanos

Há dois valores literais booleanos: true e false.

boolean_literal
    : TRUE
    | FALSE
    ;

Observação: boolean_literal é uma regra do analisador, pois agrupa outros tipos de token e não introduz um novo tipo de token. nota final

O tipo de um boolean_literal é bool.

6.4.5.3 Literais inteiros

Literais inteiros são usados para escrever valores dos tipos int, uint, longe 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 maneira:

  • Se o literal não tiver sufixo, ele terá o primeiro desses tipos no qual seu valor pode ser representado: int, uint, long, ulong.
  • Se o literal tiver o sufixo ou U u, ele terá o primeiro desses tipos no qual seu valor pode ser representado: uint, ulong.
  • Se o literal tiver o sufixo ou L l, ele terá o primeiro desses tipos no qual seu valor pode ser representado: long, ulong.
  • Se o literal tiver o sufixo , ULUl, uL, ulLU, , Lu, , lU, ou lu, é do tipo ulong.

Se o valor representado por um literal inteiro estiver fora do intervalo do tipo, ocorrerá um erro em ulong tempo de compilação.

Nota: Por uma questão de estilo, sugere-se que "L" seja usado em vez de "l" ao escrever literais do tipo long, 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 de 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, esse Integer_Literal é do tipo uint.
  • Quando um Integer_Literal representando o valor 9223372036854775808 (2⁶³) e nenhum Integer_Type_Suffix ou o Integer_Type_Suffix L ou l aparece como o token imediatamente após um token de operador de menos unário (§12.9.3), o resultado (de ambos os tokens) é uma constante do tipo long com o valor −9223372036854775808 (−2⁶³). Em todas as outras situações, esse Integer_Literal é do tipo ulong.

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 de fim

6.4.5.4 Literais reais

Literais reais são usados para escrever valores dos tipos float, doublee 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 do Real_Literal será double. Caso contrário, o Real_Type_Suffix determina o tipo do literal real, da seguinte maneira:

  • Um sufixo F literal real por ou f é do tipo float.

    Exemplo: Os literais 1f, 1.5f, 1e10f, e 123.456F são todos do tipo float. exemplo de fim

  • Um sufixo D literal real por ou d é do tipo double.

    Exemplo: Os literais 1d, 1.5d, 1e10d, e 123.456D são todos do tipo double. exemplo de fim

  • Um sufixo M literal real por ou m é do tipo decimal.

    Exemplo: Os literais 1m, 1.5m, 1e10m, e 123.456M são todos do tipo decimal. exemplo de fim
    Esse literal é convertido em um decimal valor tomando o valor exato e, se necessário, arredondando para o valor representável mais próximo usando o arredondamento bancário (§8.3.8). Qualquer escala aparente no literal é preservada, a menos que o valor seja arredondado. Nota: Portanto, o literal 2.900m será analisado para formar o decimal sinal 0with , coeficiente 2900e escala 3. 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. No entanto, um Real_Literal diferente de zero pode ser arredondado para zero. nota final

O valor de um literal real do tipo float or double é determinado usando o modo IEC 60559 "arredondar para o mais próximo" com laços quebrados para "par" (um valor com o bit menos significativo zero) e todos os dígitos considerados significativos.

Nota: Em um literal real, os dígitos decimais são sempre necessários após o ponto decimal. Por exemplo, é um literal real, 1.3F mas 1.F não é. nota final

Exemplo:

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 de fim

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: ', ", , auUx0fvbnrt\ Caso contrário, ocorrerá um erro em tempo de compilação. nota final

Nota: O uso da \x produção 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 à primeira vista que o caractere principal é o mesmo (U+0009, um caractere de tabulação) em ambas as strings. Na verdade, a segunda string começa com U+9BAD , pois todas as três letras da palavra "Bad" são dígitos hexadecimais válidos. Por uma questão de estilo, recomenda-se que \x seja evitado 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) em um literal de caracteres 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 escape Nome do personagem Ponto de código Unicode
\' Aspas simples U+0027
\" Aspas duplas U+0022
\\ Barra invertida U+005C
\0 Nulo U+0000
\a Alerta U+0007
\b Backspace U+0008
\f Avanço de formulário U+000C
\n Nova linha U+000A
\r Retorno de carro U+000D
\t Guia horizontal U+0009
\v Guia vertical U+000B

O tipo de um Character_Literal é char.

6.4.5.6 Literais de cadeia de caracteres

O C# dá suporte a duas formas de literais de cadeia de caracteres: literais de cadeia de caracteres regulares e literais de cadeia de caracteres textuais. 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 de fim

Em um literal de cadeia de caracteres literal, os caracteres entre os delimitadores são interpretados literalmente, 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 caracteres de nova linha, são preservados literalmente e cada par de caracteres de aspas duplas é substituído por um desses caracteres.

exemplo de fim

Observação: Todas as quebras de linha em literais de cadeia de caracteres textuais fazem parte da cadeia de caracteres resultante. Se os caracteres exatos usados para formar quebras de linha forem semanticamente relevantes para um aplicativo, todas as ferramentas que traduzem 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

Observação: como uma sequência de escape hexadecimal pode ter um número variável de dígitos hexadecimais, o literal "\x123" da string contém um único caractere com valor 123hexadecimal. Para criar uma string contendo o caractere com valor 12 hexadecimal seguido pelo caractere 3, pode-se escrever "\x00123" ou "\x12" + "3" . nota final

O tipo de um String_Literal é string.

Cada literal de cadeia de caracteres não resulta necessariamente em uma nova instância de cadeia de caracteres. Quando dois ou mais literais de cadeia de caracteres 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 se referem à mesma instância de cadeia de caracteres.

Exemplo: Por exemplo, a saída 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 instância de string.

exemplo de fim

6.4.5.7 O literal nulo

null_literal
    : NULL
    ;

Observação: null_literal é uma regra de analisador, pois não introduz um novo tipo de token. nota final

Um null_literal representa um null valor. Ele não tem um tipo, mas pode ser convertido em qualquer tipo de referência ou tipo de valor anulável por meio de uma conversão literal nula (§10.2.7).

6.4.6 Operadores e pontuadores

Há vários tipos de operadores e pontuadores. Os operadores são usados em expressões para descrever as operações que envolvem um ou mais operandos.

Exemplo: a expressão a + b usa o + operador para adicionar os dois operandos a e b. exemplo de fim

Os pontuadores servem para agrupamento e separação.

operator_or_punctuator
    : '{'  | '}'  | '['  | ']'  | '('   | ')'  | '.'  | ','  | ':'  | ';'
    | '+'  | '-'  | ASTERISK    | SLASH | '%'  | '&'  | '|'  | '^'  | '!' | '~'
    | '='  | '<'  | '>'  | '?'  | '??'  | '::' | '++' | '--' | '&&' | '||'
    | '->' | '==' | '!=' | '<=' | '>='  | '+=' | '-=' | '*=' | '/=' | '%='
    | '&=' | '|=' | '^=' | '<<' | '<<=' | '=>'
    ;

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Observação: right_shift e right_shift_assignment são regras do 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. Essas produções são tratadas especialmente para permitir o manuseio correto de type_parameter_lists (§15.2.3).

Observação: 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. Geralmente, é desejável usar tipos construídos aninhados, como List<Dictionary<string, int>>. Em vez de exigir que o programador separe 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 Geral

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
    ;

Observação:

  • 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 predicado getCharPositionInLine() == 0 lexical 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

As seguintes diretivas de pré-processamento estão disponíveis:

  • #define e #undef, que são usados para definir e desdefinir, respectivamente, símbolos de compilação condicional (§6.5.4).
  • #if, #elif, #else, e #endif, que são usados para ignorar condicionalmente seções do código-fonte (§6.5.5).
  • #line, que é usado para controlar os números de linha emitidos para erros e avisos (§6.5.8).
  • #error, que é usado para emitir erros (§6.5.6).
  • #region e #endregion, que são usados para marcar explicitamente seções do código-fonte (§6.5.7).
  • #nullable, que é usado 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. Pode ocorrer espaço em branco antes do # caractere e entre o # caractere e o nome da diretiva.

Uma linha de origem contendo uma #definediretiva , #undef, #if, #else#elif, #endif, #line, ou #nullable #endregionpode terminar com um comentário de linha única. Comentários delimitados (o /* */ estilo dos comentários) não são permitidos em linhas de origem contendo 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 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 lexicalmente, os dois programas são bastante diferentes, sintaticamente, eles são idênticos.

exemplo de fim

6.5.2 Símbolos de compilação condicional

A funcionalidade de compilação condicional fornecida pelas #ifdiretivas , #elif, #else, e #endif é controlada por meio 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 serão considerados iguais se forem idênticos após a aplicação das seguintes transformações, 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 é 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 atingido. Uma implicação disso é que #define as diretivas e #undef 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 truebooleano e um símbolo de compilação condicional indefinido tem o valor falsebooleano . Não há nenhum requisito de que os símbolos de compilação condicional sejam declarados explicitamente antes de serem referenciados em expressões de pré-processamento. Em vez disso, os 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 Pré-processamento de expressões

Expressões de pré-processamento podem ocorrer em #if diretivas e #elif . 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 truebooleano e um símbolo de compilação condicional indefinido tem o valor falsebooleano .

A avaliação de uma expressão de pré-processamento sempre produz um valor booliano. As regras de avaliação para uma expressão de pré-processamento são as mesmas de 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 não definir 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 fornecido seja 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 fornecido se torne indefinido, começando com a linha de origem que segue a diretiva.

As diretivas any #define and #undef em uma unidade de compilação devem ocorrer antes do primeiro token (§6.4) na unidade de compilação; caso contrário, ocorrerá um erro em tempo de compilação. Em termos intuitivos, #define as diretivas devem #undef 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 (a namespace palavra-chave) na unidade de compilação.

exemplo de fim

Exemplo: o exemplo a seguir resulta em um erro de tempo de compilação porque um #define segue o código real:

#define A
namespace N
{
#define B
#if B
    class Class1 {}
#endif
}

exemplo de fim

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 de tal redefinição ocorrer é definir o símbolo como uma opção do compilador, bem como na origem.

exemplo de fim

A #undef pode "desdefinir" 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, em seguida, o desdefine duas vezes; embora o segundo #undef não tenha efeito, ele ainda é válido.

#define A
#undef A
#undef A

exemplo de fim

6.5.5 Diretivas de compilação condicional

As diretivas de compilação condicional são usadas para incluir ou excluir condicionalmente 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 por, em ordem, 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 seção é controlada pela diretiva imediatamente anterior. Uma seção condicional pode conter diretivas de compilação condicionais aninhadas, desde que essas diretivas formem grupos completos.

No máximo, uma das seções condicionais contidas é selecionada para processamento lexical normal:

  • Os PP_Expression s das #if diretivas e #elif são avaliados em ordem até que se produza true. Se uma expressão produzir true, a seção condicional após a diretiva correspondente será selecionada.
  • Se todos os PP_Expressionrendem false, e se uma #else diretiva estiver presente, a seção condicional após a #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 estar lexicalmente incorreto. As diretivas de pré-processamento ignoradas devem ser lexicalmente 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 captura a permissão de que as seções condicionais entre as diretivas de pré-processamento podem estar malformadas lexicalmente. Portanto, a gramática não está pronta para ANTLR, pois suporta apenas entrada lexicalmente correta. nota final

Exemplo: o exemplo a seguir ilustra como as diretivas de compilação condicional podem ser aninhadas:

#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
    }
    ...
}

Observe, no entanto, que as diretivas de pré-processamento devem estar lexicalmente 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
        ");
    }
}

resulta na saída:

hello,
#if Debug
        world
#else
        Nebraska
#endif

Em casos peculiares, o conjunto de diretivas de pré-processamento 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ão X definido. Se X for definido, as únicas diretivas processadas são #if e #endif, devido ao comentário de várias linhas. Se X for indefinido, três diretivas (#if, #else, #endif) farão parte do conjunto de diretivas.

exemplo de fim

6.5.6 Diretrizes 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 de tempo de compilação ("Um build não pode ser depuração e varejo") se os símbolos Debug de compilação condicional e Retail estiverem definidos. Observe que um PP_Message pode conter texto arbitrário; especificamente, ele não precisa conter tokens bem formados, como mostrado pelas aspas simples na palavra can't.

exemplo de fim

6.5.7 Diretivas regionais

As diretivas region 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 ao uso pelo programador ou por ferramentas automatizadas para marcar uma seção do código-fonte. Deve haver uma #endregion directiva que corresponda a cada #region directiva. A mensagem especificada em uma #region diretiva ou #endregion também não tem significado semântico; serve apenas para identificar a região. Correspondência #region e #endregion diretivas podem ter PP_Message diferentes.

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 parte justa de um #if/.../#endif, ou começar 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 os nomes de unidade de compilação relatados pelo compilador na saída, como avisos e erros. Esses valores também são usados por atributos caller-info (§22.5.6).

Observação: as diretivas de linha são mais comumente usadas em ferramentas de metaprogramação que geram código-fonte C# 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 nenhuma #line diretiva está presente, o compilador relata números de linha verdadeiros e nomes de unidade de compilação em sua saída. Ao processar uma #line diretiva que inclui um PP_Line_Indicator que não defaulté, o compilador trata a linha após a diretiva como tendo o número de linha fornecido (e o nome da unidade de compilação, se especificado).

O valor máximo permitido é definido pela Decimal_Digit+ implementação.

Uma #line default directiva anula o efeito de todas as directivas anteriores #line . O compilador relata informações de linha verdadeiras para linhas subsequentes, precisamente como se nenhuma #line diretiva tivesse sido processada.

Uma #line hidden diretiva não tem efeito sobre a unidade de compilação e os números de linha relatados em mensagens de erro ou produzidos pelo uso 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 (ou seja #line hidden, não ) 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 escape, esse texto não é uma sequência de escape; 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 da _unit de compilação seja atingido. 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 anotações anuláveis e sinalizadores de avisos anuláveis.
  • #nullable enable: Habilita anotações anuláveis e sinalizadores de avisos anuláveis.
  • #nullable restore: Restaura os sinalizadores de anotações e 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: Desabilita 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 o tempo todo. 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 de variável. Os avisos só são emitidos quando o sinalizador de avisos está habilitado.

Exemplo: O exemplo

#nullable disable
string x = null;
string y = "";
#nullable enable
Console.WriteLine(x.Length); // Warning
Console.WriteLine(y.Length);

produz um aviso de tempo de compilação ("no estado em que x se encontra null"). O estado anulável de x é rastreado em todos os lugares. Um aviso é emitido quando o sinalizador de avisos está habilitado.

exemplo de fim

6.5.10 Diretivas Pragma

A #pragma diretiva de pré-processamento é usada para especificar informações contextuais para um compilador.

Observação: por exemplo, um compilador pode fornecer #pragma diretivas que

  • Habilite ou desabilite mensagens de aviso específicas ao compilar o código subsequente.
  • Especifique quais otimizações aplicar 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 pelo compilador de maneira definida pela implementação. As informações fornecidas em uma #pragma diretiva não devem alterar a semântica do programa. Uma #pragma diretiva só deve alterar o comportamento do compilador que está fora do escopo dessa especificação de linguagem. Se o compilador não puder interpretar os Input_Characters, poderá produzir um aviso; no entanto, ele não 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