Поделиться через


6 Лексическая структура

6.1 Программы

Программа C# состоит из одного или нескольких исходных файлов, известных как единицы компиляции (§14.2). Хотя единица компиляции может иметь одно-одно соответствие с файлом в файловой системе, такая связь не требуется.

Концептуально говоря, программа компилируется с помощью трех шагов:

  1. Преобразование, которое преобразует файл из определенного репертуара символов и схемы кодирования в последовательность символов Юникода.
  2. Лексический анализ, который преобразует поток входных символов Юникода в поток токенов.
  3. Синтаксический анализ, который преобразует поток токенов в исполняемый код.

Соответствующие реализации должны принимать единицы компиляции Юникода, закодированные с помощью формы кодирования UTF-8 (как определено стандартом Юникода), и преобразовывать их в последовательность символов Юникода. Реализации могут принимать и преобразовывать дополнительные схемы кодировки символов (например, UTF-16, UTF-32 или символьные сопоставления, отличные от Юникода).

Примечание. Обработка символа NULL Юникода (U+0000) определена реализацией. Настоятельно рекомендуется, чтобы разработчики не использовали этот символ в исходном коде, чтобы обеспечить переносимость и удобочитаемость. Если символ требуется в символе или строковом литерале, escape-последовательности \0 или \u0000 могут использоваться вместо этого. конечная заметка

Примечание. Это выходит за рамки этой спецификации, чтобы определить, как файл с использованием символьного представления, отличного от Юникода, может быть преобразован в последовательность символов Юникода. Однако во время такого преобразования рекомендуется перевести обычный символ с разделителями строк (или последовательность) в другой набор символов в двухзначную последовательность, состоящую из символа возврата каретки Юникода (U+000D), за которым следует символ строки Юникода (U+000A). В большинстве случаев это преобразование не будет иметь видимых эффектов; однако это повлияет на интерпретацию строковых литеральных маркеров (§6.4.5.6). Цель этой рекомендации — разрешить подробный строковый литерал создавать одну и ту же последовательность символов при перемещении единицы компиляции между системами, поддерживающими разные наборы символов, отличных от Юникода, в частности, при использовании разных последовательностей символов для разделения строк. конечная заметка

6.2 Грамматики

6.2.1 Общие

Эта спецификация содержит синтаксис языка программирования C# с помощью двух грамматик. Лексическая грамматика (§6.2.3) определяет, как символы Юникода объединяются для формирования терминаторов строк, пробелов, комментариев, маркеров и директив предварительной обработки. Синтаксическая грамматика (§6.2.4) определяет, как маркеры, полученные из лексической грамматики, объединяются для формирования программ C#.

Все символы терминала должны рассматриваться как соответствующий символ Юникода из диапазона U+0020 до U+007F, а не любые похожие символы из других диапазонов символов Юникода.

Нотация грамматики 6.2.2

Лексические и синтаксические грамматики представлены в форме расширенного backus-Naur средства грамматики ANTLR.

Хотя используется нотация ANTLR, эта спецификация не содержит полностью готовой к ANTLR "эталонной грамматике" для C#; Написание лексера и синтаксического анализатора либо с помощью средства, например ANTLR, выходит за рамки спецификации языка. С этой квалификацией эта спецификация пытается свести к минимуму разрыв между указанной грамматикой и необходимой для создания лексера и синтаксического анализа в ANTLR.

ANTLR отличается от лексического и синтаксического синтаксического анализатора ANTLR, грамматики в нотации путем запуска лексических правил с прописными буквами и правилами синтаксического анализа с строчной буквой.

Примечание. Лексическая грамматика C# (§6.2.3) и синтаксическая грамматика (§6.2.4) не совпадают с делением ANTLR на лексические и синтаксические граммы. Это небольшое несоответствие означает, что некоторые правила синтаксического анализа ANTLR используются при указании лексической грамматики C#. конечная заметка

6.2.3 Лексическая грамматика

Лексическая грамматика C# представлена в §6.3, §6.4 и §6.5. Символы лексической грамматики — это символы набора символов Юникода, а лексическая грамматика указывает, как символы объединяются для формирования маркеров (§6.4), пробела (§6.3.4), комментариев (§6.3.3) и директив предварительной обработки (§6.5).

Многие символы терминала синтаксической грамматики не определяются явно как маркеры в лексической грамматике. Скорее, преимуществом является поведение ANTLR, которое литеральные строки в грамматике извлекаются как неявные лексические маркеры; это позволяет ключевым словам, операторам и т. д. представляться в грамматике их литеральным представлением, а не именем токена.

Каждая единица компиляции в программе C# должна соответствовать входной среде лексической грамматики (§6.3.1).

6.2.4 Синтаксическая грамматика

Синтаксическая грамматика C# представлена в предложениях, подклаузах и приложениях, которые следуют этому подклаузу. Символы терминала синтаксической грамматики — это маркеры, определенные явно лексической грамматикой и неявно строками в самой грамматике (§6.2.3). Синтаксическая грамматика указывает, как маркеры объединяются для формирования программ C#.

Каждая единица компиляции в программе C# должна соответствовать compilation_unit рабочей (§14.2) синтаксической грамматики.

6.2.5 Неоднозначность грамматики

Производство для simple_name (§12.8.4) и member_access (§12.8.7) может привести к неоднозначности грамматики для выражений.

Пример: инструкция:

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

можно интерпретировать как вызов F с двумя аргументами и G < A B > (7). Кроме того, он может быть интерпретирован как вызов F с одним аргументом, который является вызовом универсального метода G с двумя аргументами типа и одним регулярным аргументом.

пример конца

Если последовательность маркеров можно проанализировать (в контексте) как simple_name (§12.8.4), member_access (§12.8.7) или pointer_member_access (§23.6.3) с type_argument_list (§8.4.2), маркер сразу после закрывающего > маркера проверяется, чтобы узнать, является ли он

  • Одно из ( ) ] } : ; , . ? == != | ^ && || & [; или
  • Один из реляционных операторов < <= >= is as; или
  • Ключевое слово контекстного запроса, отображающееся внутри выражения запроса; или
  • В определенных контекстах идентификатор обрабатывается как диамбигирующий маркер. Эти контексты находятся в том случае, когда последовательность маркеровis, несочетаемых, немедленно предшествует одному из ключевых слов или outcase возникает при анализе первого элемента кортежа литерала (в этом случае маркеры ( предшествуют или идентификатору следуют ,) или : последующим элементом кортежа.

Если следующий маркер находится в этом списке или идентификатор в таком контексте, type_argument_list сохраняется в составе simple_name, member_access или pointer_member доступа , а также любой другой возможный анализ последовательности маркеров удаляется. В противном случае type_argument_list не считается частью simple_name, member_access или pointer_member_access, даже если нет другого возможного анализа последовательности маркеров.

Примечание. Эти правила не применяются при анализе type_argument_list в namespace_or_type_name (§7.8). конечная заметка

Пример: инструкция:

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

в соответствии с этим правилом интерпретируется как вызов с одним аргументом, который является вызовом F универсального метода G с двумя аргументами типа и одним регулярным аргументом. Инструкции

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

каждый из них интерпретируется как вызов F с двумя аргументами. Оператор

x = F<A> + y;

будет интерпретирован как оператор меньше, чем оператор больше, чем оператор и унарный плюс оператор, как если бы оператор был написанx = (F < A) > (+y), а не как simple_name с type_argument_list, за которым следует оператор binary-plus. В инструкции

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

маркеры C<T> интерпретируются как namespace_or_type_name с type_argument_list из-за наличия различающегося маркера && после type_argument_list.

Выражение представляет собой кортеж (A < B, C > D) с двумя элементами, каждый из которых сравнивается.

Выражение представляет собой кортеж (A<B,C> D, E) с двумя элементами, первым из которых является выражение объявления.

Вызов M(A < B, C > D, E) имеет три аргумента.

M(out A<B,C> D, E) Вызов имеет два аргумента, первое из которых — out объявление.

Выражение e is A<B> C использует шаблон объявления.

Метка case A<B> C: дела использует шаблон объявления.

пример конца

При распознавании relational_expression (§12.12.1), если применяются альтернативные варианты "relational_expression is" и "relational_expression is constant_pattern", а тип разрешается в доступный тип, то будет выбран альтернативный тип relational_expression. is

6.3 Лексический анализ

6.3.1 Общие

Для удобства лексическая грамматика определяет и ссылается на следующие именованные токены lexer:

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

Хотя это правила лексера, эти имена написаны во всех прописных буквах, чтобы отличить их от обычных имен правил лексера.

Примечание. Эти удобные правила являются исключениями обычной практики не предоставлять явные имена маркеров для маркеров, определенных литеральными строками. конечная заметка

Входная рабочая среда определяет лексическую структуру единицы компиляции C#.

input
    : input_section?
    ;

input_section
    : input_section_part+
    ;

input_section_part
    : input_element* New_Line
    | PP_Directive
    ;

input_element
    : Whitespace
    | Comment
    | token
    ;

Примечание. Описанная выше грамматика описана правилами синтаксического анализа ANTLR, она определяет лексическую структуру единицы компиляции C#, а не лексические маркеры. конечная заметка

Пять основных элементов составляют лексическую структуру единицы компиляции C#: терминаторы линий (§6.3.2), пробелы (§6.3.4), комментарии (§6.3.3), маркеры (§6.4) и директивы предварительной обработки (§6.5). Из этих основных элементов только маркеры являются значительными в синтаксической грамматике программы C# (§6.2.4).

Лексическая обработка единицы компиляции C# состоит из уменьшения файла в последовательность маркеров, которые становятся входными данными для синтаксического анализа. Терминаторы, пробелы и комментарии могут служить отдельным маркерам, а директивы предварительной обработки могут привести к пропускам разделов единицы компиляции, но в противном случае эти лексические элементы не влияют на синтаксическую структуру программы C#.

Если несколько лексических выражений грамматики соответствуют последовательности символов в единице компиляции, лексическая обработка всегда формирует самый длинный лексический элемент.

Пример. Последовательность // символов обрабатывается как начало одно строковый комментарий, так как этот лексический элемент длиннее одного / маркера. пример конца

Некоторые маркеры определяются набором лексических правил; основное правило и одно или несколько вложенных правил. Последний отмечен в грамматике, fragment чтобы указать, что правило определяет часть другого маркера. Правила фрагментов не рассматриваются в порядке сверху вниз лексических правил.

Примечание. В ANTLR fragment используется ключевое слово, которое создает то же поведение, определенное здесь. конечная заметка

6.3.2 Конца линии

Терминаторы строки разделяют символы единицы компиляции C# на строки.

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

Для обеспечения совместимости с средствами редактирования исходного кода, добавляющими маркеры конца файла, и чтобы модуль компиляции отображался как последовательность правильно завершенных строк, к каждому блоку компиляции в программе C# применяются следующие преобразования:

  • Если последний символ единицы компиляции является символом Control-Z (U+001A), этот символ удаляется.
  • Символ возврата каретки (U+000D) добавляется в конец единицы компиляции, если эта единица компиляции не пуста, а если последний символ единицы компиляции не является возвратом каретки (U+000D), каналом строки (U+000A), следующим символом строки (U+0085), разделителем строк (U+2028) или разделителем абзаца (U+2029).

Примечание. Дополнительный возврат каретки позволяет программе заканчиваться PP_Directive (§6.5), которая не имеет конца New_Line. конечная заметка

Комментарии 6.3.3

Поддерживаются две формы комментариев: разделенные комментарии и одно строковый комментарий.

Комментарий с разделителями начинается с символов /* и заканчивается символами.*/ Разделители могут занимать часть строки, одной строки или нескольких строк.

Пример: пример

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

содержит разделенный комментарий.

пример конца

Одно строковый комментарий начинается с символов // и расширяется до конца строки.

Пример: пример

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

отображает несколько одноуровневых строковый комментарий.

пример конца

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
    ;

Примечания не вложены. Последовательности символов /* и */ не имеют специального значения в однострочном комментарии, а последовательности символов // и /* не имеют специального значения в комментариях с разделителями.

Комментарии не обрабатываются в символьных и строковых литералах.

Примечание. Эти правила необходимо тщательно интерпретировать. Например, в приведенном ниже примере комментарий с разделителями, начинающийся до окончания A между B и C(). Причина заключается в том, что

// B */ C();

не является на самом деле одним строковый комментарий, так как // не имеет особого значения внутри разделителя, и поэтому */ имеет свое обычное специальное значение в этой строке.

Аналогичным образом, комментарий с разделителями, начинающийся до D окончания.E Причина заключается в том, что "D */ " на самом деле не строковый литерал, так как начальный символ двойной кавычки отображается внутри разделителя.

Полезное следствие /* и */ отсутствие специального значения в одном строковый комментарий заключается в том, что блок строк исходного кода можно закомментировать путем размещения // в начале каждой строки. Как правило, он не работает, чтобы поместить /* перед этими строками и */ после них, так как это неправильно инкапсулирует комментарии с разделителями в блоке, и в целом может полностью изменить структуру таких комментариев с разделителями.

Пример кода:

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

конечная заметка

Single_Line_Comment и Delimited_Commentс определенными форматами можно использовать в качестве комментариев документации, как описано в разделе "D".

6.3.4 Пробелы

Пробелы определяются как любой символ с классом Z класса Юникод (который включает пробел), а также горизонтальный символ табуляции, символ вертикальной вкладки и символ веб-канала формы.

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

6.4 Токены

6.4.1 Общие

Существует несколько типов маркеров: идентификаторы, ключевые слова, литералы, операторы и пунктуаторы. Пробелы и комментарии не являются маркерами, хотя они являются разделителями для токенов.

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

Примечание. Это правило синтаксического анализа ANTLR, оно не определяет лексический токен, а скорее коллекцию типов маркеров. конечная заметка

Escape-последовательности символов Юникода 6.4.2

Escape-последовательность Юникода представляет кодовую точку Юникода. Escape-последовательности Юникода обрабатываются в идентификаторах (§6.4.3), символьных литералах (§6.4.5.5), регулярных строковых литералах (§6.4.5.6) и интерполированных регулярных строковых выражений (§12.8.3). Escape-последовательность Юникода не обрабатывается в другом расположении (например, для формирования оператора, пунктуатора или ключевого слова).

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
    ;

Escape-последовательность символов Юникода представляет одну точку кода Юникода, сформированную шестнадцатеричным числом после символов "\u" или "\U". Так как C# использует 16-разрядную кодировку точек кода Юникода в символьных и строковых значениях, то кодовая точка Юникода в диапазоне U+10000 U+10FFFF представлена двумя суррогатными единицами кода Юникода. Приведенные выше U+FFFF кодовые точки Юникода не допускаются в символьных литералах. Приведенные выше U+10FFFF кодовые точки Юникода недопустимы и не поддерживаются.

Несколько переводов не выполняются. Например, строковый литерал "\u005Cu005C" эквивалентен "\u005C" , а не "\".

Примечание. Значение \u005C Юникода — это символ "\". конечная заметка

Пример: пример

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

показывает несколько вариантов \u0066использования , который является escape-последовательностью для буквы "f". Программа эквивалентна

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

пример конца

6.4.3 Идентификаторы

Правила для идентификаторов, указанных в этом подразделе, соответствуют именно тем, которые рекомендуется в приложении Юникод стандартный 15, за исключением того, что символ подчеркивания разрешен в качестве начального символа (как традиционно в языке программирования C), escape-последовательности Юникода разрешены в идентификаторах, а символ "@" разрешен в качестве префикса, чтобы ключевые слова использовались в качестве идентификаторов.

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
    ;

Примечание.

  • Сведения о классах символов Юникода, упомянутых выше, см. в разделе "Стандартный Юникод".
  • Фрагмент Available_Identifier требует исключения ключевых слов и контекстных ключевых слов. Если грамматика в этой спецификации обрабатывается с помощью ANTLR, это исключение обрабатывается автоматически семантикой ANTLR:
    • Ключевые слова и контекстные ключевые слова происходят в грамматике в виде литеральных строк.
    • ANTLR создает неявные лексические правила токена, созданные из этих литеральных строк.
    • ANTLR считает эти неявные правила до явных лексических правил в грамматике.
    • Поэтому фрагмент Available_Identifier не будет соответствовать ключевым словам или контекстным ключевым словам в качестве лексических правил для тех, кто предшествует ему.
  • Фрагмент Escaped_Identifier включает в себя экранированные ключевые слова и контекстные ключевые слова, так как они являются частью более длинного маркера, начиная с @ и лексической обработки всегда формирует самый длинный лексический элемент (§6.3.1).
  • Как реализация применяет ограничения на допустимые значения Unicode_Escape_Sequence является проблемой реализации.

конечная заметка

Примеры допустимых идентификаторов: identifier1_identifier2и @if. пример конца

Идентификатор в соответствующей программе должен находиться в каноническом формате, определенном формой нормализации Юникода C, как определено в приложении Юникода Standard 15. Поведение при обнаружении идентификатора, не в форме нормализации C, определяется реализацией; Однако диагностика не требуется.

Префикс "@" позволяет использовать ключевые слова в качестве идентификаторов, что полезно при взаимодействии с другими языками программирования. Символ @ не является частью идентификатора, поэтому его можно увидеть на других языках как обычный идентификатор без префикса. Идентификатор с @ префиксом называется подробным идентификатором.

Примечание. Использование @ префикса для идентификаторов, которые не являются ключевыми словами, разрешены, но настоятельно не рекомендуется использовать в стиле. конечная заметка

Пример: пример:

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

определяет класс с именем "class" со статическим методом с именем "static", который принимает параметр с именем "bool". Обратите внимание, что, поскольку escape-адреса Юникода не разрешены в ключевых словах, маркер "cl\u0061ss" является идентификатором и является таким же идентификатором, как "@class".

пример конца

Два идентификатора считаются одинаковыми, если они идентичны после применения следующих преобразований, в порядке:

  • Префикс "@", если используется, удаляется.
  • Каждый Unicode_Escape_Sequence преобразуется в соответствующий символ Юникода.
  • Все Formatting_Characterудаляются.

Семантика именованного _ идентификатора зависит от контекста, в котором он отображается:

  • Он может обозначить именованный элемент программы, например переменную, класс или метод, или
  • Он может обозначить отмену (§9.2.9.1).

Идентификаторы, содержащие два последовательных символа подчеркивания (U+005F), зарезервированы для использования реализацией. Однако при определении такого идентификатора диагностика не требуется.

Примечание. Например, реализация может предоставлять расширенные ключевые слова, начинающиеся с двух символов подчеркивания. конечная заметка

Ключевые слова 6.4.4

Ключевое слово — это последовательность символов, которая зарезервирована, и ее нельзя использовать в качестве идентификатора, за исключением случаев, когда предисловит @ символ.

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

Контекстное ключевое слово — это последовательность символов, которая имеет особое значение в определенных контекстах, но не зарезервирована и может использоваться в качестве идентификатора за пределами этих контекстов, а также при предисловии символа@.

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

Примечание. Ключевое слово правил и contextual_keyword являются правилами синтаксического анализа, так как они не вводят новые типы маркеров. Все ключевые слова и контекстные ключевые слова определяются неявными лексическими правилами, так как они происходят как литералы в грамматике (§6.2.3). конечная заметка

В большинстве случаев синтаксическое расположение контекстных ключевых слов такое, что они никогда не могут быть путаны с обычным использованием идентификаторов. Например, в объявлении get свойства и set идентификаторы имеют особое значение (§15.7.3). Идентификатор, отличный от get или set никогда не допускается в этих расположениях, поэтому это использование не конфликтует с использованием этих слов в качестве идентификаторов.

В некоторых случаях грамматика недостаточно, чтобы отличить контекстное использование ключевых слов от идентификаторов. Во всех таких случаях будет указано, как диамбигуировать между двумя. Например, контекстное ключевое слово var в неявно типизированных объявлениях локальных переменных (§13.6.2) может конфликтовить с объявленным типом var, в этом случае объявленное имя имеет приоритет над использованием идентификатора в качестве контекстного ключевого слова.

Другим примером такого диамбигации является контекстное ключевое слово await (§12.9.8.1), которое считается ключевым словом только в том случае, если внутри метода объявлен async, но может использоваться в качестве идентификатора в другом месте.

Как и в случае с ключевыми словами, контекстные ключевые слова можно использовать как обычные идентификаторы, префиксируя их символом @ .

Примечание. При использовании в качестве контекстных ключевых слов эти идентификаторы не могут содержать Unicode_Escape_Sequences. конечная заметка

6.4.5 Литералы

6.4.5.1 Общие

Литерал (§12.8.2) — это представление значения в исходном коде.

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

Примечание. Литерал является правилом синтаксического анализа, так как он группирует другие виды маркеров и не вводит новый тип маркера. конечная заметка

6.4.5.2 Логические литералы

Существует два логических литерала: true и false.

boolean_literal
    : TRUE
    | FALSE
    ;

Примечание. boolean_literal является правилом синтаксического анализа, так как он группирует другие типы маркеров и не вводит новый тип маркера. конечная заметка

Тип boolean_literal .bool

6.4.5.3 Целые литералы

Целые литералы используются для записи значений типов int, uintи longulong. Целые литералы имеют три возможные формы: десятичные, шестнадцатеричные и двоичные.

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

Тип целочисленного литерала определяется следующим образом:

  • Если литерал не имеет суффикса, он имеет первый из этих типов, в которых его значение может быть представлено: int, uint, long. ulong
  • Если литерал суффиксирован U или uимеет первый из этих типов, в которых его значение может быть представлено: uint, . ulong
  • Если литерал суффиксирован L или lимеет первый из этих типов, в которых его значение может быть представлено: long, . ulong
  • Если литерал суффиксирован UlULuL, , , ul, LuLU, lUили luимеет типulong.

Если значение, представленное целым литералом, выходит за пределы диапазона ulong типа, возникает ошибка во время компиляции.

Примечание. В вопросе стиля рекомендуется использовать "L" вместо "l" при написании литералы типа long, так как легко спутать букву "l" с цифрой "1". конечная заметка

Чтобы позволить наименьшим int возможным и long значениям записываться как целые литералы, существуют следующие два правила:

  • Если Integer_Literal, представляющее значение 2147483648 (2КО) и не Integer_Type_Suffix отображается как маркер сразу после унарного маркера оператора минус (§12.9.3), результат (обоих маркеров) является константой типа int со значением −2147483648 (-2 мят). Во всех других ситуациях такой Integer_Literal имеет тип uint.
  • Если Integer_Literal, представляющее значение 9223372036854775808 (2⁶⁶ Integer_Type_Suffix) L или Integer_Type_Suffix или l отображается в качестве маркера сразу после маркера унарного оператора минуса (§12.9.3), результат (оба маркера) является константой типа long со значением −9223372036854775808 (-2⁶⁶⁶⁶). Во всех других ситуациях такой Integer_Literal имеет тип ulong.

Пример:

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

пример конца

6.4.5.4 Реальные литералы

Реальные литералы используются для записи значений типов float, doubleи 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'
    ;

Если Real_Type_Suffix нет, тип Real_Literal равен double. В противном случае Real_Type_Suffix определяет тип реального литерала следующим образом:

  • Реальный литерал суффиксирован F или f имеет тип float.

    Пример: литералы1f, 1.5fи 1e10f123.456F все они являются типамиfloat. пример конца

  • Реальный литерал суффиксирован D или d имеет тип double.

    Пример: литералы1d, 1.5dи 1e10d123.456D все они являются типамиdouble. пример конца

  • Реальный литерал суффиксирован M или m имеет тип decimal.

    Пример: литералы1m, 1.5mи 1e10m123.456M все они являются типамиdecimal. пример конца
    Этот литерал преобразуется в decimal значение, принимая точное значение, а при необходимости округляется до ближайшего представляющего значения с помощью округления банкира (§8.3.8). Любой масштаб, видимый в литерале, сохраняется, если значение округляется. Примечание. Следовательно, литерал 2.900m будет проанализирован для формирования decimal знака 0, коэффициента 2900и масштабирования 3. конечная заметка

Если величина указанного литерала слишком велика, чтобы быть представлена в указанном типе, возникает ошибка во время компиляции.

Примечание. В частности, Real_Literal никогда не будет производить бесконечность с плавающей запятой. Однако ненулевое Real_Literal может быть округлено до нуля. конечная заметка

Значение реального литерала типа float или double определяется с помощью режима IEC 60559 "округление до ближайшего" с связи с "даже" (значение с наименьшим нулем), а все цифры считаются значительными.

Примечание. В реальном литерале десятичные цифры всегда требуются после десятичной запятой. Например, это реальный литерал, 1.3F но 1.F не является. конечная заметка

Пример:

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

пример конца

6.4.5.5 Символьные литералы

Символьный литерал представляет один символ и состоит из символов в кавычках, как и в '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?
    ;

Примечание. Символ, который следует за символом обратной косой черты (\) в символе, должен быть одним из следующих символов: , \0utUx"bvafnr' В противном случае возникает ошибка во время компиляции. конечная заметка

Примечание. Использование \x рабочей среды Hexadecimal_Escape_Sequence может быть подвержено ошибкам и трудно читать из-за переменной числа шестнадцатеричных цифр после \x. Например, в коде:

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

Сначала может появиться один и тот же ведущий символ (U+0009символ табуляции) в обеих строках. На самом деле вторая строка начинается с U+9BAD того, как все три буквы в слове "Bad" являются допустимыми шестнадцатеричными цифрами. В качестве стиля рекомендуется \x избегать использования определенных escape-последовательностей (\t в этом примере) или escape-последовательности фиксированной длины \u .

конечная заметка

Шестнадцатеричная escape-последовательность представляет собой одну единицу кода Юникода UTF-16, с значением, сформированным шестнадцатеричным числом ниже "\x".

Если значение, представленное символьным литералом, больше U+FFFF, возникает ошибка во время компиляции.

Escape-последовательность Юникода (§6.4.2) в символьном литерале должна находиться в диапазоне U+0000 U+FFFF.

Простая escape-последовательность представляет символ Юникода, как описано в таблице ниже.

escape-последовательность Имя символа Кодовая точка Юникода
\' Одинарная кавычка U+0027
\" Двойная кавычка U+0022
\\ Обратная косая черта U+005C
\0 Null U+0000
\a Предупреждение U+0007
\b Backspace U+0008
\f Подача страницы U+000C
\n Новая строка U+000A
\r Возврат каретки U+000D
\t Горизонтальная табуляция U+0009
\v Вертикальная табуляция U+000B

Тип Character_Literal .char

6.4.5.6 Строковые литералы

C# поддерживает две формы строковых литералов: обычные строковые литералы и подробные строковые литералы. Обычный строковый литерал состоит из нуля или нескольких символов, заключенных в двойные кавычки, как и в "hello", и может включать как простые escape-последовательности (например \t , для символа табуляции), так и шестнадцатеричные и escape-последовательности Юникода.

Подробный строковый литерал состоит из символа @ , за которым следует символ двойной кавычки, ноль или несколько символов, а также закрывающий символ двойного кавычки.

Пример: простой пример.@"hello" пример конца

В подробном строковом литерале символы между разделителями интерпретируются подробно, при этом единственным исключением является Quote_Escape_Sequence, который представляет один символ двойной кавычки. В частности, простые escape-последовательности и шестнадцатеричные и escape-последовательности Юникода не обрабатываются в подробных строковых литералах. Подробный строковый литерал может охватывать несколько строк.

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

Пример: пример

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

отображает различные строковые литералы. Последний строковый литерал — jэто строковый литерал, охватывающий несколько строк. Символы между кавычками, включая пробелы, такие как новые символы строки, сохраняются подробно, и каждая пара символов двойного кавычка заменяется одним таким символом.

пример конца

Примечание. Любые разрывы строк внутри строковых литералов являются частью результирующей строки. Если точные символы, используемые для формирования разрывов строк, семантически относятся к приложению, все средства, которые преобразовывают разрывы строк в исходный код в разные форматы (например, "\n" и "\r\n, например) изменят поведение приложения. Разработчики должны быть осторожны в таких ситуациях. конечная заметка

Примечание. Так как шестнадцатеричная escape-последовательность может иметь переменное число шестнадцатеричных цифр, строковый литерал "\x123" содержит один символ с шестнадцатеричным значением 123. Чтобы создать строку, содержащую символ с шестнадцатеричным значением 12 , за которым следует символ 3, можно написать "\x00123" или "\x12" + "3" вместо него. конечная заметка

Тип String_Literal .string

Каждый строковый литерал не обязательно приводит к новому экземпляру строки. Если два или более строковых литералов, эквивалентных оператору равенства строк (§12.12.8), отображаются в одной сборке, эти строковые литералы ссылаются на один и тот же экземпляр строки.

Пример. Например, выходные данные, созданные с помощью

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

это True связано с тем, что два литерала ссылаются на один и тот же экземпляр строки.

пример конца

6.4.5.7 Литерал NULL

null_literal
    : NULL
    ;

Примечание. null_literal является правилом синтаксического анализа, так как он не вводит новый тип токена. конечная заметка

Null_literal представляет null значение. Он не имеет типа, но может быть преобразован в любой ссылочный тип или тип значений, допускающий значение NULL, путем преобразования литералов NULL (§10.2.7).

Операторы и пунктуаторы 6.4.6

Существует несколько типов операторов и пунктуаторов. Операторы используются в выражениях для описания операций с участием одного или нескольких операндов.

Пример. Выражение a + b использует + оператор для добавления двух операндов a и b. пример конца

Знаки препинания предназначены для группировки и разделения.

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

right_shift
    : '>'  '>'
    ;

right_shift_assignment
    : '>' '>='
    ;

Примечание. right_shift и right_shift_assignment являются правилами синтаксического анализа, так как они не вводят новый тип маркера, но представляют последовательность двух маркеров. Правило operator_or_punctuator существует только для описательных целей и не используется в других местах грамматики. конечная заметка

right_shift состоит из двух маркеров > и >. Аналогичным образом right_shift_assignment состоит из двух маркеров > и >=. В отличие от других рабочих мест в синтактической грамматике, никакие символы любого вида (даже не пробелы) разрешены между двумя маркерами в каждой из этих рабочих мест. Эти производства обрабатываются специально, чтобы обеспечить правильную обработку type_parameter_lists (§15.2.3).

Примечание. До добавления универсальных шаблонов в C#и >> >>= оба маркера были одними маркерами. Однако синтаксис для универсальных элементов использует < и > символы для разделителя параметров типа и аргументов типа. Часто рекомендуется использовать вложенные созданные типы, например List<Dictionary<string, int>>. Вместо того, чтобы программист разделял > пространство и > по пространству, было изменено определение двух operator_or_punctuator. конечная заметка

Директивы предварительной обработки 6.5

6.5.1 Общие

Директивы предварительной обработки обеспечивают возможность условного пропуска разделов единиц компиляции, сообщать об ошибках и предупреждениях, очертать отдельные области исходного кода и задавать контекст, допускающий значение NULL.

Примечание. Термин "директивы предварительной обработки" используется только для согласованности с языками программирования C и C++. В C#отсутствует отдельный этап предварительной обработки; Директивы предварительной обработки обрабатываются в рамках этапа лексического анализа. конечная заметка

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
    ;

Примечание.

  • Грамматика предварительного процессора определяет один лексический токен PP_Directive , используемый для всех директив предварительной обработки. Семантика каждой из директив предварительной обработки определяется в этой спецификации языка, но не как их реализовать.
  • Фрагмент PP_Start должен быть признан только в начале строки, getCharPositionInLine() == 0 ANTLR лексический предикат выше предлагает один из способов достижения этого и является информативным только, реализация может использовать другую стратегию.

конечная заметка

Доступны следующие директивы предварительной обработки:

  • #define и #undef, которые используются для определения и отмены определения и отмены условной компиляции символов (§6.5.4).
  • #if, #elif, #elseи #endif, которые используются для пропуска условных разделов исходного кода (§6.5.5).
  • #line, который используется для управления номерами строк, создаваемыми для ошибок и предупреждений (§6.5.8).
  • #error, который используется для выдачи ошибок (§6.5.6).
  • #region и #endregion, которые используются для явной маркировки разделов исходного кода (§6.5.7).
  • #nullable, который используется для указания контекста, допускающего значение NULL (§6.5.9).
  • #pragma, который используется для указания необязательных контекстных сведений компилятору (§6.5.10).

Директива предварительной обработки всегда занимает отдельную строку исходного кода и всегда начинается с # символа и имени директивы предварительной обработки. Пробел может возникать до символа # и между символом # и именем директивы.

Исходная строка, содержащая #define, #undef, #endif#if#else#line#elif#endregionили #nullable директиву, может заканчиваться одним строковый комментарий. Разделенные комментарии ( /* */ стиль комментариев) запрещены в исходных строках, содержащих директивы предварительной обработки.

Директивы предварительной обработки не являются частью синтаксической грамматики C#. Однако директивы предварительной обработки могут использоваться для включения или исключения последовательностей токенов и могут повлиять на смысл программы C#.

Пример. При компиляции программа

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

приводит к точной последовательности маркеров, что и программа

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

Таким образом, в то время как лексически две программы совершенно разные, синтаксически, они идентичны.

пример конца

Символы условной компиляции 6.5.2

Функции условной компиляции, предоставляемые #if,#else#elifи директивами, управляются с помощью выражений предварительной обработки (§6.5.3) и #endif условных символов компиляции.

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

Обратите внимание , что реализация применяет ограничение допустимых значений Basic_Identifier является проблемой реализации. конечная заметка

Два символа условной компиляции считаются одинаковыми, если они идентичны после применения следующих преобразований.

  • Каждый Unicode_Escape_Sequence преобразуется в соответствующий символ Юникода.
  • Все Formatting_Characters удаляются.

Символ условной компиляции имеет два возможных состояния: определенные или неопределенные. В начале лексической обработки единицы компиляции условный символ компиляции не определен, если он не определен явным образом внешним механизмом (например, параметром компилятора командной строки). При обработке директивы символ условной #define компиляции, названный в этой директиве, определяется в этом модуле компиляции. Символ остается определенным до тех пор, пока #undef не будет обработана директива для того же символа или до конца единицы компиляции. Это связано с тем, что #define и #undef директивы в одной единице компиляции не влияют на другие единицы компиляции в той же программе.

При указании в выражении предварительной обработки (§6.5.3) определенный символ условной компиляции имеет логическое значение true, а неопределенный символ условной компиляции имеет логическое значение false. Нет необходимости явно объявлять символы условной компиляции, прежде чем они ссылаются в выражениях предварительной обработки. Вместо этого необъявленные символы просто не определены и поэтому имеют значение false.

Пространство имен для символов условной компиляции отличается и отделяется от всех других именованных сущностей в программе C#. Символы условной компиляции можно ссылаться только на #define директивы и директивы и #undef в выражениях предварительной обработки.

Выражения предварительной обработки 6.5.3

Выражения предварительной обработки могут выполняться в #if и #elif директивах. Операторы (только префикс ! логического отрицания), !===, &&и разрешены в выражениях предварительной обработки, а || скобки могут использоваться для группировки.

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

При указании в выражении предварительной обработки определенный символ условной компиляции имеет логическое значение true, а неопределенный символ условной компиляции имеет логическое значение false.

Оценка выражения предварительной обработки всегда дает логическое значение. Правила оценки для предварительного выражения совпадают с правилами для константного выражения (§12.23), за исключением того, что единственными пользовательскими сущностями, на которые можно ссылаться, являются символы условной компиляции.

Директивы определения 6.5.4

Директивы определения используются для определения или отмены условной компиляции символов.

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

Обработка директивы приводит к определению заданного символа условной #define компиляции, начиная с исходной строки, следующей за директивой. Аналогичным образом обработка директивы приводит к тому, что заданный символ условной #undef компиляции становится неопределенным, начиная с исходной строки, которая следует директиве.

Все #define и #undef директивы в единице компиляции должны возникать до первого маркера (§6.4) в единице компиляции; в противном случае возникает ошибка во время компиляции. Интуитивно понятные термины #define и #undef директивы должны предшествовать любому "реальному коду" в модуле компиляции.

Пример: пример:

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

является допустимым, так как #define директивы предшествуют первому маркеру ( namespace ключевому слову) в единице компиляции.

пример конца

Пример. Следующий пример приводит к ошибке во время компиляции, так как #define следует реальному коду:

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

пример конца

Может #define определить символ условной компиляции, который уже определен, без какого-либо взаимодействия для этого символа #undef .

Пример. В приведенном ниже примере определяется символ условной компиляции A, а затем он снова определяется.

#define A
#define A

Для компиляторов, которые позволяют определять условные символы компиляции в качестве параметров компиляции, альтернативным способом для такого переопределения является определение символа в качестве параметра компилятора, а также в источнике.

пример конца

Может #undef "отменить" условный символ компиляции, который не определен.

Пример. В приведенном ниже примере определяется символ A условной компиляции, а затем не определен его дважды; хотя второй #undef не действует, он по-прежнему действителен.

#define A
#undef A
#undef A

пример конца

Директивы условной компиляции 6.5.5

Директивы условной компиляции используются для условного включения или исключения частей единицы компиляции.

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

Директивы условной компиляции должны записываться в группы, состоящие #if из директив, нулевых или более #elif директив, ноль или одна #else директива, а также директива #endif . Между директивами являются условные разделы исходного кода. Каждый раздел управляется непосредственно предыдущей директивой. Условный раздел может содержать вложенные директивы условной компиляции, предоставленные этими директивами, полными группами.

Для обычной лексической обработки выбирается не более одного из содержащихся условных разделов:

  • PP_Expression #if и #elif директивы оцениваются в порядке до тех пор, пока не будет получена trueодна из них. Если выражение дает true, выбран условный раздел, следующий за соответствующей директивой.
  • Если все PP_Expressionдоходность falseи #else если директива присутствует, выбран условный раздел после #else директивы.
  • В противном случае не выбран условный раздел.

Выбранный условный раздел, если таковой имеется, обрабатывается как обычный input_section: исходный код, содержащийся в разделе, должен соответствовать лексической грамматике; маркеры создаются из исходного кода в разделе, а директивы предварительной обработки в разделе имеют предписанные эффекты.

Все остальные условные разделы пропускаются, а маркеры, кроме директив предварительной обработки, создаются из исходного кода. Поэтому пропущенный исходный код, кроме директив предварительной обработки, может быть лексически неверным. Пропущенные директивы предварительной обработки должны быть лексически правильными, но не обрабатываются в противном случае. В рамках условного раздела, пропускающего все вложенные условные разделы (содержащиеся в вложенных #if...#endif конструкциях), также пропускаются.

Примечание. Указанная выше грамматика не фиксирует пособие, которое условные разделы между директивами предварительной обработки могут быть неправильно сформированы лексически. Поэтому грамматика не готова к ANTLR, так как она поддерживает только лексически правильные входные данные. конечная заметка

Пример. В следующем примере показано, как директивы условной компиляции могут вложены:

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

За исключением директив предварительной обработки, пропущенный исходный код не подлежит лексическому анализу. Например, это допустимо, несмотря на нетерминированный комментарий в #else разделе:

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

Обратите внимание, что директивы предварительной обработки должны быть лексически правильными даже в пропущенных разделах исходного кода.

Директивы предварительной обработки не обрабатываются при отображении внутри многострочного входного элемента. Например, программа:

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

результаты выходных данных:

hello,
#if Debug
        world
#else
        Nebraska
#endif

В особых случаях набор директив предварительной обработки может зависеть от оценки pp_expression. Пример.

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

всегда создает один и тот же поток маркеров (class }Q {), независимо от того, определен ли он.X Если X задано, единственными обработанными директивами являются #if и #endif, из-за нескольких строковый комментарий. Если X значение не определено, три директивы (#if, #else, #endif) являются частью набора директив.

пример конца

Директивы диагностики 6.5.6

Директивы диагностики используются для явного создания сообщений об ошибках и предупреждениях, сообщаемых таким же образом, как и другие ошибки во время компиляции и предупреждения.

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

fragment PP_Message
    : PP_Whitespace Input_Character*
    ;

Пример: пример

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

создает ошибку во время компиляции ("Сборка не может быть как отладкой, так и розничной торговлей"), если символы условной компиляции Debug и Retail оба определены. Обратите внимание, что PP_Message может содержать произвольный текст, в частности, он не должен содержать хорошо сформированные маркеры, как показано в одной кавычки в слове can't.

пример конца

Директивы региона 6.5.7

Директивы региона используются для явной маркировки регионов исходного кода.

fragment PP_Region
    : PP_Start_Region
    | PP_End_Region
    ;

fragment PP_Start_Region
    : 'region' PP_Message?
    ;

fragment PP_End_Region
    : 'endregion' PP_Message?
    ;

Семантическое значение не присоединено к региону; регионы предназначены для использования программистом или автоматическими средствами для маркировки раздела исходного кода. Для каждой #region директивы должно соответствовать одна #endregion директива. Сообщение, указанное #region в директиве #endregion , также не имеет семантического значения; оно просто служит для идентификации региона. #region Сопоставление и #endregion директивы могут иметь разные PP_Message.

Лексическая обработка региона:

#region
...
#endregion

соответствует лексической обработке директивы условной компиляции формы:

#if true
...
#endif

Примечание. Это означает, что регион может включать один или несколько #if/.../#endif или содержаться с условным разделом в #if/.../#endif; но регион не может перекрываться только частью #if/.../#endif, или начинаться и заканчиваться в разных условных разделах. конечная заметка

Директивы 6.5.8 Line

Директивы line могут использоваться для изменения номеров строк и имен единиц компиляции, сообщаемых компилятором в выходных данных, таких как предупреждения и ошибки. Эти значения также используются атрибутами caller-info (§22.5.6).

Примечание. Директивы линии чаще всего используются в средствах метапрограммирования, которые создают исходный код C# из некоторых других текстовых входных данных. конечная заметка

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

Если директивы отсутствуют #line , компилятор сообщает истинные номера строк и имена единиц компиляции в выходных данных. При обработке #line директивы, включающей не PP_Line_Indicatordefault, компилятор обрабатывает строку после директивы как указанную строку (и имя единицы компиляции, если указано).

Максимально допустимое Decimal_Digit+ значение определяется реализацией.

Директива #line default отменяет действие всех предыдущих #line директив. Компилятор сообщает истинные сведения о строке для последующих строк, точно так же, как если бы директивы не #line были обработаны.

Директива #line hidden не влияет на единицу компиляции и номера строк, сообщаемые в сообщениях об ошибках, или создаются с помощью CallerLineNumberAttribute (§22.5.6.2). Оно предназначено для влияния на средства отладки на уровне источника, чтобы при отладке все строки между #line hidden директивой и последующей #line директивой (не #line hiddenимеют) никаких сведений о номере строки и пропускались полностью при пошаговом выполнении кода.

Примечание. Хотя PP_Compilation_Unit_Name может содержать текст, который выглядит как escape-последовательность, такой текст не является escape-последовательностью. В этом контексте символ "\" просто обозначает обычный символ обратной косой черты. конечная заметка

Директива 6.5.9 Nullable

Директива NULL управляет контекстом, допускаемым значением NULL, как описано ниже.

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

Директива, допускаемая значение NULL, задает доступные флаги для последующих строк кода, пока другая директива, допускаемая значение NULL, не переопределяет ее или до конца _unit компиляции . Контекст, допускающий значение NULL, содержит два флага: заметки и предупреждения. Результатом каждой формы директивы NULL является следующее:

  • #nullable disable: отключает как заметки, допускающие значение NULL, так и флаги, допускающие значение NULL.
  • #nullable enable: включает как заметки, допускающие значение NULL, так и флаги, допускающие значение NULL.
  • #nullable restore: восстанавливает флаги и заметки, и предупреждения в состояние, указанное внешним механизмом, если таковые есть.
  • #nullable disable annotations: отключает флаг заметок, допускающих значение NULL. Флаг предупреждений, допускающих значение NULL, не влияет.
  • #nullable enable annotations: включает флаг заметок, допускающих значение NULL. Флаг предупреждений, допускающих значение NULL, не влияет.
  • #nullable restore annotations: восстанавливает флаг заметок, допускающих значение NULL, в состояние, указанное внешним механизмом, если таковой имеется. Флаг предупреждений, допускающих значение NULL, не влияет.
  • #nullable disable warnings: отключает флаг предупреждений, допускающих значение NULL. Флаг заметок, допускающих значение NULL, не влияет.
  • #nullable enable warnings: включает флаг предупреждений, допускающих значение NULL. Флаг заметок, допускающих значение NULL, не влияет.
  • #nullable restore warnings: восстанавливает флаг предупреждений, допускающих значение NULL, в состояние, указанное внешним механизмом, если таковой имеется. Флаг заметок, допускающих значение NULL, не влияет.

Состояние выражений, допускающих значение NULL, отслеживается всегда. Состояние флага заметки и наличия или отсутствия заметки, допускающей значение NULL, ?определяет начальное состояние null объявления переменной. Предупреждения выдаются только в том случае, если флаг предупреждений включен.

Пример: пример

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

создает предупреждение во время компиляции ("как x есть null"). Состояние, допускаемое x значение NULL, отслеживается везде. Предупреждение выдается при включении флага предупреждений.

пример конца

Директивы Pragma 6.5.10

Директива #pragma предварительной обработки используется для указания контекстных сведений компилятору.

Примечание. Например, компилятор может предоставить #pragma директивы, которые

  • Включение или отключение определенных предупреждений при компиляции последующего кода.
  • Укажите, какие оптимизации следует применить к последующему коду.
  • Укажите сведения, используемые отладчиком.

конечная заметка

fragment PP_Pragma
    : 'pragma' PP_Pragma_Text?
    ;

fragment PP_Pragma_Text
    : PP_Whitespace Input_Character*
    ;

Input_Character в PP_Pragma_Text интерпретируются компилятором путем определения реализации. Сведения, указанные в директиве #pragma , не изменяют семантику программы. Директива #pragma должна изменять только поведение компилятора, которое находится вне области данной спецификации языка. Если компилятор не может интерпретировать Input_Characters, компилятор может создать предупреждение, однако он не должен создавать ошибку во время компиляции.

Примечание. PP_Pragma_Text может содержать произвольный текст, в частности, он не должен содержать хорошо сформированные маркеры. конечная заметка