Unicode 문법

Unicode 문법 (Unicode syntax)

Elixir는 언어 전반에 걸쳐 유니코드를 지원해요. 이 문서는 Elixir가 문법에서 유니코드를 어떻게 지원하는지에 대한 완전한 레퍼런스예요. 문자열("olá")과 문자 리스트('olá')는 Elixir v1.0부터 유니코드를 지원해요 — 문자열은 UTF-8로 인코딩되고, 문자 리스트는 유니코드 코드 포인트의 리스트예요. 이 경우 내용은 개발자가 작성한 그대로 보존되며 어떤 변환도 일어나지 않아요.

출처: Unicode syntax (Elixir v1.20.4)

본문

Elixir는 v1.5부터 변수, 아톰, 호출에서도 유니코드를 지원해요. 이 문서의 초점은 Elixir가 문법에서 어떻게 유니코드를 허용하는지에 대한 높은 수준의 소개를 제공하는 거예요. 또한 Elixir가 유니코드 명세를 어떻게 준수하는지 설명하는 기술 문서도 함께 제공해요.

현재 설치된 Elixir의 유니코드 버전을 확인하려면 String.Unicode.version()을 실행하면 돼요.

소개 (Introduction)

Elixir는 변수, 아톰, 호출에서 유니코드 문자를 허용해요. 다만 유니코드 문자라도 언어 문법의 규칙은 지켜야 해요. 특히 변수와 호출은 대문자로 시작할 수 없어요. 이제부터 이 용어들을 식별자(identifier)라고 부를게요.

식별자에 허용되는 문자는 유니코드가 규정한 문자예요. 대체로 여전히 사용 중인 인간 언어의 문자 체계에서 흔히 쓰이는 문자로 제한돼요. 특히 이모지, 대체 숫자 표현, 음악 기호 같은 것은 제외돼요.

Elixir는 보안 목적으로 식별자에 많은 제한을 둬요. 예를 들어 "josé"라는 단어는 유니코드에서 두 가지로 쓸 수 있어요. j o s é 문자의 조합과 j o s e ́(여기서 악센트는 그 자체가 하나의 문자)의 조합이죠. 전자를 NFC 형식, 후자를 NFD 형식이라고 불러요. Elixir는 모든 문자를 NFC 형식으로 정규화해요.

Elixir는 _로 명시적으로 구분되지 않는 혼합 문자 체계(mixed-script)도 허용하지 않아요. 예를 들어 аdmin이라는 변수 이름을 지을 수 없는데, 여기서 а는 키릴 문자이고 나머지 문자는 라틴 문자예요. 이렇게 하면 다음 오류가 발생해요.

** (SyntaxError) invalid mixed-script identifier found: аdmin

Mixed-script identifiers are not supported for security reasons. 'аdmin' is made of the following scripts:

  \u0430 а {Cyrillic}
  \u0064 d {Latin}
  \u006D m {Latin}
  \u0069 i {Latin}
  \u006E n {Latin}

Make sure all characters in the identifier resolve to a single script or a highly
restrictive script. See https://hexdocs.pm/elixir/unicode-syntax.html for more information.

마지막으로, Elixir는 같은 파일 안에서 혼동 가능한(confusable) 식별자에 대해서도 경고를 내요. 예를 들어 변수 а(키릴 문자)와 a(라틴 문자)를 코드에서 둘 다 사용하면 경고가 발생해요.

이상이 Elixir 식별자에서 유니코드가 사용되는 방식에 대한 전체적인 소개예요. 요약하면, 오늘날 사용되는 다양한 문자 체계를 지원하면서도 Elixir 언어 자체를 명확하고 안전하게 유지하는 것이 목표예요. 기술적인 세부 내용은 다음 섹션에서 다루는 기술적 유니코드 요구사항을 참고하세요.

Unicode Standard Annex #31

Elixir는 Unicode Standard Annex #31: Unicode Identifiers and Syntax, version 17.0에 명시된 표준을 따르는 형식으로 동작해요.

R1. 기본 식별자 (Default Identifiers)

일반적인 Elixir 식별자 규칙은 다음과 같이 명시돼요.

<Identifier> := <Start> <Continue>* <Ending>?

여기서 <Start>는 명세와 같은 범주를 사용하되 NFC 형식으로 정규화해요(R4 참고). 다음이 포함돼요.

대문자·소문자·타이틀케이스·변형자·기타 문자·문자 숫자(letter numbers)의 Unicode General Category에서 파생된 문자, 그리고 Other_ID_Start를 더하고 Pattern_Syntax·Pattern_White_Space 코드 포인트를 뺀 문자

집합 표기로는 [\p{L}\p{Nl}\p{Other_ID_Start}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]이에요.

그리고 <Continue>는 명세와 같은 범주를 사용하되 NFC 형식으로 정규화해요(R4 참고). 다음이 포함돼요.

ID_Start 문자, 그리고 비분리 결합 기호(nonspacing marks)·간격 결합 기호(spacing combining marks)·십진 숫자·연결 구두점의 Unicode General Category를 가진 문자, Other_ID_Continue를 더하고 Pattern_Syntax·Pattern_White_Space 코드 포인트를 뺀 문자

집합 표기로는 [\p{ID_Start}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\p{Other_ID_Continue}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]이에요.

<Ending>은 Elixir 고유의 추가 사항으로, ?(003F)와 !(0021) 코드 포인트만 포함해요.

명세는 <Medial> 집합도 제공하지만, Elixir는 이 집합에 어떤 문자도 포함하지 않아요. 따라서 식별자 규칙은 이를 고려해 단순화됐어요.

Elixir는 식별자에 ZWJ나 ZWNJ를 허용하지 않으므로 R1a를 구현하지 않아요. 양방향 제어 문자(Bidirectional control characters)도 지원하지 않아요. R1b는 하위 호환성(backwards compatibility)을 위해 보장돼요.

아톰 (Atoms)

Elixir의 유니코드 아톰은 위 식별자 규칙을 따르되 다음 수정을 적용해요.

  • <Start>_(005F) 코드 포인트를 추가로 포함
  • <Continue>@(0040) 코드 포인트를 추가로 포함

아톰은 따옴표로 감싸서 쓸 수도 있는데, 이 경우 :"hello elixir"처럼 어떤 문자라도 허용돼요. 모든 Elixir 연산자도 :+, :@, :|> 등과 마찬가지로 유효한 아톰이에요. 유효한 아톰의 전체 설명은 문법 레퍼런스의 "Atoms" 섹션에서 확인할 수 있어요.

변수, 로컬 호출, 리모트 호출 (Variables, local calls, and remote calls)

Elixir 변수는 위 식별자 규칙을 따르되 다음 수정을 적용해요.

  • <Start>_(005F) 코드 포인트를 추가로 포함
  • <Start>에서 Lu(대문자)와 Lt(타이틀케이스) 문자를 추가로 제외

집합 표기로는 [\u{005F}\p{Ll}\p{Lm}\p{Lo}\p{Nl}\p{Other_ID_Start}-\p{Pattern_Syntax}-\p{Pattern_White_Space}]이에요.

별칭 (Aliases)

Elixir의 별칭(Aliases)은 ASCII 문자만 허용하며, 대문자로 시작하고 구두점 문자는 없어요.

R3. Pattern_White_Space와 Pattern_Syntax 문자

Elixir는 공백으로 \t(0009), \n(000A), \r(000D), \s(0020) 코드 포인트만 지원해요. 따라서 R3 요구사항을 따르지 않아요. R3는 더 다양한 공백과 문법 문자를 지원하도록 요구하거든요.

R4. 동등한 정규화 식별자 (Equivalent Normalized Identifiers)

Elixir의 식별자는 대소문자를 구분해요.

Elixir는 모든 아톰과 변수를 NFC 형식으로 정규화해요. 다만 따옴표로 감싼 아톰과 문자열은 어떤 형태든 될 수 있고, 파서가 검증하지 않아요.

다시 말해 아톰 :josé는 코드 포인트 006A 006F 0073 00E9 또는 006A 006F 0073 0065 0301로만 쓸 수 있지만, Elixir는 이를 전자(Elixir 1.14부터)로 다시 써요. 반면 :"josé"006A 006F 0073 00E9 또는 006A 006F 0073 0065 0301로 쓸 수 있고, 따옴표 안에 있으므로 그 형태가 유지돼요.

R4를 선택하면 R5, R6, R7 요구사항은 자동으로 제외돼요.

Unicode Technical Standard #39

Elixir는 Security에 관한 Unicode Technical Standard #39, version 17.0에 명시된 조항을 준수해요.

C1. 식별자 일반 보안 프로파일 (General Security Profile for Identifiers)

Elixir는 아래의 "추가 정규화(Additional normalizations)" 섹션에 명시된 경우를 제외하고, \p{Identifier_Status=Restricted}의 코드 포인트를 가진 식별자의 토큰화를 허용하지 않아요.

General Security Profile을 따르는 구현은 \p{Identifier_Status=Restricted}에 속한 문자를 허용하지 않아요...

예를 들어 흔히 보이지 않는 'HANGUL FILLER'() 문자는 흔하지 않은 코드 포인트이고 경고를 발생시켜요.

C2. 혼동 가능성 탐지 (Confusable detection)

Elixir는 같아 보이지만 다른 식별자에 대해 경고해요. 예: а = a = 1에서 두 'a' 문자는 각각 키릴 문자와 라틴 문자로 서로 혼동될 수 있어요. 力 = カ = 1에서 둘 다 일본어지만 서로 다른 코드 포인트이고, 그 문자 체계의 서로 다른 글자가 돼요. 혼동 가능한 식별자는 잡기 어려운 버그(예: 복사-붙여넣기된 코드)로 이어질 수 있고 안전하지 않을 수 있으므로, 같은 파일 안에서 서로 혼동될 수 있는 식별자에 대해 경고해요.

이 탐지는 Section 4, 'Confusable Detection'에 설명된 방법을 사용하되, 한 가지 명시된 수정을 적용해요.

Elixir는 a-z, A-Z, 0-9, _ 문자만으로 이루어진 식별자에 대해서는 혼동 가능성 경고를 하지 않아요. ASCII 식별자는 오랫동안 존재해 와서, 프로그래밍 커뮤니티가 l,1이나 O,0 같은 식별자 사이의 혼동을 처리할 자체적인 수단(예: 프로그래밍용 폰트는 보통 그런 문자를 구분하기 쉽게 설계됨)을 이미 갖추고 있기 때문이에요.

C3. 혼합 문자 체계 탐지 (Mixed Script Detection)

Elixir는 밑줄로 구분된 덩어리(http_сервер처럼)를 통해서가 아니면 혼합 문자 체계 식별자의 토큰화를 허용하지 않아요. 문자 체계 혼합이 발생하는지 판단하기 위해 Section 5.1, Mixed-Script Detection에 설명된 방법을 사용하며, 'Additional Normalizations'도 함께 적용해요.

예를 들어 Elixir는 幻한 같은 식별자를 허용해요. 여기에는 여러 '문자 체계'의 문자가 포함되지만, UTS 39 5.1의 규칙에 따라 한자는 일본어·한국어와 혼합될 수 있기 때문이에요. 라틴 문자와 일본어 문자 체계를 혼합할 때는 :T_シャツ에서처럼 밑줄이 필요해요(일본어로 't-shirt'를 뜻하는 단어에 글자 T를 구분하는 밑줄을 추가한 것).

Elixir는 if аdmin, do: :ok, else: :err 같은 코드를 허용하지 않아요. 여기서 'a' 문자의 문자 체계 집합(scriptset)은 {Cyrillic}이지만 다른 모든 문자는 {Latin} 문자 체계 집합을 가지거든요. 문자 체계 집합이 서로 일치하지 않으므로 설명적인 오류가 표시돼요.

C4, C5 (적용 불가)

'C4 - Restriction Level detection' 적합성은 주장되지 않으며 코드의 식별자에는 적용되지 않아요. 대신 주어진 임의의 문자열의 안전 수준을 5개 제한 수준 중 하나로 분류하는 데 적용돼요.

'C5 - Mixed number detection' 적합성은 Elixir가 유니코드 숫자를 지원하지 않으므로 적용할 수 없어요.

추가 정규화 (Additional Normalizations)

Elixir 1.14부터 \p{Identifier_Status=Restricted}에 있는 일부 코드 포인트는 다른 제한되지 않은 코드 포인트로 정규화돼요.

현재 이는 MICRO SIGN(µ)을 그리스어 소문자 mu(μ)로 변환하는 데만 적용돼요.

이 정규화는 혼동 가능성을 피하고, 정규화된 코드 포인트에 두 문자의 문자 체계 집합의 합집합을 부여하는 방식으로 혼합 문자 체계 탐지를 수정해요.

예를 들어 MICRO => MU 예시에서 MICRO는 'Common' 문자 체계 문자였어요 — _ 밑줄 코드 포인트에도 부여된 같은 체계죠. 따라서 정규화된 문자의 문자 체계 집합은 {Greek, Common}이 돼요. 'Common'은 모든 비어 있지 않은 문자 체계 집합과 교집합을 가지므로, 정규화된 문자는 어떤 문자 체계로 쓰인 토큰에서도 문자 체계 혼합을 일으키지 않고 사용될 수 있어요.

이런 방식으로 정규화되는 코드 포인트는 커뮤니티에서 사용 중이며 안전하지 않은 문자 체계 혼합을 일으킬 가능성이 낮다고 판단된 것들이에요. 예를 들어 MICRO나 MU 코드 포인트는 마이크로초(microseconds)를 다루는 아톰이나 변수에 사용될 수 있어요.

더 알아보기