1.7. 문자열 리터럴
문자열 리터럴은 문자의 나열을 표현하는 토큰입니다. 본 문서에서는 어휘 형식만을 다루며, 그 결과가 어떤 라이브러리 타입으로 평가되는지는 표준 라이브러리의 리터럴 핸들러가 결정합니다. 바이트 문자열 (1.7.5) 은 예외로, 그 타입은 언어가 정합니다.
1.7.1. 일반 문자열
string_lit ::= '"' string_item* '"'
string_item ::= string_char | escape_seq
string_char ::= <any unicode codepoint except '"' or '\'>
escape_seq ::= "\\" ("\\" | "\"" | "'" | "n" | "r" | "t" | "0")
| "\\x" hex_digit hex_digit
| "\\u" "{" hex_digit ("_"? hex_digit){0,5} "}"
| "\\" line_terminator whitespace*
일반 문자열은 쌍따옴표 (") 로 둘러쌉니다.
"hello"
"hello, 세계"
""
"여러
줄에
걸친 문자열"
내부에는 임의의 유니코드 코드포인트를 포함할 수 있고, 줄바꿈 또한 그대로 포함할 수 있습니다. 다만 끝까지 닫히지 않으면 컴파일 에러가 발생하며, 닫는 " 는 반드시 같은 파일 안에 있어야 하므로 파일 끝까지 닫히지 않은 문자열은 에러로 처리됩니다.
이스케이프 시퀀스
다음과 같은 이스케이프 시퀀스를 인식합니다.
| 시퀀스 | 의미 |
|---|---|
\\ | 백슬래시 (U+005C) |
\" | 쌍따옴표 (U+0022) |
\' | 작은따옴표 (U+0027) |
\n | 줄바꿈 (U+000A) |
\r | 캐리지 리턴 (U+000D) |
\t | 탭 (U+0009) |
\0 | NULL (U+0000) |
\xHH | 1바이트 (16진 두 자리, U+0000-U+007F 만 허용 - ASCII 범위) |
\u{H...} | 임의 유니코드 코드포인트 (16진 1~6 자리, 최대 U+10FFFF) |
\u{...} 안의 16진 자리는 _ 로 구분할 수 있습니다 (예: \u{1_F600}). 위에 나열된 것 외의 백슬래시 시퀀스는 모두 어휘 에러로 처리되며, 알려지지 않은 이스케이프를 암묵적으로 원본 문자 그대로 두는 일은 하지 않습니다.
줄바꿈 잇기
문자열 안의 백슬래시가 줄 종결자 바로 앞에 오면, 그 백슬래시와 줄 종결자, 그리고 다음 줄의 선행 공백이 모두 생략됩니다.
"한 줄로 \
보이는 긴 문자열"
// 결과 내용: "한 줄로 보이는 긴 문자열"
이 규칙은 긴 문자열을 소스 코드 상에서는 여러 줄에 나누어 쓰면서도 결과 값에는 줄바꿈을 넣지 않기 위한 장치입니다.
1.7.2. 원시 문자열
원시 문자열은 r 접두에 쌍따옴표를 붙여 표현합니다.
r"raw\n"
r"C:\path\to\file"
원시 문자열 안에서는 어떤 이스케이프도 처리되지 않으므로, \n 은 두 글자 그대로 보존됩니다. 만약 원시 문자열의 내용에 닫는 " 가 필요한 경우에는, 양쪽에 같은 수의 # 을 둘러서 구분자를 변환할 수 있습니다.
r#"문자열 안에 "쌍따옴표"가 있어도 됨"#
r##"r#"이러한 형태"#도 가능"##
여는 r##" 은 닫는 "## 와 정확히 같은 수의 # 으로 끝나야 하며, 실제로 세는 글자는 여는 쪽의 # 개수입니다. 또한 원시 문자열은 줄바꿈 잇기 (\ + 줄 종결자) 를 지원하지 않습니다. 모든 글자를 그대로 보존하는 것이 원시 문자열의 정의이기 때문입니다.
1.7.3. 형식 문자열
형식 문자열은 f"..." 와 f"""...""" 두 형태입니다 (Python 3.12 / PEP 701 과 같은 규칙).
f"x = {x}"
f"sum = {a + b}, diff = {a - b}"
f"""
name: "{name}"
total: {items
.len()}
"""
- 텍스트의
{{와}}는 중괄호 하나입니다. f"""..."""의 텍스트에는"를 그대로 쓸 수 있고, 구멍의 코드가 여러 줄에 걸칠 수 있습니다.f"..."의 구멍은 한 줄입니다.f없는"""문자열은 없습니다.
구멍
구멍 { .. } 안은 일반 렉서가 읽는 일반 토큰입니다.
- 바깥 따옴표를 다시 쓸 수 있습니다 (
f"{s.split(" ")}",f"{d["k"]}"). f-string 은 몇 겹이든 중첩됩니다. - 구멍 안의 문자열 리터럴은 자기 escape 를 그대로 가집니다 (
f"{"a\tb"}"). - 문자열 리터럴 밖의
\는 에러입니다. 진단은"를 그대로 쓰라고 안내합니다. - 식은 괄호 깊이 0 의
}나:에서 끝납니다.::는 경로 구분자입니다.
f"name = {user.name ?? "anon"}" // OK
f"name = {user.name ?? \"anon\"}" // ERROR - 구멍 안의 \
f"{\"ab\".replace("a", "b")}" // ERROR - f"{"ab".replace("a", "b")}" 로 씀
형식 지정자
표현식 뒤에 콜론 (:) 을 두고 형식 지정자를 적을 수 있습니다.
f"{x:.2f}" // 소수점 둘째 자리까지
f"{n:0>5d}" // 5자리 0 채움 우측 정렬
f"{val:?}" // 디버그 형식
형식 지정자의 문법은 다음과 같습니다.
spec := [fill][align][sign]['#'][0][width]['.'precision][type]
align := '<' | '>' | '^' | '='
sign := '+' | '-' | ' '
type := 'd' | 'g' | 's' | 'f' | 'b' | 'o' | 'x' | 'X' | 'e' | 'E' | '?'
width 가 있고 align 이 없으면, 수 (정수와 부동소수) 는 오른쪽 정렬, 그 밖의 타입은 왼쪽 정렬입니다. < > ^ 를 적으면 (fill 문자와 함께) 어떤 타입이든 그 정렬을 따릅니다. f-string 은 구멍의 타입을 아는 단계에서 형식화 코드로 바뀌므로, 정렬 기본값과 spec 검사가 타입에 따라 정해집니다. 구멍들은 Formatter 하나의 buffer 에 차례로 쓰며, 구멍마다 중간 문자열을 만들지 않습니다 (stdlib 2.10).
| 부분 | 의미 |
|---|---|
fill | 채울 문자 (기본 공백). 바로 뒤에 align 이 있어야 함 |
align | < 왼쪽, > 오른쪽, ^ 가운데, = 부호와 숫자 사이를 채움 (수만) |
sign | + 양수에도 +, 공백은 양수에 공백, - 는 기본 (음수만 -) |
# | b / o / x / X 에 0b / 0o / 0x 접두 |
0 | 수를 0 으로 채움 (= 정렬, fill 0 과 같음) |
.precision | 부동소수는 소수점 아래 자릿수 (변환 없이), 문자열 등은 최대 길이로 자름, 정수에는 무시 |
type | 대상 | 의미 |
|---|---|---|
(없음), d, g, s | 모든 타입 | Display |
f | 수 | 고정 소수점 (precision 자릿수) |
b o x X | 정수 | 2 / 8 / 16 진수. 그 정수 타입의 폭으로 ({-1i8:x} 는 ff) |
e E | 정수, 부동소수 | 지수 표기 |
? | 모든 타입 | Debug. width / align 과 함께 쓸 수 있음 |
정적 spec 은 컴파일 타임에 구멍의 타입과 맞춰 검사하며, 맞지 않으면 그 구멍을 가리키는 에러입니다 ({s:x} 에서 s: str 등). 각 type 글자가 부르는 트레잇은 표준 라이브러리 형식화 사양 (stdlib 2.10) 이 정의합니다.
동적 형식 지정자
형식 지정자 안에 식을 끼울 수 있습니다. {expr} (Python 식) 과 ${expr} 둘 다 됩니다.
f"{x:.{prec}f}" // 동적 정밀도
f"{x:>${width}.${prec}f}" // 동적 너비 + 정밀도
f"{val:{full_spec}}" // 전체 지정자 동적
- 끼운 식은 평가 뒤 문자열로 바뀌어 지정자 텍스트의 일부가 됩니다. 펼친 결과는
spec문법과 구멍의 타입을 만족해야 하며, 아니면 런타임 panic 입니다. - 중첩 구멍은 자기 지정자를 갖지 않습니다.
- 지정자 안의
$$는$하나입니다.${가 아닌 단독$는 그대로입니다.
어휘 처리
렉서는 형식 문자열을 다음 토큰으로 나눕니다.
FStrStart f" 또는 f"""
FStrText "x = " ({{ / }} 는 중괄호로 접힘)
HoleStart
(일반 토큰 - 식)
HoleSpec (선택) :
SpecText / 중첩 구멍
HoleEnd
FStrText ", y = "
FStrEnd
구멍 안의 식은 일반 식 파서가 처리합니다.
1.7.4. 인접 문자열의 자동 결합
C 와 Python 식의 인접 문자열 자동 결합 ("abc" "def" → "abcdef") 은 도입하지 않습니다. 연결이 필요한 경우에는 명시적인 연산자나 메서드를 사용해야 합니다.
1.7.5. 바이트 문자열
byte_string_lit ::= 'b"' byte_string_item* '"'
byte_string_item ::= <any ASCII byte except '"' or '\'> | byte_escape
바이트 문자열은 b 접두어 뒤에 큰따옴표가 오는 형태입니다. 그 바이트들을 담은 고정 배열 [u8; N] 으로 평가되며, N 은 바이트의 개수입니다.
let magic = b"\x7FELF"; // [u8; 4]
let req: Slice<u8> = b"GET /\r\n";
직접 적는 문자는 ASCII 여야 하며, 그 밖의 바이트는 escape 로 적습니다. escape 는 c'A' (1.8.1) 처럼 1.7.1 의 표를 따릅니다: \xHH 는 \x00-\xFF 의 어느 값이든 되며, 모든 escape 의 값은 U+0000-U+00FF 안에 있어야 합니다. 직접 적은 비 ASCII 문자나 U+00FF 를 넘는 escape 는 어휘 에러입니다. 줄 이음 (\ + 줄 끝) 은 지원하지 않습니다.
기대 타입이 Slice<u8> 이거나, Slice<u8> 멤버가 있고 배열 멤버가 없는 합 타입이면, 리터럴은 대신 그 배열의 Slice<u8> view 가 됩니다. b"..."[:] 라고 적은 것과 같습니다.