6 어휘 구조
6.1 프로그램
C# 프로그램은 컴파일 단위(§14.2)로 공식적으로 알려진 하나 이상의 원본 파일로 구성됩니다. 컴파일 단위에 파일 시스템의 파일과 일대일 대응이 있을 수 있지만 이러한 대응은 필요하지 않습니다.
개념적으로 말하면 프로그램은 다음 세 단계를 사용하여 컴파일됩니다.
- 특정 문자 레퍼토리 및 인코딩 구성표에서 유니코드 문자 시퀀스로 파일을 변환하는 변환입니다.
- 유니코드 입력 문자 스트림을 토큰 스트림으로 변환하는 어휘 분석입니다.
- 토큰 스트림을 실행 코드로 변환하는 구문 분석입니다.
구현 준수는 UTF-8 인코딩 형식으로 인코딩된 유니코드 컴파일 단위를 허용하고(유니코드 표준에 정의된 대로) 유니코드 문자 시퀀스로 변환해야 합니다. 구현은 추가 문자 인코딩 구성표(예: UTF-16, UTF-32 또는 유니코드가 아닌 문자 매핑)를 수락하고 변환하도록 선택할 수 있습니다.
참고: 유니코드 NULL 문자(U+0000)의 처리는 구현에서 정의됩니다. 개발자는 이식성과 가독성을 위해 소스 코드에서 이 문자를 사용하지 않는 것이 좋습니다. 문자 또는 문자열 리터럴 내에서 문자가 필요한 경우 이스케이프 시퀀스를
\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 문법 표기법
어휘 및 구문 문법은 ANTLR 문법 도구의 확장 Backus-Naur 형식으로 표시됩니다.
ANTLR 표기법을 사용하는 동안 이 사양은 C#에 대한 완전한 ANTLR 준비 "참조 문법"을 제공하지 않습니다. 직접 또는 ANTLR과 같은 도구를 사용하여 렉서 및 파서를 작성하는 것은 언어 사양의 범위를 벗어난다. 해당 정규화에서 이 사양은 지정된 문법과 ANTLR에서 렉서와 파서를 작성하는 데 필요한 간격을 최소화하려고 시도합니다.
ANTLR은 어휘와 구문, ANTLR로 용어가 지정된 구문, 대문자로 어휘 규칙을 시작하여 표기법의 문법, 소문자로 된 파서 규칙을 구분합니다.
참고: C# 어휘 문법(§6.2.3) 및 구문 문법(§6.2.4)은 어휘 및 파서 그래머로 ANTLR 나누기와 정확히 일치하지 않습니다. 이 작은 불일치는 C# 어휘 문법을 지정할 때 일부 ANTLR 파서 규칙이 사용됨을 의미합니다. 끝 메모
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));
는 두 개의 인수
G < A
를 사용하는 호출F
로 해석될 수 있습니다B > (7)
. 또는 두 개의 형식 인수와 하나의 일반 인수가 있는 제네릭 메서드G
에 대한 호출인 하나의 인수를 사용하여 호출F
로 해석할 수 있습니다.끝 예제
토큰 시퀀스를 simple_name(§12.8.4), member_access(§12.8.7) 또는 type_argument_list(§8.4.2)로 끝나는 pointer_member_access(§23.6.3)로 구문 분석할 수 있는 경우 닫는 >
토큰 바로 다음에 있는 토큰이 검사되어 있는지 확인합니다.
- ;
( ) ] } : ; , . ? == != | ^ && || & [
또는 - 관계형 연산
< <= >= is as
자 중 하나 - 쿼리 식 내에 나타나는 컨텍스트 쿼리 키워드입니다. 또는
- 특정 컨텍스트 에서 식별자는 명확한 토큰으로 처리됩니다. 이러한 컨텍스트는 명확하게 구분되는 토큰의 시퀀스가 키워드
is
중 하나 바로 앞에 오거나out
,case
튜플 리터럴의 첫 번째 요소를 구문 분석하는 동안 발생하는 위치입니다(이 경우 토큰이 앞에(
오거나:
식별자가 뒤에,
오는 경우) 또는 튜플 리터럴의 후속 요소입니다.
다음 토큰이 이 목록 또는 이러한 컨텍스트의 식별자 중 하나이면 type_argument_list simple_name, member_access 또는 pointer_member 액세스의 일부로 유지되고 토큰 시퀀스의 다른 가능한 구문 분석이 삭제됩니다. 그렇지 않으면 type_argument_list 토큰 시퀀스의 다른 가능한 구문 분석이 없더라도 simple_name member_access 또는 pointer_member_access 일부로 간주되지 않습니다.
참고: 이러한 규칙은 namespace_or_type_name type_argument_list 구문 분석할 때 적용되지 않습니다(§7.8). 끝 메모
예: 문:
F(G<A, B>(7));
는 이 규칙에 따라 두 개의 형식 인수와 하나의 일반 인수가 있는 제네릭 메서드
G
에 대한 호출인 하나의 인수를 사용하는 호출F
로 해석됩니다. 문F(G<A, B>7); F(G<A, B>>7);
는 각각 두 개의 인수가 있는 호출
F
로 해석됩니다. 문x = F<A> + y;
은 문 다음에 이진 더하기 연산자가 오는 type_argument_list 있는 simple_name 대신 문이 작성된
x = (F < A) > (+y)
것처럼 보다 작은 연산자, 보다 큰 연산자 및 단항 더하기 연산자로 해석됩니다. 문에서x = y is C<T> && z;
토큰
C<T>
은 type_argument_list 후 명확한 토큰&&
이 있기 때문에 type_argument_list 있는 namespace_or_type_name 해석됩니다.식
(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 형식" 및 "relational_expression constant_patternis
" 대안이 모두 적용되고 형식이 액세스 가능한 형식으로 확인되면 relational_expressionis
(§12.12.1)를 인식할 때 "relational_expression is
형식" 대안을 선택해야 합니다.
6.3 어휘 분석
6.3.1 일반
편의를 위해 어휘 문법은 다음과 같은 명명된 렉서 토큰을 정의하고 참조합니다.
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# 컴파일 단위의 어휘 구조를 정의합니다. 끝 메모
5가지 기본 요소는 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).
참고: 추가 캐리지 리턴을 사용하면 종료 New_Line 없는 PP_Directive(§6.5)로 프로그램을 종료할 수 있습니다. 끝 메모
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
;
주석은 중첩되지 않습니다. /*
및 */
문자 시퀀스는 한 줄 주석 내에서 특별한 의미가 없으며, //
및 /*
문자 시퀀스는 끝을 지정하는 주석 내에서 특별한 의미가 없습니다.
주석은 문자 및 문자열 리터럴 내에서 처리되지 않습니다.
참고: 이러한 규칙은 신중하게 해석되어야 합니다. 예를 들어 아래 예제에서 사이에 종료
B
C()
되기 전에A
시작되는 구분된 주석입니다. 그 이유는// B */ C();
는 구분된 주석 내에 특별한 의미가 없기 때문에
*/
실제로 단일 줄 주석//
아니며, 해당 줄에 일반적인 특별한 의미가 있습니다.마찬가지로 구분된 주석은 앞에 끝나기 전에
D
E
시작됩니다. 그 이유는 처음 큰따옴표 문자가"D */ "
구분된 주석 안에 나타나기 때문에 실제로 문자열 리터럴이 아니기 때문입니다.단일 줄 주석 내에서 특별한 의미가 없는 경우 유용한 결과는
/*
*/
각 줄의 시작 부분에 배치//
하여 소스 코드 줄 블록을 주석 처리할 수 있다는 것입니다. 일반적으로 블록에서 구분된 주석을 제대로 캡슐화하지 않으므로 해당 줄 앞과*/
그 뒤에 배치/*
하는 것은 작동하지 않으며 일반적으로 구분된 주석의 구조를 완전히 변경할 수 있습니다.예시 코드:
static void Main() { /* A // B */ C(); Console.WriteLine(/* "D */ "E"); }
끝 메모
특정 형식을 갖는 Single_Line_Comment 및 Delimited_Comment§D에 설명된 대로 설명서 주석으로 사용할 수 있습니다.
6.3.4 공백
공백은 유니코드 클래스 Zs(공백 문자 포함)와 가로 탭 문자, 세로 탭 문자 및 양식 피드 문자가 있는 모든 문자로 정의됩니다.
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 파서 규칙이며 어휘 토큰을 정의하는 것이 아니라 토큰 종류의 컬렉션입니다. 끝 메모
6.4.2 유니코드 문자 이스케이프 시퀀스
유니코드 이스케이프 시퀀스는 유니코드 코드 포인트를 나타냅니다. 유니코드 이스케이프 시퀀스는 식별자(§6.4.3), 문자 리터럴(§6.4.5.5), 일반 문자열 리터럴(§6.4.5.6) 및 보간된 정규 문자열 식(§12.8.3)에서 처리됩니다. 유니코드 이스케이프 시퀀스는 다른 위치에서 처리되지 않습니다(예: 연산자, 문장 부호 또는 키워드를 형성하기 위해).
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
;
유니코드 문자 이스케이프 시퀀스는 "\u" 또는 "\U" 문자 다음에 16진수로 구성된 단일 유니코드 코드 포인트를 나타냅니다. C#은 문자 및 문자열 값에 유니코드 코드 포인트의 16비트 인코딩을 사용하므로 U+10FFFF
범위 U+10000
의 유니코드 코드 포인트는 두 개의 유니코드 서로게이트 코드 단위를 사용하여 표시됩니다. 위의 U+FFFF
유니코드 코드 포인트는 문자 리터럴에서 허용되지 않습니다. 위의 U+10FFFF
유니코드 코드 포인트가 잘못되었으며 지원되지 않습니다.
여러 번역이 수행되지 않습니다. 예를 들어 문자열 리터럴 "\u005Cu005C"
은 "\u005C"
"\"
.
참고: 유니코드 값
\u005C
은 문자 "\
"입니다. 끝 메모
예: 예제
class Class1 { static void Test(bool \u0066) { char c = '\u0066'; if (\u0066) { System.Console.WriteLine(c.ToString()); } } }
는 문자 "
f
"의\u0066
이스케이프 시퀀스인 몇 가지 용도를 보여 집니다. 프로그램은class Class1 { static void Test(bool f) { char c = 'f'; if (f) { System.Console.WriteLine(c.ToString()); } } }
끝 예제
6.4.3 식별자
이 하위 언어에 지정된 식별자에 대한 규칙은 밑줄이 초기 문자로 허용되고(C 프로그래밍 언어의 기존 문자처럼), 유니코드 이스케이프 시퀀스가 식별자에 허용되고, "@
" 문자가 키워드를 식별자로 사용할 수 있도록 하는 접두사로 허용된다는 점을 제외하고 유니코드 표준 부록 15에서 권장하는 것과 정확히 일치합니다.
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
. 끝 예제
준수 프로그램의 식별자는 유니코드 표준 부록 15에 정의된 유니코드 정규화 양식 C에 정의된 정식 형식이어야 합니다. 정규화 양식 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
"라는 클래스를 정의합니다. 유니코드 이스케이프는 키워드에서 허용되지 않으므로 토큰 "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
식별자는 허용되지 않으므로 이 사용은 이러한 단어를 식별자로 사용하는 것과 충돌하지 않습니다.
경우에 따라 문법이 컨텍스트 키워드 사용을 식별자와 구분하기에 충분하지 않습니다. 이러한 모든 경우에 둘 사이에 명확하게 구분하는 방법을 지정합니다. 예를 들어 암시적으로 형식화된 지역 변수 선언(§13.6.2)의 컨텍스트 키워드 var
는 선언var
된 형식과 충돌할 수 있습니다. 이 경우 선언된 이름이 식별자를 컨텍스트 키워드로 사용하는 데 우선합니다.
이러한 명확성의 또 다른 예는 컨텍스트 키워드 await
(§12.9.8.1)입니다. 이 키워드는 메서드 내부에서 선언된 async
경우에만 키워드로 간주되지만 다른 곳에서는 식별자로 사용할 수 있습니다.
키워드와 마찬가지로 컨텍스트 키워드는 문자 앞에 접두사를 지정하여 @
일반 식별자로 사용할 수 있습니다.
참고: 컨텍스트 키워드로 사용되는 경우 이러한 식별자에는 Unicode_Escape_Sequence포함할 수 없습니다. 끝 메모
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 정수 리터럴
정수 리터럴은 형식, uint
long
및 ulong
.int
정수 리터럴에는 10진수, 16진수 및 이진의 세 가지 가능한 형식이 있습니다.
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
수 있는 첫 번째 형식이 있습니다. - 리터럴의 접미사가
UL
, ,Ul
,uL
,LU
ul
,lU
Lu
lu
또는 형식ulong
인 경우 .
정수 리터럴이 나타내는 값이 형식 범위를 ulong
벗어나면 컴파일 시간 오류가 발생합니다.
참고: 스타일에 따라 문자 ""를 숫자 "
l
L
"와1
쉽게 혼동할 수 있으므로 형식long
의 리터럴을 작성할 때 "" 대신 "l
"를 사용하는 것이 좋습니다. 끝 메모
가능한 int
가장 작은 값과 long
값을 정수 리터럴로 작성할 수 있도록 하려면 다음 두 규칙이 있습니다.
- 단항 빼기 연산자 토큰(§12.9.3) 바로 다음에 값
2147483648
(2개 2개)을 나타내는 Integer_Literal 및 Integer_Type_Suffix 없는 토큰으로 나타나면 결과(두 토큰의 경우)는 값−2147483648
(-2개 2개)을 가진 int 형식의 상수입니다. 다른 모든 상황에서는 이러한 Integer_Literal 형식uint
입니다. - 값
9223372036854775808
(2개)을 나타내는 Integer_Literal Integer_Type_Suffix 또는 Integer_Type_Suffixl
L
없거나 단항 빼기 연산자 토큰(§12.9.3) 바로 다음에 토큰으로 표시되는 경우 결과(두 토큰의 결과)는 값−9223372036854775808
(-2 1)을 가진 형식long
의 상수입니다. 다른 모든 상황에서는 이러한 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
,1e10f
및123.456F
모든 형식float
입니다. 끝 예제 - 접미사가
D
있는 실제 리터럴이거나d
형식double
입니다.예: 리터럴
1d
,1.5d
,1e10d
및123.456D
모든 형식double
입니다. 끝 예제 - 접미사가
M
있는 실제 리터럴이거나m
형식decimal
입니다.예: 리터럴
1m
,1.5m
,1e10m
및123.456M
모든 형식decimal
입니다. 끝 예제
이 리터럴은 정확한 값을 가져와서 값으로 변환decimal
되며, 필요한 경우 은행의 반올림(§8.3.8)을 사용하여 가장 가까운 표현 가능한 값으로 반올림합니다. 값이 반올림되지 않는 한 리터럴의 모든 눈금이 유지됩니다. 참고: 따라서 리터럴2.900m
은 구문 분석되어 부호0
, 계2900
수 및 배율을3
형성decimal
합니다. 끝 메모
지정된 리터럴의 크기가 너무 커서 표시된 형식으로 표현되지 않으면 컴파일 시간 오류가 발생합니다.
참고: 특히 Real_Literal 부동 소수점 무한대를 생성하지 않습니다. 그러나 0 이 아닌 Real_Literal 0으로 반올림될 수 있습니다. 끝 메모
형식 float
의 실제 리터럴 값이거나 double
IEC 60559 "가장 가까운 둥글게" 모드를 사용하여 "짝수"(최하위 비트가 0인 값)로 나뉘고 모든 숫자가 중요한 것으로 간주됩니다.
참고: 실제 리터럴에서는 소수점 이후에 항상 소수 자릿수가 필요합니다. 예를 들어 실제
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?
;
참고: 문자에서 백슬래시 문자(
\
)를 따르는 문자는 다음x
n
f
r
b
t
a
v
"
U
\
u
0
문자'
중 하나여야 합니다. 그렇지 않으면 컴파일 타임 오류가 발생합니다. 끝 메모
참고: Hexadecimal_Escape_Sequence 프로덕션의
\x
사용은 다음의 가변적인 16진수 숫자로 인해 오류가 발생하기 쉽고 읽기 어려울 수 있습니다\x
. 예를 들어 코드에서 다음을 수행합니다.string good = "\x9Good text"; string bad = "\x9Bad text";
두 문자열에서 선행 문자가 같고(
U+0009
탭 문자) 처음에 나타날 수 있습니다. 실제로 두 번째 문자열은U+9BAD
"Bad"라는 단어의 세 글자가 모두 유효한 16진수 숫자이기 때문에 시작됩니다. 스타일에 따라 특정 이스케이프 시퀀스(\t
이 예제의 경우) 또는 고정 길이\u
이스케이프 시퀀스를 선호하지 않는 것이 좋습니다\x
.끝 메모
16진수 이스케이프 시퀀스는 단일 유니코드 UTF-16 코드 단위를 나타내며 값은 "\x
"에 따라 16진수로 구성됩니다.
문자 리터럴로 표현되는 값이 보다 U+FFFF
크면 컴파일 시간 오류가 발생합니다.
문자 리터럴의 유니코드 이스케이프 시퀀스(§6.4.2)는 다음 범위 U+0000
U+FFFF
여야 합니다.
간단한 이스케이프 시퀀스는 아래 표에 설명된 대로 유니코드 문자를 나타냅니다.
이스케이프 시퀀스 | 문자 이름 | 유니코드 코드 포인트 |
---|---|---|
\' |
작은따옴표 | U+0027 |
\" |
큰따옴표 | U+0022 |
\\ |
백슬래시 | U+005C |
\0 |
Null | U+0000 |
\a |
경고 | U+0007 |
\b |
백스페이스 | 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#은 일반 문자열 리터럴과 축자 문자열 리터럴이라는 두 가지 형식의 문자열 리터럴을 지원합니다. 일반 문자열 리터럴은 큰따옴표로 묶인 0개 이상의 문자로 "hello"
구성되며, 간단한 이스케이프 시퀀스(예: \t
탭 문자) 및 16진수 및 유니코드 이스케이프 시퀀스를 모두 포함할 수 있습니다.
축자 문자열 리터럴은 @
문자 뒤에 큰따옴표 문자, 0개 이상의 문자 및 닫는 큰따옴표 문자로 구성됩니다.
예: 간단한 예는 다음과 같습니다
@"hello"
. 끝 예제
축자 문자열 리터럴에서 구분 기호 사이의 문자는 축자로 해석되며, 유일한 예외는 큰따옴표 문자 하나를 나타내는 Quote_Escape_Sequence. 특히 간단한 이스케이프 시퀀스와 16진수 및 유니코드 이스케이프 시퀀스는 축자 문자열 리터럴에서 처리되지 않습니다. 축자 문자열 리터럴은 여러 줄에 걸쳐 있을 수 있습니다.
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
" 사이)으로 변환하는 도구는 애플리케이션 동작을 변경합니다. 개발자는 이러한 상황에서 주의해야 합니다. 끝 메모
참고: 16진수 이스케이프 시퀀스에는 가변 수의 16진수가 있을 수 있으므로 문자열 리터럴
"\x123"
에는 16진수 값123
이 있는 단일 문자가 포함됩니다. 문자 뒤에 16진수 값12
이 있는 문자가 포함된 문자열을3
만들려면 대신 쓰거나"\x12"
+"3"
쓸"\x00123"
수 있습니다. 끝 메모
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 리터럴 변환(§10.2.7)을 통해 참조 형식 또는 null 허용 값 형식으로 변환할 수 있습니다.
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 일반
사전 처리 지시문은 컴파일 단위의 섹션을 조건부로 건너뛰고, 오류 및 경고 조건을 보고하고, 소스 코드의 고유한 영역을 구분하고, nullable 컨텍스트를 설정하는 기능을 제공합니다.
참고: "사전 처리 지시문"이라는 용어는 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
#else
소스 코드의 조건부 섹션(§6.5.5)을 건너뛰는 데 사용되는 ,#elif
및#endif
#line
오류 및 경고(§6.5.8)에 대해 내보낸 줄 번호를 제어하는 데 사용됩니다.#error
오류(§6.5.6)를 발급하는 데 사용됩니다.#region
및#endregion
소스 코드의 섹션을 명시적으로 표시하는 데 사용됩니다(§6.5.7).#nullable
nullable 컨텍스트(§6.5.9)를 지정하는 데 사용됩니다.#pragma
- 컴파일러에 선택적 컨텍스트 정보를 지정하는 데 사용됩니다(§6.5.10).
사전 처리 지시문은 항상 별도의 소스 코드 줄을 차지하고 항상 문자와 사전 처리 지시문 이름으로 시작합니다 #
. 공백은 #
문자 앞과 문자와 지시문 이름 사이에 #
발생할 수 있습니다.
, ,, , #endif
#if
#endregion
#elif
#line
#else
또는 #nullable
지시문을 포함하는 #define
소스 줄은 단일 줄 주석 끝날 수 있습니다. #undef
구분된 주석( /* */
주석 스타일)은 사전 처리 지시문을 포함하는 소스 줄에서 허용되지 않습니다.
전처리 지시문은 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 조건부 컴파일 기호
, #elif
및 #else
#endif
지시문에서 제공하는 #if
조건부 컴파일 기능은 사전 처리 식(§6.5.3) 및 조건부 컴파일 기호를 통해 제어됩니다.
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# 프로그램의 다른 모든 명명된 엔터티와는 별개입니다. 조건부 컴파일 기호는 지시문 및 #undef
사전 처리 식에서만 참조 #define
할 수 있습니다.
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 }
끝 예제
A #define
는 해당 기호에 대한 개입 없이 이미 정의된 조건부 컴파일 기호를 #undef
정의할 수 있습니다.
예제: 아래 예제에서는 조건부 컴파일 기호 A를 정의한 다음 다시 정의합니다.
#define A #define A
조건부 컴파일 기호를 컴파일 옵션으로 정의할 수 있도록 하는 컴파일러의 경우 이러한 재정의를 수행하는 다른 방법은 소스뿐만 아니라 컴파일러 옵션으로 기호를 정의하는 것입니다.
끝 예제
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
지시문, 0개 이상의 #elif
지시문, 0개 또는 #else
1개의 지시문 및 지시문으로 #endif
구성된 그룹으로 작성되어야 합니다. 지시문 사이에는 소스 코드의 조건부 섹션 이 있습니다. 각 섹션은 바로 앞의 지시문에 의해 제어됩니다. 조건부 섹션 자체에는 이러한 지시문이 전체 그룹을 형성하는 경우 중첩된 조건부 컴파일 지시문이 포함될 수 있습니다.
일반적인 어휘 처리를 위해 포함된 조건부 섹션 중 하나만 선택됩니다.
- 및
#elif
지시문의#if
PP_Expression하나가 생성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
#endif
의#if
일부와 겹치거나 다른 조건부 섹션에서 시작할 수 없습니다. 끝 메모
6.5.8 Line 지시문
줄 지시문은 경고 및 오류와 같은 출력에서 컴파일러가 보고하는 줄 번호 및 컴파일 단위 이름을 변경하는 데 사용할 수 있습니다. 이러한 값은 호출자 정보 특성(§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
으면 컴파일러는 출력에 실제 줄 번호와 컴파일 단위 이름을 보고합니다. 없는 PP_Line_Indicator 포함하는 지시문을 처리하는 #line
경우 컴파일러는 지시문 뒤의 줄을 지정된 줄 번호(지정된 경우 컴파일 단위 이름)를 갖는 것으로 처리합니다. default
허용되는 Decimal_Digit+
최대값은 구현 정의입니다.
지시문은 #line default
이전 #line
의 모든 지시문의 효과를 취소합니다. 컴파일러는 지시문이 처리되지 않은 #line
것처럼 정확하게 후속 줄에 대한 실제 줄 정보를 보고합니다.
#line hidden
지시문은 오류 메시지에 보고되거나 (§22.5.6.2)를 사용하여 CallerLineNumberAttribute
생성된 컴파일 단위 및 줄 번호에 영향을 주지 않습니다. 디버깅할 때 지시문과 후속 #line
지시문 사이의 #line hidden
모든 줄에 #line hidden
줄 번호 정보가 없고 코드를 단계별로 실행하면 완전히 건너뛰도록 소스 수준 디버깅 도구에 영향을 줍니다.
참고: PP_Compilation_Unit_Name 이스케이프 시퀀스처럼 보이는 텍스트를 포함할 수 있지만 이러한 텍스트는 이스케이프 시퀀스가 아닙니다. 이 컨텍스트에서 '
\
' 문자는 단순히 일반 백슬래시 문자를 지정합니다. 끝 메모
6.5.9 Nullable 지시문
nullable 지시문은 아래에 설명된 대로 nullable 컨텍스트를 제어합니다.
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'
;
nullable 지시문은 다른 nullable 지시문이 재정의하거나 컴파일 _unit 끝날 때까지 후속 코드 줄에 사용 가능한 플래그를 설정합니다. nullable 컨텍스트에는 주석과 경고의 두 가지 플래그가 포함됩니다. nullable 지시문의 각 형식의 효과는 다음과 같습니다.
#nullable disable
: nullable 주석과 nullable 경고 플래그를 모두 사용하지 않도록 설정합니다.#nullable enable
: nullable 주석과 nullable 경고 플래그를 모두 사용하도록 설정합니다.#nullable restore
: 주석 및 경고 플래그를 모두 외부 메커니즘에 지정된 상태로 복원합니다(있는 경우).#nullable disable annotations
: nullable 주석 플래그를 사용하지 않도록 설정합니다. nullable 경고 플래그는 영향을 받지 않습니다.#nullable enable annotations
: nullable 주석 플래그를 사용하도록 설정합니다. nullable 경고 플래그는 영향을 받지 않습니다.#nullable restore annotations
: nullable 주석 플래그를 외부 메커니즘에 지정된 상태로 복원합니다(있는 경우). nullable 경고 플래그는 영향을 받지 않습니다.#nullable disable warnings
: nullable 경고 플래그를 사용하지 않도록 설정합니다. nullable 주석 플래그는 영향을 받지 않습니다.#nullable enable warnings
: nullable 경고 플래그를 사용하도록 설정합니다. nullable 주석 플래그는 영향을 받지 않습니다.#nullable restore warnings
: nullable 경고 플래그가 있는 경우 외부 메커니즘에서 지정한 상태로 복원합니다. nullable 주석 플래그는 영향을 받지 않습니다.
식의 nullable 상태는 항상 추적됩니다. 주석 플래그의 상태와 null 허용 주석 ?
의 존재 여부 또는 부재는 변수 선언의 초기 null 상태를 결정합니다. 경고 플래그를 사용하는 경우에만 경고가 발생합니다.
예: 예제
#nullable disable string x = null; string y = ""; #nullable enable Console.WriteLine(x.Length); // Warning Console.WriteLine(y.Length);
는 컴파일 시간 경고("있는
null
그대로x
")를 생성합니다. nullable 상태는x
어디에서나 추적됩니다. 경고 플래그를 사용하도록 설정하면 경고가 발생합니다.끝 예제
6.5.10 Pragma 지시문
#pragma
전처리 지시문은 컴파일러에 컨텍스트 정보를 지정하는 데 사용됩니다.
참고: 예를 들어 컴파일러는 다음 지시문을 제공할
#pragma
수 있습니다.
- 후속 코드를 컴파일할 때 특정 경고 메시지를 사용하거나 사용하지 않도록 설정합니다.
- 후속 코드에 적용할 최적화를 지정합니다.
- 디버거에서 사용할 정보를 지정합니다.
끝 메모
fragment PP_Pragma
: 'pragma' PP_Pragma_Text?
;
fragment PP_Pragma_Text
: PP_Whitespace Input_Character*
;
PP_Pragma_Text Input_Character 구현 정의 방식으로 컴파일러에 의해 해석됩니다. 지시문에 #pragma
제공된 정보는 프로그램 의미 체계를 변경하지 않습니다. #pragma
지시문은 이 언어 사양의 범위를 벗어난 컴파일러 동작만 변경해야 합니다. 컴파일러가 Input_Character해석할 수 없는 경우 컴파일러는 경고를 생성할 수 있지만 컴파일 시간 오류를 생성하지 않습니다.
참고: PP_Pragma_Text 임의의 텍스트를 포함할 수 있습니다. 특히 올바른 형식의 토큰을 포함할 필요는 없습니다. 끝 메모
ECMA C# draft specification