Compartir a través de


6 Estructura léxica

6.1 Programas

Un programa de C# consta de uno o varios archivos de código fuente, conocidos formalmente como unidades de compilación (§14.2). Aunque una unidad de compilación puede tener una correspondencia uno a uno con un archivo en un sistema de archivos, no se requiere dicha correspondencia.

En términos conceptuales, un programa se compila mediante tres pasos:

  1. Transformación, que convierte un archivo de un repertorio de caracteres determinado y un esquema de codificación en una secuencia de caracteres Unicode.
  2. Análisis léxico, que traduce una secuencia de caracteres de entrada Unicode en un flujo de tokens.
  3. Análisis sintáctico, que convierte el flujo de tokens en código ejecutable.

Las implementaciones conformes aceptarán unidades de compilación Unicode codificadas con el formulario de codificación UTF-8 (definido por el estándar Unicode) y los transformarán en una secuencia de caracteres Unicode. Las implementaciones pueden optar por aceptar y transformar esquemas de codificación de caracteres adicionales (como asignaciones de caracteres UTF-16, UTF-32 o no Unicode).

Nota: El control del carácter NULL Unicode (U+0000) está definido por la implementación. Se recomienda encarecidamente que los desarrolladores eviten usar este carácter en su código fuente, por motivos de portabilidad y legibilidad. Cuando se requiere el carácter dentro de un carácter o literal de cadena, las secuencias \0 de escape o \u0000 se pueden usar en su lugar. nota final

Nota: Queda fuera del ámbito de esta especificación definir cómo un archivo usa una representación de caracteres distinta de Unicode podría transformarse en una secuencia de caracteres Unicode. Sin embargo, durante esta transformación, se recomienda que el carácter de separación de líneas (o secuencia) habitual en el otro juego de caracteres se traduzca en la secuencia de dos caracteres que consta del carácter de retorno de carro Unicode (U+000D) seguido del carácter de avance de línea Unicode (U+000A). En la mayor parte, esta transformación no tendrá efectos visibles; sin embargo, afectará a la interpretación de los tokens literales de cadena textual (§6.4.5.6). El propósito de esta recomendación es permitir que un literal de cadena textual genere la misma secuencia de caracteres cuando su unidad de compilación se mueve entre sistemas que admiten diferentes conjuntos de caracteres no Unicode, en particular, aquellos que usan secuencias de caracteres diferentes para la separación de líneas. nota final

6.2 Gramáticas

6.2.1 General

Esta especificación presenta la sintaxis del lenguaje de programación de C# mediante dos gramáticas. La gramática léxica (§6.2.3) define cómo se combinan los caracteres Unicode para formar terminadores de línea, espacios en blanco, comentarios, tokens y directivas de preprocesamiento. La gramática sintáctica (§6.2.4) define cómo se combinan los tokens resultantes de la gramática léxica para formar programas de C#.

Todos los caracteres de terminal deben entenderse como el carácter Unicode adecuado del intervalo U+0020 a U+007F, en lugar de cualquier carácter similar de otros intervalos de caracteres Unicode.

6.2.2 Notación gramatical

Las gramáticas léxicas y sintácticas se presentan en la forma de Backus-Naur extendida de la herramienta de gramática ANTLR.

Aunque se usa la notación ANTLR, esta especificación no presenta una "gramática de referencia" lista para ANTLR completa para C#; escribir un lexer y un analizador, ya sea a mano o mediante una herramienta como ANTLR, está fuera del ámbito de una especificación de lenguaje. Con esa calificación, esta especificación intenta minimizar la brecha entre la gramática especificada y la necesaria para crear un lexer y un analizador en ANTLR.

ANTLR distingue entre el analizador léxico y el sintáctico, a los que antlr denomina antlr, las gramáticas en su notación inician reglas léxicas con una letra léxica y reglas de analizador con una letra minúscula.

Nota: La gramática léxica de C# (§6.2.3) y la gramática sintáctica (§6.2.4) no están en correspondencia exacta con la división ANTLR en grammers léxicos y analizadores. Esta pequeña falta de coincidencia significa que se usan algunas reglas del analizador ANTLR al especificar la gramática léxica de C#. nota final

6.2.3 Gramática léxica

La gramática léxica de C# se presenta en §6.3, §6.4 y §6.5. Los símbolos terminales de la gramática léxica son los caracteres del juego de caracteres Unicode y la gramática léxica especifica cómo se combinan los caracteres para formar tokens (§6.4), espacios en blanco (§6.3.4), comentarios (§6.3.3) y directivas de preprocesamiento (§6.5).

Muchos de los símbolos terminales de la gramática sintáctica no se definen explícitamente como tokens en la gramática léxica. En su lugar, se aprovecha el comportamiento ANTLR que las cadenas literales de la gramática se extraen como tokens léxicos implícitos; esto permite que las palabras clave, los operadores, etc. se representen en la gramática mediante su representación literal en lugar de un nombre de token.

Cada unidad de compilación de un programa de C# se ajustará a la producción de entrada de la gramática léxica (§6.3.1).

6.2.4 Gramática sintáctica

La gramática sintáctica de C# se presenta en las cláusulas, subclases y anexos que siguen a esta subclausa. Los símbolos terminales de la gramática sintáctica son los tokens definidos explícitamente por la gramática léxica y implícitamente por cadenas literales en la propia gramática (§6.2.3). La gramática sintáctica especifica cómo se combinan los tokens para formar programas de C#.

Cada unidad de compilación de un programa de C# se ajustará a la compilation_unit producción (§14.2) de la gramática sintáctica.

6.2.5 Ambigüedades gramaticales

Las producciones de simple_name (§12.8.4) y member_access (§12.8.7) pueden dar lugar a ambigüedades en la gramática de las expresiones.

Ejemplo: La instrucción :

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

podría interpretarse como una llamada a F con dos argumentos y G < A B > (7). Como alternativa, podría interpretarse como una llamada a F con un argumento, que es una llamada a un método G genérico con dos argumentos de tipo y un argumento normal.

ejemplo final

Si se puede analizar una secuencia de tokens (en contexto) como una simple_name (§12.8.4), member_access (§12.8.7) o pointer_member_access (§23.6.3) que termina con un type_argument_list (§8.4.2), se examina el token inmediatamente después del token de cierre>, para ver si es así.

  • Uno de ( ) ] } : ; , . ? == != | ^ && || & [; o
  • Uno de los operadores relacionales < <= >= is as; o
  • Una palabra clave de consulta contextual que aparece dentro de una expresión de consulta; o
  • En determinados contextos, el identificador se trata como un token de desambiguación. Esos contextos son donde la secuencia de tokens que se desambigua inmediatamente va precedida por una de las palabras clave is, case o out, o surge al analizar el primer elemento de un literal de tupla (en cuyo caso los tokens van precedidos de ( o : y el identificador va seguido de ) ,o un elemento posterior de un literal de tupla.

Si el siguiente token está entre esta lista o un identificador en este contexto, el type_argument_list se conserva como parte de la simple_name, se descarta el acceso member_access o pointer_member y cualquier otro análisis posible de la secuencia de tokens. De lo contrario, el type_argument_list no se considera parte del simple_name, member_access o pointer_member_access, aunque no haya ningún otro análisis posible de la secuencia de tokens.

Nota: Estas reglas no se aplican al analizar un type_argument_list en un namespace_or_type_name (§7.8). nota final

Ejemplo: La instrucción :

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

se interpretará, según esta regla, como una llamada a F con un argumento, que es una llamada a un método G genérico con dos argumentos de tipo y un argumento normal. Instrucciones

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

cada uno se interpretará como una llamada a F con dos argumentos. La instrucción

x = F<A> + y;

se interpretará como un operador menor que, operador mayor que y operador unario-plus, como si la instrucción se hubiera escrito x = (F < A) > (+y), en lugar de como un simple_name con un type_argument_list seguido de un operador binario-plus. En la instrucción

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

los tokens C<T> se interpretan como un namespace_or_type_name con un type_argument_list debido a la presencia del token && de desambiguación después del type_argument_list.

La expresión (A < B, C > D) es una tupla con dos elementos, cada una de ellas una comparación.

La expresión (A<B,C> D, E) es una tupla con dos elementos, el primero de los cuales es una expresión de declaración.

La invocación M(A < B, C > D, E) tiene tres argumentos.

La invocación M(out A<B,C> D, E) tiene dos argumentos, el primero de los cuales es una out declaración.

La expresión e is A<B> C usa un patrón de declaración.

La etiqueta case A<B> C: case usa un patrón de declaración.

ejemplo final

Al reconocer una relational_expression (§12.12.1) si se aplican las alternativas "tipo relational_expressionis" y "relational_expression is constant_pattern" y el tipo se resuelve en un tipo accesible, se elegirá la alternativa "tipo relational_expression".is

6.3 Análisis léxico

6.3.1 General

Para mayor comodidad, la gramática léxica define y hace referencia a los siguientes tokens lexer con nombre:

DEFAULT  : 'default' ;
NULL     : 'null' ;
TRUE     : 'true' ;
FALSE    : 'false' ;
ASTERISK : '*' ;
SLASH    : '/' ;

Aunque estas son reglas de lexer, estos nombres se escriben en letras en mayúsculas para distinguirlos de los nombres de las reglas de lexer normales.

Nota: Estas reglas de conveniencia son excepciones a la práctica habitual de no proporcionar nombres de token explícitos para tokens definidos por cadenas literales. nota final

La producción de entrada define la estructura léxica de una unidad de compilación de C#.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Nota: La gramática anterior se describe mediante reglas de análisis ANTLR, define la estructura léxica de una unidad de compilación de C# y no tokens léxicos. nota final

Cinco elementos básicos componen la estructura léxica de una unidad de compilación de C#: terminadores de línea (§6.3.2), espacios en blanco (§6.3.4), comentarios (§6.3.3), tokens (§6.4) y directivas de preprocesamiento (§6.5). De estos elementos básicos, solo los tokens son significativos en la gramática sintáctica de un programa de C# (§6.2.4).

El procesamiento léxico de una unidad de compilación de C# consiste en reducir el archivo en una secuencia de tokens que se convierte en la entrada del análisis sintáctico. Los terminadores de línea, el espacio en blanco y los comentarios pueden servir para separar tokens y las directivas de preprocesamiento pueden hacer que se omitan secciones de la unidad de compilación, pero de lo contrario, estos elementos léxicos no tienen ningún impacto en la estructura sintáctica de un programa de C#.

Cuando varias producciones de gramática léxica coinciden con una secuencia de caracteres en una unidad de compilación, el procesamiento léxico siempre forma el elemento léxico más largo posible.

Ejemplo: La secuencia // de caracteres se procesa como el principio de un comentario de una sola línea porque ese elemento léxico es mayor que un solo / token. ejemplo final

Algunos tokens se definen mediante un conjunto de reglas léxicas; una regla principal y una o varias sub-reglas. Este último se marca en la gramática para fragment indicar que la regla define parte de otro token. Las reglas de fragmentos no se consideran en la ordenación de arriba a abajo de las reglas léxicas.

Nota: En ANTLR fragment es una palabra clave que genera el mismo comportamiento definido aquí. nota final

6.3.2 Terminadores de línea

Los terminadores de línea dividen los caracteres de una unidad de compilación de C# en líneas.

New_Line
    : New_Line_Character
    | '\u000D\u000A'    // carriage return, line feed 
    ;

Para la compatibilidad con las herramientas de edición de código fuente que agregan marcadores de fin de archivo y para permitir que una unidad de compilación se vea como una secuencia de líneas terminadas correctamente, se aplican las transformaciones siguientes, para cada unidad de compilación de un programa de C#:

  • Si el último carácter de la unidad de compilación es un carácter Control-Z (U+001A), se elimina este carácter.
  • Se agrega un carácter de retorno de carro (U+000D) al final de la unidad de compilación si esa unidad de compilación no está vacía y si el último carácter de la unidad de compilación no es un retorno de carro (U+000D), una fuente de línea (U+000A), un carácter de línea siguiente (U+0085), un separador de línea (U+2028) o un separador de párrafos (U+2029).

Nota: El retorno de carro adicional permite que un programa finalice en un PP_Directive (§6.5) que no tenga un New_Line de terminación. nota final

6.3.3 Comentarios

Se admiten dos formas de comentarios: comentarios delimitados y comentarios de una sola línea.

Un comentario delimitado comienza con los caracteres /* y termina con los caracteres */. Los comentarios delimitados pueden ocupar una parte de una línea, una sola línea o varias líneas.

Ejemplo: El ejemplo

/* Hello, world program
   This program writes "hello, world" to the console
*/
class Hello
{
    static void Main()
    {
        System.Console.WriteLine("hello, world");
    }
}

se incluye un comentario delimitado.

ejemplo final

Un comentario de una sola línea comienza con los caracteres // y se extiende hasta el final de la línea.

Ejemplo: El ejemplo

// 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");
    }
}

se muestran varios comentarios de una línea.

ejemplo 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
    ;

Los comentarios no se anidan. Las secuencias de caracteres /* y */ no tienen ningún significado especial en un comentario de una sola línea y las secuencias de caracteres // y /* tampoco lo tienen dentro de un comentario delimitado.

Los comentarios no se procesan en literales de caracteres y cadenas.

Nota: Estas reglas deben interpretarse cuidadosamente. Por ejemplo, en el ejemplo siguiente, el comentario delimitado que comienza antes de A que finalice entre B y C(). La razón es que

// B */ C();

no es realmente un comentario de una sola línea, ya que // no tiene ningún significado especial dentro de un comentario delimitado, por lo que */ tiene su significado especial habitual en esa línea.

Del mismo modo, el comentario delimitado que comienza antes de D que finalice antes de E. El motivo es que no es realmente un literal de cadena, ya que "D */ " el carácter de comilla doble inicial aparece dentro de un comentario delimitado.

Una consecuencia útil de /* y */ sin ningún significado especial dentro de un comentario de una sola línea es que se puede comentar un bloque de líneas de código fuente colocando // al principio de cada línea. En general, no funciona para colocar /* antes de esas líneas y */ después de ellas, ya que esto no encapsula correctamente los comentarios delimitados en el bloque y, en general, puede cambiar completamente la estructura de dichos comentarios delimitados.

Código de ejemplo:

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

nota final

Single_Line_Comment s y Delimited_Commentque tienen formatos concretos se pueden usar como comentarios de documentación, como se describe en §D.

6.3.4 Espacio en blanco

El espacio en blanco se define como cualquier carácter con la clase Unicode Zs (que incluye el carácter de espacio), así como el carácter de tabulación horizontal, el carácter de tabulación vertical y el carácter de fuente de formularios.

Whitespace
    : [\p{Zs}]  // any character with Unicode class Zs
    | '\u0009'  // horizontal tab
    | '\u000B'  // vertical tab
    | '\u000C'  // form feed
    ;

6.4 Tokens

6.4.1 General

Hay varios tipos de tokens: identificadores, palabras clave, literales, operadores y signos de puntuación. Los espacios en blanco y los comentarios no son tokens, aunque actúan como separadores para los tokens.

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

Nota: Se trata de una regla de analizador ANTLR, no define un token léxico, sino la colección de tipos de token. nota final

6.4.2 Secuencias de escape de caracteres Unicode

Una secuencia de escape Unicode representa un punto de código Unicode. Las secuencias de escape Unicode se procesan en identificadores (§6.4.3), literales de caracteres (§6.4.5.5), literales de cadena regulares (§6.4.5.6) y expresiones de cadena regulares interpoladas (§12.8.3). Una secuencia de escape Unicode no se procesa en ninguna otra ubicación (por ejemplo, para formar un operador, un signo de puntuación o una palabra clave).

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
    ;

Una secuencia de escape de caracteres Unicode representa el único punto de código Unicode formado por el número hexadecimal que sigue a los caracteres "\u" o "\U". Dado que C# usa una codificación de 16 bits de puntos de código Unicode en valores de caracteres y cadenas, se representa un punto de código Unicode en el intervalo U+10000 en U+10FFFF mediante dos unidades de código suplente Unicode. Los puntos de código Unicode anteriores U+FFFF no se permiten en literales de caracteres. Los puntos de código Unicode anteriores U+10FFFF no son válidos y no se admiten.

No se realizan varias traducciones. Por ejemplo, el literal "\u005Cu005C" de cadena es equivalente a "\u005C" en lugar de "\".

Nota: El valor \u005C Unicode es el carácter "\". nota final

Ejemplo: El ejemplo

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

muestra varios usos de , que es la secuencia de \u0066escape de la letra "f". El programa es equivalente a

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

ejemplo final

6.4.3 Identificadores

Las reglas para los identificadores proporcionados en esta subclausa corresponden exactamente a las recomendadas por el Anexo 15 estándar unicode, salvo que el carácter de subrayado se permite como carácter inicial (como es tradicional en el lenguaje de programación C), las secuencias de escape Unicode se permiten en identificadores y el carácter "@" se permite como prefijo para permitir que las palabras clave se usen 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 obtener información sobre las clases de caracteres Unicode mencionadas anteriormente, vea El estándar Unicode.
  • El fragmento Available_Identifier requiere la exclusión de palabras clave y palabras clave contextuales. Si la gramática de esta especificación se procesa con ANTLR, esta exclusión se controla automáticamente mediante la semántica de ANTLR:
    • Las palabras clave y las palabras clave contextuales se producen en la gramática como cadenas literales.
    • ANTLR crea reglas implícitas de token léxico a partir de estas cadenas literales.
    • ANTLR considera estas reglas implícitas antes de las reglas léxicas explícitas en la gramática.
    • Por lo tanto, el fragmento Available_Identifier no coincidirá con palabras clave ni palabras clave contextuales, ya que las reglas léxicas para las que preceden a ella.
  • El fragmento Escaped_Identifier incluye palabras clave de escape y palabras clave contextuales, ya que forman parte del token más largo a partir de un @ procesamiento léxico y léxico siempre forman el elemento léxico más largo posible (§6.3.1).
  • La forma en que una implementación aplica las restricciones en los valores de Unicode_Escape_Sequence permitidos es un problema de implementación.

nota final

Ejemplo: Ejemplos de identificadores válidos son identifier1, _identifier2y @if. ejemplo final

Un identificador de un programa conforme tendrá el formato canónico definido por el formulario de normalización Unicode C, tal como se define en el Anexo 15 del Estándar Unicode. El comportamiento al encontrar un identificador que no está en el formulario de normalización C está definido por la implementación; sin embargo, no se requiere un diagnóstico.

El prefijo "@" permite el uso de palabras clave como identificadores, lo que resulta útil al interactuar con otros lenguajes de programación. El carácter @ no forma parte realmente del identificador, por lo que es posible que el identificador se vea en otros idiomas como un identificador normal, sin el prefijo. Un identificador con un @ prefijo se denomina identificador textual.

Nota: Se permite el uso del @ prefijo para los identificadores que no son palabras clave, pero no se recomienda encarecidamente como cuestión de estilo. nota final

Ejemplo: Ejemplo:

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 una clase denominada "class" con un método estático denominado "static" que toma un parámetro denominado "bool". Tenga en cuenta que, dado que no se permiten escapes Unicode en palabras clave, el token "cl\u0061ss" es un identificador y es el mismo identificador que "@class".

ejemplo final

Dos identificadores se consideran iguales si son idénticos después de aplicar las transformaciones siguientes, en orden:

  • Se quita el prefijo "@", si se usa.
  • Cada Unicode_Escape_Sequence se transforma en su carácter Unicode correspondiente.
  • Se quitan todos los Formatting_Character.

La semántica de un identificador denominado _ depende del contexto en el que aparece:

  • Puede indicar un elemento de programa con nombre, como una variable, una clase o un método, o
  • Puede indicar un descarte (§9.2.9.1).

Los identificadores que contienen dos caracteres de subrayado consecutivos (U+005F) están reservados para su uso por la implementación; sin embargo, no se requiere ningún diagnóstico si se define dicho identificador.

Nota: Por ejemplo, una implementación podría proporcionar palabras clave extendidas que comienzan con dos caracteres de subrayado. nota final

6.4.4 Palabras clave

Una palabra clave es una secuencia de caracteres similar a un identificador que está reservada y no se puede usar como identificador, excepto cuando el carácter lo precede @ .

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'
    ;

Una palabra clave contextual es una secuencia de caracteres similar a un identificador que tiene un significado especial en determinados contextos, pero no está reservado, y se puede usar como identificador fuera de esos contextos, así como cuando el carácter lo precede @ .

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: La palabra clave rules y contextual_keyword son reglas del analizador, ya que no introducen nuevos tipos de token. Todas las palabras clave y las palabras clave contextuales se definen mediante reglas léxicas implícitas a medida que se producen como cadenas literales en la gramática (§6.2.3). nota final

En la mayoría de los casos, la ubicación sintáctica de las palabras clave contextuales es tal que nunca se pueden confundir con el uso normal del identificador. Por ejemplo, dentro de una declaración de propiedad, los get identificadores y set tienen un significado especial (§15.7.3). Un identificador distinto de get o set nunca se permite en estas ubicaciones, por lo que este uso no entra en conflicto con el uso de estas palabras como identificadores.

En ciertos casos, la gramática no es suficiente para distinguir el uso de palabras clave contextuales de los identificadores. En todos estos casos, se especificará cómo desambiguar entre los dos. Por ejemplo, la palabra clave var contextual en declaraciones de variables locales con tipo implícito (§13.6.2) podría entrar en conflicto con un tipo declarado denominado var, en cuyo caso el nombre declarado tiene prioridad sobre el uso del identificador como palabra clave contextual.

Otro ejemplo de esta desambiguación es la palabra clave await contextual (§12.9.8.1), que se considera una palabra clave solo cuando dentro de un método declarado async, pero se puede usar como identificador en otra parte.

Al igual que con las palabras clave, las palabras clave contextuales se pueden usar como identificadores normales prefijos con el @ carácter .

Nota: Cuando se usa como palabras clave contextuales, estos identificadores no pueden contener Unicode_Escape_Sequences. nota final

6.4.5 Literales

6.4.5.1 General

Un literal (§12.8.2) es una representación de código fuente de un valor.

literal
    : boolean_literal
    | Integer_Literal
    | Real_Literal
    | Character_Literal
    | String_Literal
    | null_literal
    ;

Nota: literal es una regla de analizador, ya que agrupa otros tipos de token y no introduce un nuevo tipo de token. nota final

6.4.5.2 Literales booleanos

Hay dos valores literales booleanos: true y false.

boolean_literal
    : TRUE
    | FALSE
    ;

Nota: boolean_literal es una regla de analizador, ya que agrupa otros tipos de token y no introduce un nuevo tipo de token. nota final

El tipo de un boolean_literal es bool.

6.4.5.3 Literales enteros

Los literales enteros se usan para escribir valores de tipos int, uint, longy ulong. Los literales enteros tienen tres formas posibles: decimal, hexadecimal y binario.

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'
    ;

El tipo de un literal entero se determina de la siguiente manera:

  • Si el literal no tiene sufijo, tiene el primero de estos tipos en los que su valor se puede representar: int, uint, long, ulong.
  • Si el literal está sufijo por U o u, tiene el primero de estos tipos en el que su valor se puede representar: uint, ulong.
  • Si el literal está sufijo por L o l, tiene el primero de estos tipos en el que su valor se puede representar: long, ulong.
  • Si el literal tiene el sufijo UL, , UluLulLULu, lUo lu, es de tipo .ulong

Si el valor representado por un literal entero está fuera del intervalo del ulong tipo, se produce un error en tiempo de compilación.

Nota: Como cuestión de estilo, se sugiere que "L" se use en lugar de "l" al escribir literales de tipo long, ya que es fácil confundir la letra "l" con el dígito "1". nota final

Para permitir que los valores más pequeños y long posibles int se escriban como literales enteros, existen las dos reglas siguientes:

  • Cuando un Integer_Literal que representa el valor 2147483648 (2³¹) y ningún Integer_Type_Suffix aparece como el token inmediatamente después de un token de operador unario menos (§12.9.3), el resultado (de ambos tokens) es una constante de tipo int con el valor −2147483648 (−2³¹). En todas las demás situaciones, este Integer_Literal es de tipo uint.
  • Cuando un Integer_Literal que representa el valor 9223372036854775808 (2⁶³) y ningún Integer_Type_Suffix o el Integer_Type_Suffix L o l aparece como el token inmediatamente después de un token de operador unario menos (§12.9.3), el resultado (de ambos tokens) es una constante de tipo long con el valor −9223372036854775808 (−2⁶³). En todas las demás situaciones, este Integer_Literal es de tipo ulong.

Ejemplo:

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

ejemplo final

6.4.5.4 Literales reales

Los literales reales se usan para escribir valores de tipos float, doubley 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'
    ;

Si no se especifica ningún Real_Type_Suffix , el tipo del Real_Literal es double. De lo contrario, el Real_Type_Suffix determina el tipo del literal real, como se indica a continuación:

  • Un literal real sufijo por F o f es de tipo float.

    Ejemplo: los literales 1f, 1.5f, 1e10fy 123.456F son todos de tipo float. ejemplo final

  • Un literal real sufijo por D o d es de tipo double.

    Ejemplo: los literales 1d, 1.5d, 1e10dy 123.456D son todos de tipo double. ejemplo final

  • Un literal real sufijo por M o m es de tipo decimal.

    Ejemplo: los literales 1m, 1.5m, 1e10my 123.456M son todos de tipo decimal. ejemplo final
    Este literal se convierte en un decimal valor tomando el valor exacto y, si es necesario, redondeo al valor representable más cercano mediante el redondeo bancario (§8.3.8). Cualquier escala aparente en el literal se conserva a menos que el valor se redondee. Nota: Por lo tanto, el literal 2.900m se analizará para formar el decimal signo 0, el coeficiente 2900y la escala 3. nota final

Si la magnitud del literal especificado es demasiado grande para representarse en el tipo indicado, se produce un error en tiempo de compilación.

Nota: En concreto, un Real_Literal nunca producirá un infinito de punto flotante. Sin embargo, un Real_Literal distinto de cero puede redondearse a cero. nota final

El valor de un literal real de tipo float o double se determina mediante el modo iec 60559 "redondeo a más cercano" con vínculos rotos a "par" (un valor con el cero de bits menos significativo) y todos los dígitos considerados significativos.

Nota: En un literal real, los dígitos decimales siempre son necesarios después del separador decimal. Por ejemplo, 1.3F es un literal real, pero 1.F no lo es. nota final

Ejemplo:

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

ejemplo final

6.4.5.5 Literales de caracteres

Un literal de carácter representa un carácter único y consta de un carácter entre comillas, como en '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: Un carácter que sigue a un carácter de barra diagonal inversa (\) en un carácter debe ser uno de los siguientes caracteres: ', , a\0frnbu"t, U, , . vx De lo contrario, se produce un error en tiempo de compilación. nota final

Nota: El uso de la \x producción de Hexadecimal_Escape_Sequence puede ser propenso a errores y difícil de leer debido al número variable de dígitos hexadecimales que siguen a \x. Por ejemplo, en el código:

string good = "\x9Good text";
string bad = "\x9Bad text";

puede aparecer al principio que el carácter inicial es el mismo (U+0009, un carácter de tabulación) en ambas cadenas. De hecho, la segunda cadena comienza con U+9BAD , ya que las tres letras de la palabra "Bad" son dígitos hexadecimales válidos. Como cuestión de estilo, se recomienda \x evitar en favor de secuencias de escape específicas (\t en este ejemplo) o la secuencia de escape de longitud \u fija.

nota final

Una secuencia de escape hexadecimal representa una sola unidad de código UTF-16 Unicode, con el valor formado por el número hexadecimal siguiente a "\x".

Si el valor representado por un literal de caracteres es mayor que U+FFFF, se produce un error en tiempo de compilación.

Una secuencia de escape Unicode (§6.4.2) en un literal de caracteres estará en el intervalo U+0000 a U+FFFF.

Una secuencia de escape simple representa un carácter Unicode, como se describe en la tabla siguiente.

Secuencia de escape Nombre de carácter Punto de código Unicode
\' Comilla simple U + 0027
\" Comilla doble U + 0022
\\ Barra invertida U + 005C
\0 Null U+0000
\a Alerta U+0007
\b Retroceso U + 0008
\f Avance de página U + 000C
\n Nueva línea U + 000A
\r Retorno de carro U + 000D
\t Tabulación horizontal U + 0009
\v Tabulación vertical U+000B

El tipo de un Character_Literal es char.

6.4.5.6 Literales de cadena

C# admite dos formas de literales de cadena: literales de cadena normales y literales de cadena textual. Un literal de cadena normal consta de cero o más caracteres entre comillas dobles, como en "hello"y puede incluir secuencias de escape simples (como \t para el carácter de tabulación) y secuencias de escape hexadecimales y Unicode.

Un literal de cadena textual consta de un @ carácter seguido de un carácter de comillas dobles, cero o más caracteres y un carácter de comillas dobles de cierre.

Ejemplo: Un ejemplo sencillo es @"hello". ejemplo final

En un literal de cadena textual, los caracteres entre los delimitadores se interpretan textualmente, con la única excepción de ser un Quote_Escape_Sequence, que representa un carácter de comillas dobles. En concreto, las secuencias de escape simples y las secuencias de escape hexadecimal y Unicode no se procesan en literales de cadena textual. Un literal de cadena textual puede abarcar varias líneas.

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
    : '""'
    ;

Ejemplo: El ejemplo

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";

muestra una variedad de literales de cadena. El último literal de cadena, j, es un literal de cadena textual que abarca varias líneas. Los caracteres entre las comillas, incluidos los espacios en blanco, como los nuevos caracteres de línea, se conservan textualmente y cada par de caracteres de comillas dobles se reemplaza por uno de estos caracteres.

ejemplo final

Nota: Los saltos de línea dentro de los literales de cadena textual forman parte de la cadena resultante. Si los caracteres exactos usados para formar saltos de línea son semánticamente relevantes para una aplicación, cualquier herramienta que traduzca saltos de línea en el código fuente a diferentes formatos (entre "\n" y "\r\n", por ejemplo), cambiará el comportamiento de la aplicación. Los desarrolladores deben tener cuidado en tales situaciones. nota final

Nota: Dado que una secuencia de escape hexadecimal puede tener un número variable de dígitos hexadecimales, el literal "\x123" de cadena contiene un único carácter con el valor 123hexadecimal . Para crear una cadena que contenga el carácter con el valor 12 hexadecimal seguido del carácter 3, se podría escribir "\x00123" o "\x12" + "3" en su lugar. nota final

El tipo de un String_Literal es string.

Cada literal de cadena no produce necesariamente una nueva instancia de cadena. Cuando dos o más literales de cadena equivalentes según el operador de igualdad de cadenas (§12.12.8), aparecen en el mismo ensamblado, estos literales de cadena hacen referencia a la misma instancia de cadena.

Ejemplo: por ejemplo, la salida generada por

class Test
{
    static void Main()
    {
        object a = "hello";
        object b = "hello";
        System.Console.WriteLine(a == b);
    }
}

es True porque los dos literales hacen referencia a la misma instancia de cadena.

ejemplo final

6.4.5.7 Literal null

null_literal
    : NULL
    ;

Nota: null_literal es una regla de analizador, ya que no introduce un nuevo tipo de token. nota final

Un null_literal representa un null valor. No tiene un tipo, pero se puede convertir a cualquier tipo de referencia o tipo de valor que acepta valores NULL a través de una conversión literal nula (§10.2.7).

6.4.6 Operadores y signos de puntuación

Hay varios tipos de operadores y signos de puntuación. Los operadores se usan en expresiones para describir las operaciones con uno o varios operandos implicados.

Ejemplo: La expresión a + b usa el + operador para agregar los dos operandos a y b. ejemplo final

Los signos de puntuación se usan para agrupar y separar.

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Nota: right_shift y right_shift_assignment son reglas del analizador, ya que no introducen un nuevo tipo de token, sino que representan una secuencia de dos tokens. La regla de operator_or_punctuator existe solo con fines descriptivos y no se usa en otra parte de la gramática. nota final

right_shift se compone de los dos tokens > y >. Del mismo modo, right_shift_assignment se compone de los dos tokens > y >=. A diferencia de otras producciones en la gramática sintáctica, no se permiten caracteres de ningún tipo (ni siquiera espacios en blanco) entre los dos tokens de cada una de estas producciones. Estas producciones se tratan especialmente para permitir el control correcto de type_parameter_lists (§15.2.3).

Nota: Antes de la adición de genéricos a C#, >> y >>= eran tokens únicos. Sin embargo, la sintaxis de los genéricos usa los < caracteres y > para delimitar los parámetros de tipo y los argumentos de tipo. A menudo es conveniente usar tipos construidos anidados, como List<Dictionary<string, int>>. En lugar de requerir que el programador separe y > > por un espacio, se cambió la definición de los dos operator_or_punctuators. nota final

6.5 Directivas de preprocesamiento

6.5.1 General

Las directivas de preprocesamiento proporcionan la capacidad de omitir condicionalmente secciones de unidades de compilación, notificar condiciones de error y advertencia, para delinear regiones distintas del código fuente y establecer el contexto que acepta valores NULL.

Nota: El término "directivas de preprocesamiento" solo se usa para la coherencia con los lenguajes de programación C y C++. En C#, no hay ningún paso de preprocesamiento independiente; Las directivas de preprocesamiento se procesan como parte de la fase de análisis léxico. 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:

  • La gramática del preprocesador define un único token PP_Directive léxico que se usa para todas las directivas de preprocesamiento. La semántica de cada una de las directivas de preprocesamiento se define en esta especificación del lenguaje, pero no en cómo implementarlas.
  • El PP_Start fragmento solo debe reconocerse al principio de una línea, el getCharPositionInLine() == 0 predicado léxico ANTLR anterior sugiere una manera en la que esto se puede lograr y es informativo únicamente, una implementación puede usar una estrategia diferente.

nota final

Están disponibles las siguientes directivas de preprocesamiento:

  • #define y #undef, que se usan para definir y no definir, respectivamente, símbolos de compilación condicional (§6.5.4).
  • #if, #elif, #elsey #endif, que se usan para omitir secciones condicionales del código fuente (§6.5.5).
  • #line, que se usa para controlar los números de línea emitidos para errores y advertencias (§6.5.8).
  • #error, que se usa para emitir errores (§6.5.6).
  • #region y #endregion, que se usan para marcar explícitamente secciones del código fuente (§6.5.7).
  • #nullable, que se usa para especificar el contexto que acepta valores NULL (§6.5.9).
  • #pragma, que se usa para especificar información contextual opcional para un compilador (§6.5.10).

Una directiva de preprocesamiento siempre ocupa una línea de código fuente independiente y siempre comienza con un # carácter y un nombre de directiva de preprocesamiento. Es posible que se produzcan espacios en blanco antes del # carácter y entre el # carácter y el nombre de la directiva.

Una línea de origen que contiene una #definedirectiva , #undef, #if, #else#endif#elif, #line, , , #endregiono #nullable puede terminar con un comentario de una sola línea. Los comentarios delimitados (el /* */ estilo de comentarios) no se permiten en las líneas de origen que contienen directivas de preprocesamiento.

Las directivas de preprocesamiento no forman parte de la gramática sintáctica de C#. Sin embargo, las directivas de preprocesamiento se pueden usar para incluir o excluir secuencias de tokens y pueden afectar de esa manera al significado de un programa de C#.

Ejemplo: cuando se compila, el programa

#define A
#undef B
class C
{
#if A
    void F() {}
#else
    void G() {}
#endif
#if B
    void H() {}
#else    
    void I() {}
#endif
}

da como resultado la misma secuencia exacta de tokens que el programa

class C
{
    void F() {}
    void I() {}
}

Por lo tanto, mientras que léxicamente, los dos programas son bastante diferentes, sintácticamente, son idénticos.

ejemplo final

6.5.2 Símbolos de compilación condicional

La funcionalidad de compilación condicional proporcionada por las #ifdirectivas , #elif, #elsey #endif se controla mediante expresiones de preprocesamiento (§6.5.3) y símbolos de compilación condicional.

fragment PP_Conditional_Symbol
    // Must not be equal to tokens TRUE or FALSE. See note below.
    : Basic_Identifier
    ;

Nota Cómo aplica una implementación la restricción en los valores de Basic_Identifier permitidos es un problema de implementación. nota final

Dos símbolos de compilación condicional se consideran iguales si son idénticos después de aplicar las transformaciones siguientes, en orden:

  • Cada Unicode_Escape_Sequence se transforma en su carácter Unicode correspondiente.
  • Se quita cualquier Formatting_Characters .

Un símbolo de compilación condicional tiene dos estados posibles: definidos o sin definir. Al principio del procesamiento léxico de una unidad de compilación, un símbolo de compilación condicional no está definido a menos que se haya definido explícitamente mediante un mecanismo externo (como una opción del compilador de línea de comandos). Cuando se procesa una #define directiva, el símbolo de compilación condicional denominado en esa directiva se define en esa unidad de compilación. El símbolo permanece definido hasta que se procesa una #undef directiva para ese mismo símbolo o hasta que se alcanza el final de la unidad de compilación. Una implicación de esto es que #define las directivas y #undef de una unidad de compilación no tienen ningún efecto en otras unidades de compilación del mismo programa.

Cuando se hace referencia a en una expresión de preprocesamiento (§6.5.3), un símbolo de compilación condicional definido tiene el valor truebooleano y un símbolo de compilación condicional no definido tiene el valor falsebooleano . No hay ningún requisito de que los símbolos de compilación condicional se declaren explícitamente antes de que se hagan referencia a ellos en expresiones de preprocesamiento. En su lugar, los símbolos no declarados son simplemente indefinidos y, por tanto, tienen el valor false.

El espacio de nombres para símbolos de compilación condicional es distinto y independiente de todas las demás entidades con nombre en un programa de C#. Solo se puede hacer referencia a símbolos de compilación condicional en #define directivas y #undef y en expresiones de preprocesamiento.

6.5.3 Expresiones de preprocesamiento

Las expresiones de preprocesamiento pueden producirse en #if directivas y #elif . Los operadores ! (solo negación lógica de prefijo), ==, !=, &&y || se permiten en expresiones de preprocesamiento y se pueden usar paréntesis para la agrupación.

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? ')'
    ;

Cuando se hace referencia a en una expresión de preprocesamiento, un símbolo de compilación condicional definido tiene el valor truebooleano y un símbolo de compilación condicional no definido tiene el valor falsebooleano .

La evaluación de una expresión de preprocesamiento siempre produce un valor booleano. Las reglas de evaluación de una expresión de preprocesamiento son las mismas que las de una expresión constante (§12.23), excepto que las únicas entidades definidas por el usuario a las que se puede hacer referencia son símbolos de compilación condicional.

6.5.4 Directivas de definición

Las directivas de definición se usan para definir o anular la definición de símbolos de compilación condicional.

fragment PP_Declaration
    : 'define' PP_Whitespace PP_Conditional_Symbol
    | 'undef' PP_Whitespace PP_Conditional_Symbol
    ;

El procesamiento de una #define directiva hace que se defina el símbolo de compilación condicional especificado, empezando por la línea de origen que sigue a la directiva . Del mismo modo, el procesamiento de una #undef directiva hace que el símbolo de compilación condicional especificado se defina, empezando por la línea de origen que sigue a la directiva .

Todas #define las directivas y #undef de una unidad de compilación se producirán antes del primer token (§6.4) en la unidad de compilación; de lo contrario, se produce un error en tiempo de compilación. En términos intuitivos, #define las #undef directivas precederán a cualquier "código real" en la unidad de compilación.

Ejemplo: Ejemplo:

#define Enterprise
#if Professional || Enterprise
#define Advanced
#endif
namespace Megacorp.Data
{
#if Advanced
    class PivotTable {...}
#endif
}

es válido porque las #define directivas preceden al primer token (la namespace palabra clave) de la unidad de compilación.

ejemplo final

Ejemplo: en el ejemplo siguiente se produce un error en tiempo de compilación porque un #define sigue el código real:

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

ejemplo final

Un #define puede definir un símbolo de compilación condicional que ya está definido, sin que intervenga #undef para ese símbolo.

Ejemplo: En el ejemplo siguiente se define un símbolo de compilación condicional A y, a continuación, se define de nuevo.

#define A
#define A

Para los compiladores que permiten definir símbolos de compilación condicionales como opciones de compilación, una manera alternativa de que se produzca dicha redefinición es definir el símbolo como una opción del compilador, así como en el origen.

ejemplo final

Un #undef puede "anular la definición" de un símbolo de compilación condicional que no está definido.

Ejemplo: En el ejemplo siguiente se define un símbolo A de compilación condicional y, a continuación, se desenlace dos veces; aunque el segundo #undef no tiene ningún efecto, sigue siendo válido.

#define A
#undef A
#undef A

ejemplo final

6.5.5 Directivas de compilación condicional

Las directivas de compilación condicional se usan para incluir o excluir de forma condicional partes de una unidad de compilación.

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'
    ;

Las directivas de compilación condicional se escribirán en grupos que constan de, en orden, una #if directiva, cero o más #elif directivas, cero o una #else directiva y una #endif directiva. Entre las directivas hay secciones condicionales del código fuente. Cada sección se controla mediante la directiva inmediatamente anterior. Una sección condicional puede contener directivas de compilación condicional anidadas siempre que estas directivas formenten grupos completos.

Como máximo, se selecciona una de las secciones condicionales contenidas para el procesamiento léxico normal:

  • Los PP_Expression de las #if directivas y #elif se evalúan en orden hasta que uno produzca true. Si una expresión produce true, se selecciona la sección condicional que sigue a la directiva correspondiente.
  • Si todos los PP_Expressionproducen falsey, si hay una #else directiva, se selecciona la sección condicional que sigue a la #else directiva.
  • De lo contrario, no se selecciona ninguna sección condicional.

La sección condicional seleccionada, si existe, se procesa como una input_section normal: el código fuente contenido en la sección se adhiere a la gramática léxica; los tokens se generan a partir del código fuente de la sección; y las directivas de preprocesamiento de la sección tienen los efectos prescritos.

Las secciones condicionales restantes se omiten y no se generan tokens, excepto los de las directivas de preprocesamiento, a partir del código fuente. Por lo tanto, el código fuente omitido, excepto las directivas de preprocesamiento, puede ser léxicamente incorrecto. Las directivas de preprocesamiento omitidas serán léxicasmente correctas, pero no se procesarán de otro modo. Dentro de una sección condicional que se omite cualquier sección condicional anidada (contenida en construcciones anidadas #if...#endif ) también se omite.

Nota: La gramática anterior no captura la asignación de que las secciones condicionales entre las directivas de preprocesamiento pueden tener un formato léxico incorrecto. Por lo tanto, la gramática no está lista para ANTLR, ya que solo admite entradas léxicasmente correctas. nota final

Ejemplo: en el ejemplo siguiente se muestra cómo pueden anidar las directivas de compilación condicional:

#define Debug // Debugging on
#undef Trace // Tracing off
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
    #if Trace
        WriteToLog(this.ToString());
    #endif
#endif
        CommitHelper();
    }
    ...
}

Excepto para las directivas de preprocesamiento, el código fuente omitido no está sujeto a análisis léxicos. Por ejemplo, lo siguiente es válido a pesar del comentario noterminado de la #else sección :

#define Debug // Debugging on
class PurchaseTransaction
{
    void Commit()
    {
#if Debug
        CheckConsistency();
#else
        /* Do something else
#endif
    }
    ...
}

Tenga en cuenta, sin embargo, que las directivas de preprocesamiento deben ser léxicasmente correctas incluso en secciones omitidas del código fuente.

Las directivas de preprocesamiento no se procesan cuando aparecen dentro de elementos de entrada de varias líneas. Por ejemplo, el programa:

class Hello
{
    static void Main()
    {
        System.Console.WriteLine(@"hello,
#if Debug
        world
#else
        Nebraska
#endif
        ");
    }
}

da como resultado la salida:

hello,
#if Debug
        world
#else
        Nebraska
#endif

En casos peculiares, el conjunto de directivas de preprocesamiento que se procesan puede depender de la evaluación del pp_expression. El ejemplo:

#if X
    /*
#else
    /* */ class Q { }
#endif

siempre genera la misma secuencia de token (class }Q { ), independientemente de si se define o no.X Si X se define, las únicas directivas procesadas son #if y #endif, debido al comentario de varias líneas. Si X no está definido, tres directivas (#if, #else, #endif) forman parte del conjunto de directivas.

ejemplo final

6.5.6 Directivas de diagnóstico

Las directivas de diagnóstico se usan para generar mensajes de error y advertencia explícitos que se notifican de la misma manera que otros errores y advertencias en tiempo de compilación.

fragment PP_Diagnostic
    : 'error' PP_Message?
    | 'warning' PP_Message?
    ;

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Ejemplo: El ejemplo

#if Debug && Retail
    #error A build can't be both debug and retail
#endif
class Test {...}

genera un error en tiempo de compilación ("Una compilación no puede ser debug y retail") si se definen los símbolos Debug de compilación condicional y Retail ambos. Tenga en cuenta que un PP_Message puede contener texto arbitrario; en concreto, no necesita contener tokens bien formados, como se muestra en la comilla simple de la palabra can't.

ejemplo final

6.5.7 Directivas de región

Las directivas region se usan para marcar explícitamente las regiones del código fuente.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

No se adjunta ningún significado semántico a una región; las regiones están diseñadas para su uso por el programador o por herramientas automatizadas para marcar una sección de código fuente. Habrá una #endregion directiva que coincida con cada #region directiva. El mensaje especificado en una #region directiva o #endregion tampoco tiene ningún significado semántico; simplemente sirve para identificar la región. La coincidencia #region y #endregion las directivas pueden tener PP_Messages diferentes.

El procesamiento léxico de una región:

#region
...
#endregion

corresponde exactamente al procesamiento léxico de una directiva de compilación condicional del formulario:

#if true
...
#endif

Nota: Esto significa que una región puede incluir una o varias #if/.../#endif, o estar contenidas en una sección condicional dentro de / #if.../#endif; pero una región no se puede superponer con una parte #ifde /.../#endif, o iniciar y finalizar en diferentes secciones condicionales. nota final

6.5.8 Directivas de línea

Las directivas de línea se pueden usar para modificar los números de línea y los nombres de unidad de compilación notificados por el compilador en la salida, como advertencias y errores. Estos valores también los usan los atributos de información del autor de la llamada (§22.5.6).

Nota: Las directivas de línea se usan con más frecuencia en las herramientas de meta-programación que generan código fuente de C# a partir de alguna otra 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' | '#')
    ;

Cuando no hay ninguna #line directiva, el compilador notifica números de línea verdaderos y nombres de unidad de compilación en su salida. Al procesar una #line directiva que incluye un PP_Line_Indicator que no defaultes , el compilador trata la línea después de la directiva como tener el número de línea especificado (y el nombre de la unidad de compilación, si se especifica).

El valor máximo permitido para es definido por Decimal_Digit+ la implementación.

Una #line default directiva deshace el efecto de todas las directivas anteriores #line . El compilador notifica información de línea verdadera para las líneas posteriores, exactamente como si no se hubiera procesado ninguna #line directiva.

Una #line hidden directiva no tiene ningún efecto en los números de línea y unidad de compilación notificados en los mensajes de error o producidos por el uso de CallerLineNumberAttribute (§22.5.6.2). Está pensado para afectar a las herramientas de depuración de nivel de origen para que, al depurar, todas las líneas entre una #line hidden directiva y la directiva posterior #line (que no #line hiddenes ) no tengan información de número de línea y se omitan por completo al recorrer el código.

Nota: Aunque un PP_Compilation_Unit_Name podría contener texto similar a una secuencia de escape, este texto no es una secuencia de escape; en este contexto, un carácter "\" simplemente designa un carácter de barra diagonal inversa normal. nota final

6.5.9 Directiva que acepta valores NULL

La directiva que acepta valores NULL controla el contexto que acepta valores NULL, como se describe a continuación.

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'
    ;

Una directiva que acepta valores NULL establece las marcas disponibles para las líneas de código posteriores, hasta que se invalida otra directiva que acepta valores NULL o hasta que se alcanza el final de la compilación _unit . El contexto que acepta valores NULL contiene dos marcas: anotaciones y advertencias. El efecto de cada forma de directiva que acepta valores NULL es, como se indica a continuación:

  • #nullable disable: deshabilita las anotaciones que aceptan valores NULL y las marcas de advertencia que aceptan valores NULL.
  • #nullable enable: habilita las anotaciones que aceptan valores NULL y las marcas de advertencia que aceptan valores NULL.
  • #nullable restore: restaura las marcas de anotaciones y advertencias al estado especificado por el mecanismo externo, si existe.
  • #nullable disable annotations: deshabilita la marca de anotaciones que aceptan valores NULL. La marca de advertencias que aceptan valores NULL no se ve afectada.
  • #nullable enable annotations: habilita la marca de anotaciones que aceptan valores NULL. La marca de advertencias que aceptan valores NULL no se ve afectada.
  • #nullable restore annotations: restaura la marca de anotaciones que aceptan valores NULL al estado especificado por el mecanismo externo, si existe. La marca de advertencias que aceptan valores NULL no se ve afectada.
  • #nullable disable warnings: deshabilita la marca de advertencias que acepta valores NULL. La marca de anotaciones que aceptan valores NULL no se ve afectada.
  • #nullable enable warnings: habilita la marca de advertencias que aceptan valores NULL. La marca de anotaciones que aceptan valores NULL no se ve afectada.
  • #nullable restore warnings: restaura la marca de advertencias que aceptan valores NULL en el estado especificado por el mecanismo externo, si existe. La marca de anotaciones que aceptan valores NULL no se ve afectada.

El estado que acepta valores NULL de las expresiones se realiza en todo momento. El estado de la marca de anotación y la presencia o ausencia de una anotación que acepta valores NULL, ?, determina el estado null inicial de una declaración de variable. Las advertencias solo se emiten cuando la marca de advertencias está habilitada.

Ejemplo: El ejemplo

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

genera una advertencia en tiempo de compilación ("tal cual x null"). El estado que acepta valores NULL de x se realiza un seguimiento en todas partes. Se emite una advertencia cuando la marca de advertencias está habilitada.

ejemplo final

6.5.10 Directivas Pragma

La #pragma directiva de preprocesamiento se usa para especificar información contextual en un compilador.

Nota: Por ejemplo, un compilador podría proporcionar #pragma directivas que

  • Habilite o deshabilite mensajes de advertencia concretos al compilar código posterior.
  • Especifique qué optimizaciones se aplicarán al código posterior.
  • Especifique la información que va a usar un depurador.

nota final

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

El compilador interpreta los Input_Character de la PP_Pragma_Text de una manera definida por la implementación. La información proporcionada en una #pragma directiva no cambiará la semántica del programa. Una #pragma directiva solo cambiará el comportamiento del compilador que está fuera del ámbito de esta especificación del lenguaje. Si el compilador no puede interpretar los Input_Characters, el compilador puede generar una advertencia; sin embargo, no generará un error en tiempo de compilación.

Nota: PP_Pragma_Text puede contener texto arbitrario; en concreto, no necesita contener tokens bien formados. nota final