Partager via


6 Structure lexicale

6.1 Programmes

Un programme C# se compose d’un ou plusieurs fichiers sources, appelés officiellement unités de compilation (§14.2). Bien qu’une unité de compilation puisse avoir une correspondance un-à-un avec un fichier dans un système de fichiers, cette correspondance n’est pas nécessaire.

Conceptuellement, un programme est compilé en trois étapes :

  1. Transformation, qui convertit un fichier d’un répertoire de caractères particulier et un schéma d’encodage en séquence de caractères Unicode.
  2. Analyse lexicale, qui traduit un flux de caractères d’entrée Unicode en un flux de jetons.
  3. Analyse syntactique, qui traduit le flux de jetons en code exécutable.

Les implémentations conformes acceptent les unités de compilation Unicode encodées avec le formulaire d’encodage UTF-8 (tel que défini par la norme Unicode) et les transforment en une séquence de caractères Unicode. Les implémentations peuvent choisir d’accepter et de transformer des schémas d’encodage de caractères supplémentaires (tels que UTF-16, UTF-32 ou non-Unicode).

Remarque : La gestion du caractère NULL Unicode (U+0000) est définie par l’implémentation. Il est fortement recommandé aux développeurs d’éviter d’utiliser ce caractère dans leur code source, pour la portabilité et la lisibilité. Lorsque le caractère est requis dans un littéral de caractère ou de chaîne, les séquences \0 d’échappement ou \u0000 peuvent être utilisées à la place. Note de fin

Remarque : il est au-delà de l’étendue de cette spécification pour définir la façon dont un fichier à l’aide d’une représentation de caractères autre que Unicode peut être transformé en une séquence de caractères Unicode. Toutefois, lors de cette transformation, il est recommandé que le caractère de séparation de ligne habituel (ou séquence) dans l’autre jeu de caractères soit traduit dans la séquence à deux caractères composée du caractère retour chariot Unicode (U+000D) suivi du caractère de saut de ligne Unicode (U+000A). Dans la plupart des cas, cette transformation n’aura aucun effet visible ; Toutefois, elle affectera l’interprétation des jetons littéraux de chaîne verbatim (§6.4.5.6). L’objectif de cette recommandation est de permettre à un littéral de chaîne détaillé de produire la même séquence de caractères lorsque son unité de compilation est déplacée entre les systèmes qui prennent en charge des jeux de caractères non Unicode différents, en particulier ceux qui utilisent des séquences de caractères différentes pour la séparation des lignes. Note de fin

6.2 Grammaires

6.2.1 Général

Cette spécification présente la syntaxe du langage de programmation C# à l’aide de deux grammaires. La grammaire lexicale (§6.2.3) définit la façon dont les caractères Unicode sont combinés pour former des terminateurs de ligne, des espaces blancs, des commentaires, des jetons et des directives de prétraitement. La grammaire syntaxique (§6.2.4) définit la façon dont les jetons résultant de la grammaire lexicale sont combinés pour former des programmes C#.

Tous les caractères de terminal doivent être compris comme le caractère Unicode approprié de la plage U+0020 à U+007F, par opposition aux caractères similaires d’autres plages de caractères Unicode.

6.2.2 Notation grammaticale

Les grammaires lexicales et syntactiques sont présentées dans le formulaire Extended Backus-Naur de l’outil de grammaire ANTLR.

Bien que la notation ANTLR soit utilisée, cette spécification ne présente pas une « grammaire de référence » complète prête pour ANTLR pour C# ; l’écriture d’un lexeur et d’un analyseur, manuellement ou à l’aide d’un outil tel qu’ANTLR, est en dehors de l’étendue d’une spécification de langage. Avec cette qualification, cette spécification tente de réduire l’écart entre la grammaire spécifiée et celle requise pour générer un lexeur et un analyseur dans ANTLR.

ANTLR fait la distinction entre lexical et la syntaxe, appelée par ANTLR, grammaires dans sa notation en commençant des règles lexicales avec une lettre majuscule et des règles d’analyseur avec une lettre minuscule.

Remarque : La grammaire lexicale C# (§6.2.3) et la grammaire syntaxique (§6.2.4) ne sont pas en correspondance exacte avec la division ANTLR en grammes lexicals et analyseurs. Cette petite incompatibilité signifie que certaines règles d’analyseur ANTLR sont utilisées lors de la spécification de la grammaire lexicale C#. Note de fin

6.2.3 Grammaire lexicale

La grammaire lexicale de C# est présentée dans le §6.3, le §6.4 et le §6.5. Les symboles terminal de la grammaire lexicale sont les caractères du jeu de caractères Unicode et la grammaire lexicale spécifie comment les caractères sont combinés pour former des jetons (§6.4), des espaces blancs (§6.3.4), des commentaires (§6.3.3) et des directives de prétraitement (§6.5).

La plupart des symboles terminaux de la grammaire syntaxique ne sont pas définis explicitement en tant que jetons dans la grammaire lexicale. Au lieu de cela, l’avantage est tiré du comportement ANTLR que les chaînes littérales de la grammaire sont extraites en tant que jetons lexicals implicites ; cela permet aux mots clés, opérateurs, etc. d’être représentés dans la grammaire par leur représentation littérale plutôt qu’un nom de jeton.

Chaque unité de compilation d’un programme C# doit être conforme à la production d’entrée de la grammaire lexicale (§6.3.1).

6.2.4 Grammaire syntactique

La grammaire syntaxique de C# est présentée dans les clauses, sous-clauses et annexes qui suivent ce sous-volet. Les symboles terminal de la grammaire syntaxique sont les jetons définis explicitement par la grammaire lexicale et implicitement par des chaînes littérales dans la grammaire elle-même (§6.2.3). La grammaire syntaxique spécifie comment les jetons sont combinés pour former des programmes C#.

Chaque unité de compilation d’un programme C# doit être conforme à la production compilation_unit (§14.2) de la grammaire syntaxique.

6.2.5 Ambiguïtés grammaticales

Les productions de simple_name (§12.8.4) et de member_access (§12.8.7) peuvent donner lieu à des ambiguïtés dans la grammaire des expressions.

Exemple : L’instruction :

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

peut être interprété comme un appel à deux F arguments, G < A et B > (7). Il peut également être interprété comme un appel à un argument, qui est un appel à F une méthode G générique avec deux arguments de type et un argument normal.

exemple de fin

Si une séquence de jetons peut être analysée (en contexte) en tant que simple_name (§12.8.4), member_access (§12.8.7) ou pointer_member_access (§23.6.3) se terminant par un type_argument_list (§8.4.2), le jeton immédiatement suivant le jeton de fermeture est examiné, pour voir s’il s’agit d’un type_argument_list (§8.4.2), le jeton immédiatement suivant le jeton de fermeture > est examiné.

  • Un des ( ) ] } : ; , . ? == != | ^ && || & [; ou
  • Un des opérateurs < <= >= is asrelationnels ; ou
  • Mot clé de requête contextuelle apparaissant à l’intérieur d’une expression de requête ; ou
  • Dans certains contextes, l’identificateur est traité comme un jeton ambigu. Ces contextes sont l’endroit où la séquence de jetons en cours d’ambiguïté est immédiatement précédée de l’un des mots clés is, case ou outest levée lors de l’analyse du premier élément d’un littéral tuple (auquel cas les jetons sont précédés ( ou : l’identificateur est suivi d’un ) ou d’un ,élément ultérieur d’un littéral de tuple.

Si le jeton suivant figure dans cette liste ou un identificateur dans un tel contexte, l’type_argument_list est conservé dans le cadre de l’simple_name, member_access ou pointer_member-access et toute autre analyse possible de la séquence de jetons est ignorée. Sinon, la type_argument_list n’est pas considérée comme faisant partie de l’simple_name, member_access ou pointer_member_access, même s’il n’y a pas d’autre analyse possible de la séquence de jetons.

Remarque : Ces règles ne sont pas appliquées lors de l’analyse d’un type_argument_list dans un namespace_or_type_name (§7.8). Note de fin

Exemple : L’instruction :

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

sera, selon cette règle, interprété comme un appel à F un argument, qui est un appel à une méthode G générique avec deux arguments de type et un argument normal. Instructions

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

sera interprété comme un appel à deux F arguments. Instruction

x = F<A> + y;

sera interprété comme un opérateur inférieur, supérieur à l’opérateur et unaire-plus, comme si l’instruction avait été écrite x = (F < A) > (+y), au lieu d’un simple_name avec un type_argument_list suivi d’un opérateur binaire plus. Dans l’instruction

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

les jetons sont interprétés C<T> comme un namespace_or_type_name avec un type_argument_list en raison de la présence du jeton && ambiguïté après la type_argument_list.

L’expression (A < B, C > D) est un tuple avec deux éléments, chacun une comparaison.

L’expression (A<B,C> D, E) est un tuple avec deux éléments, dont le premier est une expression de déclaration.

L’appel M(A < B, C > D, E) comporte trois arguments.

L’appel M(out A<B,C> D, E) a deux arguments, dont le premier est une out déclaration.

L’expression e is A<B> C utilise un modèle de déclaration.

L’étiquette case A<B> C: de cas utilise un modèle de déclaration.

exemple de fin

Lorsque vous reconnaissez une relational_expression (§12.12.1) si les alternatives « relational_expressionis » et « relational_expression constant_pattern is» sont applicables, et que le type est résolu en type accessible, l’alternative « relational_expression is type » doit être choisie.

6.3 Analyse lexicale

6.3.1 Général

Pour des raisons pratiques, la grammaire lexicale définit et référence les jetons lexer nommés suivants :

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

Bien qu’il s’agit de règles lexer, ces noms sont orthographiés en lettres majuscules pour les distinguer des noms de règles lexer ordinaires.

Remarque : Ces règles pratiques sont des exceptions à la pratique habituelle de ne pas fournir de noms de jetons explicites pour les jetons définis par des chaînes littérales. Note de fin

La production d’entrée définit la structure lexicale d’une unité de compilation C#.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Remarque : La grammaire ci-dessus est décrite par les règles d’analyse ANTLR, elle définit la structure lexicale d’une unité de compilation C# et non des jetons lexicals. Note de fin

Cinq éléments de base constituent la structure lexicale d’une unité de compilation C# : terminateurs de ligne (§6.3.2), espace blanc (§6.3.4), commentaires (§6.3.3), jetons (§6.4) et directives de prétraitement (§6.5). De ces éléments de base, seuls les jetons sont significatifs dans la grammaire syntaxique d’un programme C# (§6.2.4).

Le traitement lexical d’une unité de compilation C# consiste à réduire le fichier en une séquence de jetons qui devient l’entrée de l’analyse syntaxique. Les terminateurs de ligne, les espaces blancs et les commentaires peuvent servir à séparer les jetons, et les directives de prétraitement peuvent entraîner l’ignorer des sections de l’unité de compilation, mais ces éléments lexicals n’ont aucun impact sur la structure syntaxique d’un programme C#.

Lorsque plusieurs productions grammaticales lexicales correspondent à une séquence de caractères dans une unité de compilation, le traitement lexical forme toujours l’élément lexical le plus long possible.

Exemple : la séquence // de caractères est traitée comme début d’une commentaire de ligne unique, car cet élément lexical est plus long qu’un seul / jeton. exemple de fin

Certains jetons sont définis par un ensemble de règles lexicales ; une règle principale et une ou plusieurs sous-règles. Ces derniers sont marqués dans la grammaire fragment en indiquant que la règle définit une partie d’un autre jeton. Les règles de fragment ne sont pas considérées dans l’ordre supérieur à inférieur des règles lexicales.

Remarque : Dans ANTLR fragment est un mot clé qui produit le même comportement défini ici. Note de fin

6.3.2 Terminateurs de ligne

Les terminateurs de ligne divisent les caractères d’une unité de compilation C# en lignes.

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

Pour la compatibilité avec les outils d’édition de code source qui ajoutent des marqueurs de fin de fichier et pour permettre à une unité de compilation d’être vue comme une séquence de lignes correctement terminées, les transformations suivantes sont appliquées, afin que chaque unité de compilation d’un programme C# :

  • Si le dernier caractère de l’unité de compilation est un caractère Control-Z (U+001A), ce caractère est supprimé.
  • Un caractère de retour chariot (U+000D) est ajouté à la fin de l’unité de compilation si cette unité de compilation n’est pas vide et si le dernier caractère de l’unité de compilation n’est pas un retour chariot (U+000D), un flux de ligne (U+000A), un caractère de ligne suivant (U+0085), un séparateur de lignes (U+2028) ou un séparateur de paragraphe (U+2029).

Remarque : Le retour chariot supplémentaire permet à un programme de se terminer par un PP_Directive (§6.5) qui n’a pas de New_Line de fin. Note de fin

6.3.3 Commentaires

Deux formes de commentaires sont prises en charge : les commentaires délimités et les commentaire de ligne uniques.

Un commentaire délimité commence par les caractères /* et se termine par les caractères */. Les commentaires délimités peuvent occuper une partie d’une ligne, d’une seule ligne ou de plusieurs lignes.

Exemple : l’exemple

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

comprend un commentaire délimité.

exemple de fin

Une commentaire de ligne commence par les caractères // et s’étend à la fin de la ligne.

Exemple : l’exemple

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

illustre plusieurs commentaires sur une seule ligne.

exemple de fin

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
    ;

Les commentaires ne peuvent pas être imbriqués. Les séquences de caractères /* et */ n’ont aucune signification particulière dans un commentaire sur une seule ligne, et les séquences de caractères // et /* n’ont aucune signification particulière dans un commentaire délimité.

Les commentaires ne sont pas traités dans les littéraux de caractères et de chaîne.

Remarque : Ces règles doivent être interprétées avec soin. Par exemple, dans l’exemple ci-dessous, le commentaire délimité qui commence avant A les extrémités entre B et C(). La raison est que

// B */ C();

n’est pas réellement un commentaire de ligne unique, car // n’a pas de signification spéciale dans un commentaire délimité, et a donc */ sa signification spéciale habituelle dans cette ligne.

De même, le commentaire délimité commençant avant D la fin avant E. La raison est que ce "D */ " n’est pas réellement un littéral de chaîne, car le caractère de guillemets doubles initial apparaît à l’intérieur d’un commentaire délimité.

Une conséquence utile et /* */ sans signification particulière dans une seule commentaire de ligne est qu’un bloc de lignes de code source peut être commenté en plaçant // au début de chaque ligne. En règle générale, il ne fonctionne pas pour placer /* ces lignes et */ après ces lignes, car cela n’encapsule pas correctement les commentaires délimités dans le bloc, et en général peut complètement modifier la structure de ces commentaires délimités.

Exemple de code :

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

Note de fin

Single_Line_Comment s et les Delimited_Commentayant des formats particuliers peuvent être utilisés comme commentaires de documentation, comme décrit dans §D.

6.3.4 Espace blanc

L’espace blanc est défini comme n’importe quel caractère avec la classe Unicode Zs (qui inclut le caractère d’espace), ainsi que le caractère de tabulation horizontal, le caractère de tabulation vertical et le caractère de flux de formulaire.

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

6.4 Jetons

6.4.1 Général

Il existe plusieurs types de jetons : identificateurs, mots clés, littéraux, opérateurs et ponctuateurs. Les espaces blancs et les commentaires ne sont pas des jetons, bien qu’ils agissent comme séparateurs pour les jetons.

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

Remarque : Il s’agit d’une règle d’analyseur ANTLR, elle ne définit pas de jeton lexical, mais plutôt la collection de types de jetons. Note de fin

Séquences d’échappement de caractères Unicode 6.4.2

Une séquence d’échappement Unicode représente un point de code Unicode. Les séquences d’échappement Unicode sont traitées dans des identificateurs (§6.4.3), des littéraux de caractères (§6.4.5.5), des littéraux de chaîne standard (§6.4.5.6) et des expressions de chaîne régulière interpolées (§12.8.3). Une séquence d’échappement Unicode n’est pas traitée à un autre emplacement (par exemple, pour former un opérateur, un ponctuateur ou un mot clé).

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
    ;

Une séquence d’échappement de caractères Unicode représente le point de code Unicode unique formé par le nombre hexadécimal suivant les caractères « \u » ou « \U ». Étant donné que C# utilise un encodage 16 bits de points de code Unicode dans des valeurs de caractères et de chaînes, un point de code Unicode dans la plage U+10000 à U+10FFFF représenter à l’aide de deux unités de code de substitution Unicode. Les points de code Unicode ci-dessus U+FFFF ne sont pas autorisés dans les littéraux de caractères. Les points de code Unicode ci-dessus U+10FFFF ne sont pas valides et ne sont pas pris en charge.

Plusieurs traductions ne sont pas effectuées. Par exemple, le littéral "\u005Cu005C" de chaîne est équivalent au "\u005C" lieu de "\".

Remarque : La valeur \u005C Unicode est le caractère «\ ». Note de fin

Exemple : l’exemple

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

montre plusieurs utilisations de \u0066, qui est la séquence d’échappement pour la lettre «f ». Le programme est équivalent à

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

exemple de fin

6.4.3 Identificateurs

Les règles pour les identificateurs donnés dans ce sous-volet correspondent exactement à celles recommandées par l’annexe 15 standard Unicode, sauf que le trait de soulignement est autorisé en tant que caractère initial (tel qu’il est traditionnel dans le langage de programmation C), les séquences d’échappement Unicode sont autorisées dans les identificateurs, et le caractère «@ » est autorisé comme préfixe pour permettre aux mots clés d’être utilisés comme identificateurs.

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
    ;

Remarque :

  • Pour plus d’informations sur les classes de caractères Unicode mentionnées ci-dessus, consultez la norme Unicode.
  • Le fragment Available_Identifier nécessite l’exclusion de mots clés et de mots clés contextuels. Si la grammaire de cette spécification est traitée avec ANTLR, cette exclusion est gérée automatiquement par la sémantique d’ANTLR :
    • Les mots clés et les mots clés contextuels se produisent dans la grammaire sous forme de chaînes littérales.
    • ANTLR crée des règles de jetons lexicales implicites sont créées à partir de ces chaînes littérales.
    • ANTLR considère ces règles implicites avant les règles lexicales explicites dans la grammaire.
    • Par conséquent, le fragment Available_Identifier ne correspond pas aux mots clés ou aux mots clés contextuels en tant que règles lexicales pour ceux qui l’précèdent.
  • Les Escaped_Identifier fragments incluent des mots clés échappés et des mots clés contextuels, car ils font partie du jeton plus long commençant par un @ traitement lexical et lexical forment toujours l’élément lexical le plus long possible (§6.3.1).
  • La façon dont une implémentation applique les restrictions sur les valeurs Unicode_Escape_Sequence autorisées est un problème d’implémentation.

Note de fin

Exemple : Des exemples d’identificateurs valides sont identifier1, _identifier2et @if. exemple de fin

Un identificateur dans un programme conforme doit être au format canonique défini par le formulaire de normalisation Unicode C, tel que défini par l’annexe 15 standard Unicode. Le comportement lors de la rencontre d’un identificateur non dans le formulaire de normalisation C est défini par l’implémentation ; toutefois, un diagnostic n’est pas obligatoire.

Le préfixe «@ » permet d’utiliser des mots clés en tant qu’identificateurs, ce qui est utile lors de l’interfaçage avec d’autres langages de programmation. Le caractère @ ne fait pas partie de l’identificateur. L’identificateur peut donc être vu dans d’autres langues comme un identificateur normal, sans le préfixe. Un identificateur avec un @ préfixe est appelé identificateur détaillé.

Remarque : L’utilisation du @ préfixe pour les identificateurs qui ne sont pas des mots clés n’est pas autorisée, mais fortement déconseillée en matière de style. Note de fin

Exemple : l’exemple :

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

définit une classe nommée «class » avec une méthode statique nommée «static » qui prend un paramètre nommé «bool ». Notez que, étant donné que les échappements Unicode ne sont pas autorisés dans les mots clés, le jeton «cl\u0061ss » est un identificateur et est le même identificateur que «@class ».

exemple de fin

Deux identificateurs sont considérés comme identiques s’ils sont identiques après l’application des transformations suivantes, dans l’ordre :

  • Le préfixe « »,@ s’il est utilisé, est supprimé.
  • Chaque Unicode_Escape_Sequence est transformé en son caractère Unicode correspondant.
  • Toutes les Formatting_Charactersont supprimées.

La sémantique d’un identificateur nommé _ dépend du contexte dans lequel il apparaît :

  • Il peut désigner un élément de programme nommé, tel qu’une variable, une classe ou une méthode, ou
  • Il peut indiquer un abandon (§9.2.9.1).

Les identificateurs contenant deux caractères de soulignement consécutifs (U+005F) sont réservés à une utilisation par l’implémentation . Toutefois, aucun diagnostic n’est requis si un tel identificateur est défini.

Remarque : Par exemple, une implémentation peut fournir des mots clés étendus commençant par deux traits de soulignement. Note de fin

6.4.4 Mots clés

Un mot clé est une séquence de caractères de type identificateur qui est réservée et ne peut pas être utilisée comme identificateur, sauf lorsqu’il est précédé par le @ caractère.

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

Un mot clé contextuel est une séquence de caractères de type identificateur qui a une signification particulière dans certains contextes, mais qui n’est pas réservée, et peut être utilisée comme identificateur en dehors de ces contextes, ainsi que lorsqu’ils sont précédés par le @ caractère.

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

Remarque : le mot clé de règles et les contextual_keyword sont des règles d’analyseur, car elles n’introduisent pas de nouveaux types de jetons. Tous les mots clés et mots clés contextuels sont définis par des règles lexicales implicites, car elles se produisent en tant que chaînes littérales dans la grammaire (§6.2.3). Note de fin

Dans la plupart des cas, l’emplacement syntaxique des mots clés contextuels est tel qu’ils ne peuvent jamais être confondus avec l’utilisation ordinaire de l’identificateur. Par exemple, dans une déclaration de propriété, les get identificateurs ont set une signification particulière (§15.7.3). Un identificateur autre que get ou set n’est jamais autorisé dans ces emplacements, cette utilisation n’est donc pas en conflit avec l’utilisation de ces mots comme identificateurs.

Dans certains cas, la grammaire n’est pas suffisante pour distinguer l’utilisation contextuelle des mots clés des identificateurs. Dans tous ces cas, il sera spécifié comment lever l’ambiguïté entre les deux. Par exemple, le mot clé var contextuel dans les déclarations de variables locales implicitement typées (§13.6.2) peut entrer en conflit avec un type déclaré appelé var, auquel cas le nom déclaré est prioritaire sur l’utilisation de l’identificateur comme mot clé contextuel.

Un autre exemple de disambiguation est le mot clé await contextuel (§12.9.8.1), qui est considéré comme un mot clé uniquement lorsqu’une méthode déclarée async, mais peut être utilisée comme identificateur ailleurs.

Tout comme avec les mots clés, les mots clés contextuels peuvent être utilisés comme identificateurs ordinaires en les préfixant avec le @ caractère.

Remarque : lorsqu’ils sont utilisés comme mots clés contextuels, ces identificateurs ne peuvent pas contenir Unicode_Escape_Sequences. Note de fin

6.4.5 Littéraux

6.4.5.1 Général

Un littéral (§12.8.2) est une représentation de code source d’une valeur.

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

Remarque : le littéral est une règle d’analyseur, car elle regroupe d’autres types de jetons et n’introduit pas de nouveau type de jeton. Note de fin

6.4.5.2 Littéraux booléens

Il existe deux valeurs littérales booléennes : true et false.

boolean_literal
    : TRUE
    | FALSE
    ;

Remarque : boolean_literal est une règle d’analyseur, car elle regroupe d’autres types de jetons et n’introduit pas de nouveau type de jeton. Note de fin

Le type d’un boolean_literal est bool.

6.4.5.3 Littéraux entiers

Les littéraux entiers sont utilisés pour écrire des valeurs de types int, uint, longet ulong. Les littéraux entiers ont trois formes possibles : décimale, hexadécimale et binaire.

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

Le type d’un littéral entier est déterminé comme suit :

  • Si le littéral n’a pas de suffixe, il a le premier de ces types dans lesquels sa valeur peut être représentée : int, uint, long, ulong.
  • Si le littéral est suffixe par U ou u, il a le premier de ces types dans lesquels sa valeur peut être représentée : uint, ulong.
  • Si le littéral est suffixe par L ou l, il a le premier de ces types dans lesquels sa valeur peut être représentée : long, ulong.
  • Si le littéral est suffixe par UL, , uLUl, LUul, Lu, , lUou lu, il est de type ulong.

Si la valeur représentée par un littéral entier est en dehors de la plage du type, une erreur au moment de la ulong compilation se produit.

Remarque : En matière de style, il est suggéré que «L » soit utilisé au lieu de «l » lors de l’écriture de littéraux de type long, car il est facile de confondre la lettre «l » avec le chiffre «1 ». Note de fin

Pour permettre l’écriture des valeurs les plus petites possibles int et long les plus petites en tant que littéraux entiers, les deux règles suivantes existent :

  • Lorsqu’un Integer_Literal représentant la valeur 2147483648 (2¹¹) et qu’aucune Integer_Type_Suffix n’apparaît comme jeton immédiatement après un jeton d’opérateur unaire moins (§12.9.3), le résultat (des deux jetons) est une constante de type int avec la valeur −2147483648 (−2¹¹). Dans toutes les autres situations, une telle Integer_Literal est de type uint.
  • Lorsqu’un Integer_Literal représentant la valeur 9223372036854775808 (2⁶³) et aucun Integer_Type_Suffix ou le Integer_Type_Suffix L ou l s’affiche comme jeton immédiatement après un jeton d’opérateur moins unaire (§12.9.3), le résultat (des deux jetons) est une constante de type long avec la valeur −9223372036854775808 (−2⁶³). Dans toutes les autres situations, une telle Integer_Literal est de type ulong.

Exemple :

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

exemple de fin

6.4.5.4 Littéraux réels

Les littéraux réels sont utilisés pour écrire des valeurs de types float, doubleet 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 aucune Real_Type_Suffix n’est spécifiée, le type de la Real_Literal est double. Sinon, le Real_Type_Suffix détermine le type du littéral réel, comme suit :

  • Un vrai suffixe littéral par F ou f est de type float.

    Exemple : les littéraux 1f, 1.5f, 1e10fet 123.456F sont tous de type float. exemple de fin

  • Un vrai suffixe littéral par D ou d est de type double.

    Exemple : les littéraux 1d, 1.5d, 1e10det 123.456D sont tous de type double. exemple de fin

  • Un vrai suffixe littéral par M ou m est de type decimal.

    Exemple : les littéraux 1m, 1.5m, 1e10met 123.456M sont tous de type decimal. exemple de fin
    Ce littéral est converti en decimal valeur en prenant la valeur exacte et, si nécessaire, arrondi à la valeur représentée la plus proche à l’aide de l’arrondi du banquier (§8.3.8). Toute échelle apparente dans le littéral est conservée, sauf si la valeur est arrondie. Remarque : Par conséquent, le littéral 2.900m sera analysé pour former le signe0, le coefficient 2900et l’échelle decimal 3. Note de fin

Si l’ampleur du littéral spécifié est trop grande pour être représentée dans le type indiqué, une erreur au moment de la compilation se produit.

Remarque : en particulier, un Real_Literal ne produira jamais d’infini à virgule flottante. Une Real_Literal non nulle peut toutefois être arrondie à zéro. Note de fin

La valeur d’un littéral réel de type float ou double est déterminée à l’aide du mode IEC 60559 « arrondi au plus proche » avec des liens rompus à « même » (valeur avec le bit le moins significatif zéro) et tous les chiffres considérés comme significatifs.

Remarque : Dans un littéral réel, les chiffres décimaux sont toujours requis après la virgule décimale. Par exemple, 1.3F est un littéral réel, mais 1.F pas. Note de fin

Exemple :

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

exemple de fin

6.4.5.5 Littéraux de caractères

Un littéral de caractère représente un seul caractère et se compose d’un caractère entre guillemets, comme dans '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?
    ;

Remarque : Un caractère qui suit un caractère de barre oblique inverse (\) dans un caractère doit être l’un des caractères suivants : ', , ", \, 0, nfab, , r, . Utuxv Sinon, une erreur au moment de la compilation se produit. Note de fin

Remarque : L’utilisation de la \x production de Hexadecimal_Escape_Sequence peut être sujette à des erreurs et difficile à lire en raison du nombre variable de chiffres hexadécimaux suivant le \x. Par exemple, dans le code :

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

il peut apparaître d’abord que le caractère de début est le même (U+0009un caractère tabulation) dans les deux chaînes. En fait, la deuxième chaîne commence par U+9BAD comme les trois lettres du mot « Bad » sont des chiffres hexadécimaux valides. En tant que style, il est recommandé d’éviter \x d’utiliser des séquences d’échappement spécifiques (\t dans cet exemple) ou la séquence d’échappement de longueur \u fixe.

Note de fin

Une séquence d’échappement hexadécimale représente une seule unité de code UTF-16 Unicode, avec la valeur formée par le nombre hexadécimal suivant «\x ».

Si la valeur représentée par un littéral de caractère est supérieure U+FFFFà , une erreur au moment de la compilation se produit.

Une séquence d’échappement Unicode (§6.4.2) dans un littéral de caractère doit se trouver dans la plage U+0000 à U+FFFF.

Une séquence d’échappement simple représente un caractère Unicode, comme décrit dans le tableau ci-dessous.

Séquence d'échappement Nom du caractère Point de code Unicode
\' Guillemet simple U+0027
\" Guillemet double U+0022
\\ Barre oblique inverse U+005C
\0 Null U+0000
\a Alerte U+0007
\b Retour arrière U+0008
\f Saut de page U+000C
\n Nouvelle ligne U+000A
\r Retour chariot U+000D
\t Tabulation horizontale U+0009
\v Tabulation verticale U+000B

Le type d’un Character_Literal est char.

6.4.5.6 Littéraux de chaîne

C# prend en charge deux formes de littéraux de chaîne : littéraux de chaîne standard et littéraux de chaînes détaillées. Un littéral de chaîne standard se compose de zéro ou plusieurs caractères placés entre guillemets doubles, comme dans "hello"le cas présent, et peut inclure à la fois des séquences d’échappement simples (comme \t pour le caractère tabulation) et des séquences d’échappement hexadécimales et Unicode.

Un littéral de chaîne détaillée se compose d’un @ caractère suivi d’un caractère de guillemets doubles, de zéro ou de caractères supplémentaires et d’un caractère de guillemet double fermant.

Exemple : Un exemple simple est @"hello". exemple de fin

Dans un littéral de chaîne verbatim, les caractères entre les délimiteurs sont interprétés comme verbatim, à l’exception d’une Quote_Escape_Sequence, qui représente un caractère entre guillemets doubles. En particulier, les séquences d’échappement simples et les séquences d’échappement hexadécimales et Unicode ne sont pas traitées dans des littéraux de chaîne détaillé. Un littéral de chaîne détaillée peut s’étendre sur plusieurs lignes.

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

Exemple : l’exemple

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

montre une variété de littéraux de chaîne. Le dernier littéral de chaîne, jest un littéral de chaîne détaillé qui s’étend sur plusieurs lignes. Les caractères entre les guillemets, y compris les espaces blancs tels que les nouveaux caractères de ligne, sont conservés en détail, et chaque paire de guillemets doubles est remplacée par un caractère de ce type.

exemple de fin

Remarque : les sauts de ligne dans les littéraux de chaîne détaillée font partie de la chaîne résultante. Si les caractères exacts utilisés pour former des sauts de ligne sont sémantiquement pertinents pour une application, tous les outils qui traduisent les sauts de ligne dans le code source dans différents formats (entre «\n » et «\r\n », par exemple) modifient le comportement de l’application. Les développeurs doivent être prudents dans de telles situations. Note de fin

Remarque : Étant donné qu’une séquence d’échappement hexadécimale peut avoir un nombre variable de chiffres hexadécimaux, le littéral "\x123" de chaîne contient un caractère unique avec une valeur 123hexadécimale. Pour créer une chaîne contenant le caractère avec une valeur 12 hexadécimal suivie du caractère 3, on peut écrire "\x00123" ou "\x12" + "3" à la place. Note de fin

Le type d’un String_Literal est string.

Chaque littéral de chaîne n’entraîne pas nécessairement une nouvelle instance de chaîne. Lorsque deux littéraux de chaîne ou plus équivalents selon l’opérateur d’égalité de chaîne (§12.12.8), apparaissent dans le même assembly, ces littéraux de chaîne font référence à la même instance de chaîne.

Exemple : Par exemple, la sortie produite par

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

est True dû au fait que les deux littéraux font référence à la même instance de chaîne.

exemple de fin

6.4.5.7 Littéral null

null_literal
    : NULL
    ;

Remarque : null_literal est une règle d’analyseur, car elle n’introduit pas de nouveau type de jeton. Note de fin

Un null_literal représente une null valeur. Il n’a pas de type, mais peut être converti en type référence ou valeur nullable par le biais d’une conversion littérale Null (§10.2.7).

6.4.6 Opérateurs et ponctuateurs

Il existe plusieurs genres d’opérateurs et de signes de ponctuation. Les opérateurs sont utilisés dans les expressions pour décrire des opérations impliquant un ou plusieurs opérandes.

Exemple : L’expression a + b utilise l’opérateur + pour ajouter les deux opérandes a et b. exemple de fin

Les signes de ponctuation sont destinés au regroupement et à la séparation.

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Remarque : right_shift et right_shift_assignment sont des règles d’analyseur, car elles n’introduisent pas de nouveau type de jeton, mais représentent une séquence de deux jetons. La règle operator_or_punctuator existe uniquement à des fins descriptives et n’est pas utilisée ailleurs dans la grammaire. Note de fin

right_shift est constitué des deux jetons > et >. De même, right_shift_assignment est constituée des deux jetons > et >=. Contrairement à d’autres productions de la grammaire syntaxique, aucun caractère d’un type (pas même d’espace blanc) n’est autorisé entre les deux jetons de chacune de ces productions. Ces productions sont traitées spécialement pour permettre la gestion correcte des type_parameter_lists (§15.2.3).

Remarque : Avant l’ajout de génériques à C#, >> et >>= étaient tous deux des jetons uniques. Toutefois, la syntaxe des génériques utilise les caractères et > les < paramètres de type pour délimiter les paramètres de type et les arguments de type. Il est souvent souhaitable d’utiliser des types construits imbriqués, tels que List<Dictionary<string, int>>. Au lieu d’exiger que le programmeur sépare le programmeur et > > par un espace, la définition des deux operator_or_punctuators a été modifiée. Note de fin

6.5 Directives de prétraitement

6.5.1 Général

Les directives de prétraitement permettent d’ignorer conditionnellement des sections d’unités de compilation, de signaler des conditions d’erreur et d’avertissement, de délimiter des régions distinctes du code source et de définir le contexte nullable.

Remarque : Le terme « directives de prétraitement » est utilisé uniquement pour la cohérence avec les langages de programmation C et C++. En C#, il n’existe aucune étape de prétraitement distincte ; Les directives de prétraitement sont traitées dans le cadre de la phase d’analyse lexicale. Note de fin

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
    ;

Remarque :

  • La grammaire du préprocesseur définit un seul jeton PP_Directive lexical utilisé pour toutes les directives de prétraitement. La sémantique de chacune des directives de prétraitement est définie dans cette spécification de langage, mais pas la façon de les implémenter.
  • Le PP_Start fragment ne doit être reconnu qu’au début d’une ligne, le getCharPositionInLine() == 0 prédicat lexical lexical ANTLR ci-dessus suggère une façon dont cela peut être réalisé et n’est informatif que, une implémentation peut utiliser une stratégie différente.

Note de fin

Les directives de prétraitement suivantes sont disponibles :

  • #define et #undef, qui sont utilisés pour définir et annuler la définition, respectivement, des symboles de compilation conditionnelle (§6.5.4).
  • #if, , #else#elifet #endif, qui sont utilisés pour ignorer les sections conditionnelles du code source (§6.5.5).
  • #line, qui est utilisé pour contrôler les numéros de ligne émis pour les erreurs et les avertissements (§6.5.8).
  • #error, qui est utilisé pour émettre des erreurs (§6.5.6).
  • #region et #endregion, qui sont utilisés pour marquer explicitement les sections du code source (§6.5.7).
  • #nullable, qui est utilisé pour spécifier le contexte nullable (§6.5.9).
  • #pragma, qui est utilisé pour spécifier des informations contextuelles facultatives à un compilateur (§6.5.10).

Une directive de prétraitement occupe toujours une ligne distincte de code source et commence toujours par un # caractère et un nom de directive de prétraitement. L’espace blanc peut se produire avant le # caractère et entre le # caractère et le nom de la directive.

Une ligne source contenant un #define, #else#if#undef#endif#line#elif#endregionou #nullable une directive peut se terminer par un commentaire de ligne unique. Les commentaires délimités (le /* */ style de commentaires) ne sont pas autorisés sur les lignes sources contenant des directives de prétraitement.

Les directives de prétraitement ne font pas partie de la grammaire syntaxique de C#. Toutefois, les directives de prétraitement peuvent être utilisées pour inclure ou exclure des séquences de jetons et peuvent ainsi affecter la signification d’un programme C#.

Exemple : lorsqu’il est compilé, le programme

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

entraîne la même séquence de jetons que le programme

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

Ainsi, alors que lexicalement, les deux programmes sont assez différents, syntactiquement, ils sont identiques.

exemple de fin

6.5.2 Symboles de compilation conditionnelle

La fonctionnalité de compilation conditionnelle fournie par les directives et #else#elif#endif les #ifexpressions de prétraitement (§6.5.3) et les symboles de compilation conditionnelle sont contrôlés.

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

Notez comment une implémentation applique la restriction sur les valeurs de Basic_Identifier autorisées est un problème d’implémentation. Note de fin

Deux symboles de compilation conditionnelle sont considérés comme identiques s’ils sont identiques après l’application des transformations suivantes, dans l’ordre :

  • Chaque Unicode_Escape_Sequence est transformé en son caractère Unicode correspondant.
  • Toutes les Formatting_Characters sont supprimées.

Un symbole de compilation conditionnelle a deux états possibles : défini ou non défini. Au début du traitement lexical d’une unité de compilation, un symbole de compilation conditionnel n’est pas défini, sauf s’il a été explicitement défini par un mécanisme externe (par exemple, une option de compilateur de ligne de commande). Lorsqu’une #define directive est traitée, le symbole de compilation conditionnelle nommé dans cette directive devient défini dans cette unité de compilation. Le symbole reste défini jusqu’à ce qu’une #undef directive pour ce même symbole soit traitée, ou jusqu’à ce que la fin de l’unité de compilation soit atteinte. Cela implique que #define les #undef directives d’une unité de compilation n’ont aucun effet sur d’autres unités de compilation dans le même programme.

Lorsqu’il est référencé dans une expression de prétraitement (§6.5.3), un symbole de compilation conditionnelle défini a la valeur truebooléenne et un symbole de compilation conditionnelle non défini a la valeur falsebooléenne. Il n’est pas nécessaire que les symboles de compilation conditionnelle soient explicitement déclarés avant qu’ils ne soient référencés dans les expressions de prétraitement. Au lieu de cela, les symboles non déclarés sont simplement non définis et ont donc la valeur false.

L’espace de noms pour les symboles de compilation conditionnelle est distinct et distinct de toutes les autres entités nommées dans un programme C#. Les symboles de compilation conditionnelle ne peuvent être référencés que dans #define les directives et #undef dans les expressions de prétraitement.

6.5.3 Expressions de prétraitement

Les expressions de prétraitement peuvent se produire et les #if #elif directives. Les opérateurs ! (négation logique de préfixe uniquement), ==, !=, &&et || sont autorisés dans les expressions de prétraitement et les parenthèses peuvent être utilisés pour le regroupement.

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

Lorsqu’il est référencé dans une expression de prétraitement, un symbole de compilation conditionnelle défini a la valeur truebooléenne et un symbole de compilation conditionnelle non défini a la valeur falsebooléenne.

L’évaluation d’une expression de prétraitement génère toujours une valeur booléenne. Les règles d’évaluation d’une expression de prétraitement sont identiques à celles d’une expression constante (§12.23), sauf que les seules entités définies par l’utilisateur qui peuvent être référencées sont des symboles de compilation conditionnelle.

6.5.4 Directives de définition

Les directives de définition sont utilisées pour définir ou annuler les symboles de compilation conditionnelle.

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

Le traitement d’une #define directive entraîne la définition du symbole de compilation conditionnelle donné, en commençant par la ligne source qui suit la directive. De même, le traitement d’une #undef directive entraîne l’annulation de la définition du symbole de compilation conditionnelle donné, en commençant par la ligne source qui suit la directive.

Toutes #define les directives d’une #undef unité de compilation doivent se produire avant le premier jeton (§6.4) dans l’unité de compilation ; sinon, une erreur au moment de la compilation se produit. En termes intuitifs, #define et #undef les directives précèdent tout « code réel » dans l’unité de compilation.

Exemple : l’exemple :

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

est valide, car les #define directives précèdent le premier jeton (le namespace mot clé) dans l’unité de compilation.

exemple de fin

Exemple : L’exemple suivant génère une erreur au moment de la compilation, car un #define suit le code réel :

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

exemple de fin

Un #define peut définir un symbole de compilation conditionnelle déjà défini, sans qu’il y ait d’intervention #undef pour ce symbole.

Exemple : L’exemple ci-dessous définit un symbole de compilation conditionnelle A, puis le définit à nouveau.

#define A
#define A

Pour les compilateurs qui autorisent la définition de symboles de compilation conditionnels comme options de compilation, une autre façon de définir le symbole en tant qu’option de compilateur et dans la source consiste à définir le symbole comme option de compilation.

exemple de fin

Un #undef symbole de compilation conditionnelle qui n’est pas défini peut « annuler la définition » d’un symbole de compilation conditionnelle.

Exemple : L’exemple ci-dessous définit un symbole A de compilation conditionnelle, puis l’annule deux fois ; bien que le deuxième #undef n’ait aucun effet, il est toujours valide.

#define A
#undef A
#undef A

exemple de fin

6.5.5 Directives de compilation conditionnelle

Les directives de compilation conditionnelle sont utilisées pour inclure ou exclure des parties d’une unité de compilation de manière conditionnelle.

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

Les directives de compilation conditionnelle doivent être écrites dans des groupes composés, dans l’ordre, d’une #if directive, de zéro ou plusieurs #elif directives, de zéro ou d’une directive et d’une #else #endif directive. Les directives sont des sections conditionnelles du code source. Chaque section est contrôlée par la directive immédiatement précédente. Une section conditionnelle peut elle-même contenir des directives de compilation conditionnelle imbriquées, à condition que ces directives forment des groupes complets.

Au plus l’une des sections conditionnelles contenues est sélectionnée pour le traitement lexical normal :

  • Les PP_Expressiondes directives sont #elif évaluées dans l’ordre #if jusqu’à ce qu’un rendement soit générétrue. Si une expression génère true, la section conditionnelle qui suit la directive correspondante est sélectionnée.
  • Si tous les PP_Expressionrendement falseet si une #else directive est présente, la section conditionnelle qui suit la #else directive est sélectionnée.
  • Sinon, aucune section conditionnelle n’est sélectionnée.

La section conditionnelle sélectionnée, le cas échéant, est traitée comme une input_section normale : le code source contenu dans la section doit respecter la grammaire lexicale ; les jetons sont générés à partir du code source de la section ; et les directives de prétraitement de la section ont les effets prescrits.

Les sections conditionnelles restantes sont ignorées et aucun jeton, à l’exception des directives de prétraitement, sont générés à partir du code source. Par conséquent, le code source ignoré, sauf les directives de prétraitement, peut être lexicalement incorrect. Les directives de prétraitement ignorées doivent être lexicalement correctes, mais ne sont pas traitées autrement. Dans une section conditionnelle ignorée, toutes les sections conditionnelles imbriquées (contenues dans les constructions imbriquées) sont également ignorées #if...#endif .

Remarque : La grammaire ci-dessus ne capture pas l’allocation que les sections conditionnelles entre les directives de prétraitement peuvent être mal formées de manière lexicale. Par conséquent, la grammaire n’est pas prête pour ANTLR, car elle prend uniquement en charge l’entrée lexicalement correcte. Note de fin

Exemple : L’exemple suivant illustre la façon dont les directives de compilation conditionnelle peuvent imbriquer :

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

À l’exception des directives de prétraitement, le code source ignoré n’est pas soumis à une analyse lexicale. Par exemple, les éléments suivants sont valides malgré le commentaire non déterminé dans la #else section :

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

Notez toutefois que les directives de prétraitement doivent être lexicalement correctes même dans les sections ignorées du code source.

Les directives de prétraitement ne sont pas traitées lorsqu’elles apparaissent à l’intérieur d’éléments d’entrée multiligne. Par exemple, le programme :

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

aboutit à la sortie :

hello,
#if Debug
        world
#else
        Nebraska
#endif

Dans des cas particuliers, l’ensemble des directives de prétraitement traitées peut dépendre de l’évaluation du pp_expression. L'exemple :

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

produit toujours le même flux de jetons (),class }Q { qu’il soit défini ou non.X Si X elle est définie, les seules directives traitées sont #if et#endif, en raison de l’commentaire de ligne multi-commentaire de ligne. Si X elle n’est pas définie, trois directives (#if, #else, #endif) font partie de l’ensemble de directives.

exemple de fin

6.5.6 Directives de diagnostic

Les directives de diagnostic sont utilisées pour générer des messages d’erreur et d’avertissement explicites signalés de la même façon que d’autres erreurs et avertissements au moment de la compilation.

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

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Exemple : l’exemple

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

génère une erreur au moment de la compilation (« Une build ne peut pas être à la fois déboguée et commerciale ») si les symboles Debug de compilation conditionnelle et Retail sont tous les deux définis. Notez qu’un PP_Message peut contenir du texte arbitraire ; en particulier, il n’a pas besoin de contenir de jetons bien formés, comme indiqué par la citation unique dans le mot can't.

exemple de fin

6.5.7 Directives de région

Les directives de région sont utilisées pour marquer explicitement les régions du code source.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

Aucune signification sémantique n’est attachée à une région ; les régions sont destinées à être utilisées par le programmeur ou par des outils automatisés pour marquer une section de code source. Il y aura une #endregion directive correspondant à chaque #region directive. Le message spécifié dans une #region ou #endregion directive n’a pas de signification sémantique ; il sert simplement à identifier la région. La correspondance #region et #endregion les directives peuvent avoir des PP_Messagedifférentes.

Traitement lexical d’une région :

#region
...
#endregion

correspond exactement au traitement lexical d’une directive de compilation conditionnelle du formulaire :

#if true
...
#endif

Remarque : cela signifie qu’une région peut inclure un ou plusieurs #if/.../#endif, ou être contenue avec une section conditionnelle dans un #if/.../#endif ; mais qu’une région ne peut pas chevaucher une partie juste d’un #if/.../#endif, ou démarrer et se terminer dans différentes sections conditionnelles. Note de fin

6.5.8 Directives de ligne

Les directives de ligne peuvent être utilisées pour modifier les numéros de ligne et les noms d’unités de compilation signalés par le compilateur dans la sortie, tels que les avertissements et les erreurs. Ces valeurs sont également utilisées par les attributs caller-info (§22.5.6).

Remarque : Les directives de ligne sont les plus couramment utilisées dans les outils de méta-programmation qui génèrent du code source C# à partir d’une autre entrée de texte. Note de fin

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

Lorsqu’aucune directive n’est #line présente, le compilateur signale les numéros de ligne vrais et les noms d’unités de compilation dans sa sortie. Lors du traitement d’une directive qui inclut un #line PP_Line_Indicator qui n’est pas default, le compilateur traite la ligne après la directive comme ayant le numéro de ligne donné (et le nom de l’unité de compilation, si spécifié).

La valeur maximale autorisée pour Decimal_Digit+ est définie par l’implémentation.

Une #line default directive annule l’effet de toutes les directives précédentes #line . Le compilateur signale des informations de ligne vraies pour les lignes suivantes, exactement comme si aucune directive n’avait #line été traitée.

Une #line hidden directive n’a aucun effet sur l’unité de compilation et les numéros de ligne signalés dans les messages d’erreur ou produits par l’utilisation de CallerLineNumberAttribute (§22.5.6.2). Il est destiné à affecter les outils de débogage au niveau source afin que, lors du débogage, toutes les lignes entre une #line hidden directive et la directive suivante #line (ce n’est pas le cas) n’aient aucune #line hiddeninformation de numéro de ligne et soient ignorées entièrement lors de l’exécution pas à pas du code.

Remarque : Bien qu’un PP_Compilation_Unit_Name puisse contenir du texte qui ressemble à une séquence d’échappement, ce texte n’est pas une séquence d’échappement ; dans ce contexte, un caractère «\ » désigne simplement un caractère de barre oblique inverse ordinaire. Note de fin

6.5.9 Directive Nullable

La directive Nullable contrôle le contexte nullable, comme décrit ci-dessous.

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

Une directive nullable définit les indicateurs disponibles pour les lignes de code suivantes, jusqu’à ce qu’une autre directive nullable la remplace, ou jusqu’à ce que la fin de la _unit de compilation soit atteinte. Le contexte nullable contient deux indicateurs : annotations et avertissements. L’effet de chaque forme de directive nullable est, comme suit :

  • #nullable disable: désactive les annotations nullables et les indicateurs d’avertissement nullables.
  • #nullable enable: active les annotations nullables et les indicateurs d’avertissement nullables.
  • #nullable restore: restaure les annotations et les indicateurs d’avertissement à l’état spécifié par le mécanisme externe, le cas échéant.
  • #nullable disable annotations: désactive l’indicateur d’annotations nullables. L’indicateur d’avertissements nullables n’est pas affecté.
  • #nullable enable annotations: active l’indicateur d’annotations nullables. L’indicateur d’avertissements nullables n’est pas affecté.
  • #nullable restore annotations: restaure l’indicateur d’annotations nullables à l’état spécifié par le mécanisme externe, le cas échéant. L’indicateur d’avertissements nullables n’est pas affecté.
  • #nullable disable warnings: désactive l’indicateur d’avertissements nullables. L’indicateur d’annotations nullables n’est pas affecté.
  • #nullable enable warnings: active l’indicateur d’avertissements nullables. L’indicateur d’annotations nullables n’est pas affecté.
  • #nullable restore warnings: restaure l’indicateur d’avertissements nullables à l’état spécifié par le mécanisme externe, le cas échéant. L’indicateur d’annotations nullables n’est pas affecté.

L’état nullable des expressions est suivi à tout moment. L’état de l’indicateur d’annotation et la présence ou l’absence d’une annotation nullable, ?détermine l’état null initial d’une déclaration de variable. Les avertissements sont émis uniquement lorsque l’indicateur d’avertissement est activé.

Exemple : l’exemple

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

génère un avertissement au moment de la compilation (« tel est x null»). L’état nullable de x est suivi partout. Un avertissement est émis lorsque l’indicateur d’avertissement est activé.

exemple de fin

6.5.10 Directives pragma

La #pragma directive de prétraitement est utilisée pour spécifier des informations contextuelles à un compilateur.

Remarque : Par exemple, un compilateur peut fournir des #pragma directives qui

  • Activez ou désactivez des messages d’avertissement particuliers lors de la compilation du code suivant.
  • Spécifiez les optimisations à appliquer au code suivant.
  • Spécifiez les informations à utiliser par un débogueur.

Note de fin

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

Les Input_Characterdu PP_Pragma_Text sont interprétés par le compilateur de manière définie par l’implémentation. Les informations fournies dans une #pragma directive ne modifient pas la sémantique du programme. Une #pragma directive modifie uniquement le comportement du compilateur qui n’est pas dans l’étendue de cette spécification de langage. Si le compilateur ne peut pas interpréter les Input_Characters, le compilateur peut générer un avertissement ; toutefois, il ne génère pas d’erreur au moment de la compilation.

Remarque : PP_Pragma_Text peut contenir du texte arbitraire ; en particulier, il n’a pas besoin de contenir de jetons bien formés. Note de fin