naru language specification

Revision 3.1 (slated for 2009-01-XX)

한국어판


이 문서는 강 성훈이 설계한 일반 목적의 객체 지향 프로그래밍 언어인 나루 프로그래밍 언어의 셋째 판을 소개하고 서술합니다.

목차

1장: 소개

나루(naru)는 사용성에 촛점을 맞춘 강력한 프로그래밍 언어입니다. 나루는 다양한 종류의 프로그래밍에 사용될 수 있도록 설계되었으며, 간단한 스크립트로부터 어느 정도의 규모가 되는 애플리케이션까지 다양하게 사용될 수 있도록 만들어진 일반 목적의 언어입니다. 간결함을 가장 큰 목적으로 하고 있지는 않지만, 나루는 C/C++나 Java와 같은 언어에 비해 더 간결하고 간단한 대안을 제시합니다.

1.1 자습서

처음 봤을 때 나루는 인터프리트되는 언어로 보입니다. 만약 나루가 성공적으로 구현된다면, 다음과 같이 나루를 사용할 수 있을 것입니다:

$ naru
print "안녕 세상아!"
(EOF)
안녕 세상아!

한편으론, 나루는 컴파일이 가능하도록 설계되었기 때문에 다음과 같이 실행 파일로 컴파일하여 실행시킬 수도 있을 것입니다:

$ naruc -o hello
print "안녕 세상아!"
(EOF)
$ ./hello
안녕 세상아!

[TBW]

1.2 역사

나루의 설계는 2005년 4월 16일에 시작되었으며, 세 종류의 명세서가 존재합니다.

1판과 2판은 모두 폐기되었으며 3.1판은 3판을 대규모로 수정한 것입니다. 현재의 언어 명세는 3.1판에 따릅니다.

[TBW]

1.3 이 문서에 대하여

이 문서는 언어의 공식적인 명세서인 동시에 레퍼런스 문서로서의 역할을 수행합니다. 또한 언어를 공식적으로 서술하는 유일한 문서이기에, 언어에 대한 간략한 역사와 설계 의도 등도 함께 소개합니다.

[TBW]

1.4 표기법

이 절은 이 문서에서 사용하는 다양한 표기법을 서술합니다.

1.4.1 용어

이 문서에서 나타나는 용어는 다음과 같습니다. [TBW]

"~해야 합니다."
"~하지 말아야 합니다."
이 표현들은 RFC 2119가 정의하는 "MUST", "MUST NOT"과 대응되며, 모든 구현체들이 만족해야 할 제약 사항을 나타냅니다.
"~할 것을 권장합니다."
"~하지 않을 것을 권장합니다."
이 표현들은 RFC 2119가 정의하는 "SHOULD", "SHOULD NOT"과 대응되며, 모든 구현체들이 가능하면 만족해야 하는, 즉 만족하지 않으려면 충분한 이유가 있어야 하는 제약 사항을 나타냅니다.
"~할 수 있습니다."
이 표현은 RFC 2119가 정의하는 "MAY"와 대응되며, 구현체들이 구현할지 말지를 선택할 수 있으나 만약 구현할 경우 다른 구현체들과 어느 정도의 상호 호환성을 보장하기 위하여 그 명세가 제시되어 있는 사항입니다.

1.4.2 정규 문법

나루의 전체 문법은 일부 예외를 제외하면 문맥 독립 문법(context-free grammar)으로 표현할 수 있으며, 그 표현에는 백-나우어스 정규 문법(BNF grammar)의 변형을 사용합니다. 이 표현은 다음과 같이 생겼습니다.

(* 수식은 기본적으로 곱셈적 수식과 동일하다. *)
expression ::= mult-expression

mult-expression ::=
	add-expression
	mult-expression mult-operator add-expression
add-expression ::=
	atom
	add-expression add-operator atom
atom ::=
	NUMBER
	STRING
	"(" mult-expression ")"

mult-operator ::= "*" | "/"
add-operator ::= "+" | "-"

expression ::= "unknown"

STRING ::=
	("u")? '"' (<"\"를 제외한 모든 문자> | ESCAPE-SEQUENCE)* '"'
	("u")? "'" (<"\"를 제외한 모든 문자> | ESCAPE-SEQUENCE)* "'"

(주의: 이 절에 나타나는 모든 문법은 나루의 문법과 무관합니다.)

모든 문법 정의는 비단말 심볼을 다른 심볼들의 연속으로 치환하는 생성 규칙(production rule)으로 이루어집니다. 비단말 심볼은 ::=의 왼쪽에 쓰여지며, 오른쪽에는 가능한 심볼들의 연속이 나타납니다. 그 밖에 (*부터 *)까지 묶인 내용은 문법의 설명을 나타냅니다.

비단말 심볼은 두 종류로 나뉩니다. 모든 글자가 대문자로 쓰여 있으면 구문 분석 과정에서 하나의 토큰 또는 토큰의 부분을 가리키며, 모든 글자가 소문자로 쓰여 있으면 여러 개의 토큰으로 이루어져 있는 하나의 문법 요소를 가리킵니다. 생성 규칙의 처리 방법은 그 좌측에 있는 비단말 심볼의 종류에 따라 다음과 같이 바뀝니다.

이 문서는 혼란을 방지하기 위해 한 문법에 위의 두 규칙을 섞어 쓰지 않습니다. 하지만 한 생성 규칙의 우측에 두 종류의 심볼이 섞여 나오는 것은 가능합니다.

가능한 심볼들의 연속이 여러 줄로 쓰여지고, 괄호 안에 들어 있지 않을 경우, 각 줄은 해당 비단말 심볼에 대한 다른 선택(alternative)을 나타냅니다.

따라서 다음 두 생성 규칙은 동일합니다.

symbol ::=
	symbol "!"
	"WOW"
symbol ::= symbol "!" | "WOW"

이렇게 하면 규칙을 여러 장에 쪼개서 설명할 수 있다는 장점이 있습니다. 물론 대부분의 경우 각 장마다 별도의 심볼을 만드는 것이 보통입니다.

예제의 expression 심볼과 같이, 하나의 비단말 심볼에 대한 선택들은 여러 생성 규칙으로 나뉠 수 있습니다. 따라서 완전한 문법은 이 문서에 나타나는 모든 문법을 합쳐야 얻을 수 있습니다.

하나의 선택은 다음과 같은 내용들을 포함할 수 있습니다.

표 1.1 문법 요소의 목록
문법 요소설명
non-terminal비단말 심볼.
TOKEN토큰.
"terminal", 'terminal'그대로 나타나는 문자열. 해석 과정에서는 하나의 토큰으로 간주됩니다.
[a \uAC00-\uD7A4 <Pd>]안에 있는 글자 중 하나. 다음과 같은 내용들로 구성됩니다.
  • 안에 들어 있는 모든 공백은 무시됩니다.
  • 두 문자가 "-"로 이어지면 그 범위 안의 모든 문자를 가리킵니다. 여기에는 문법에 나타난 두 문자도 포함됩니다.
  • \uXXXX 또는 \UXXXXXXXX 형태의 문자는 16진법으로 표기된 유니코드 문자를 나타냅니다. "\"와 "u"라는 두 문자를 나타내려면 둘 사이에 공백을 넣습니다.
  • <Pd>와 같이 각괄호 안에 묶인 내용은 유니코드 문자 카테고리[Unicode-CharProp]에 속하거나 또는 해당 유니코드 문자 데이터베이스 속성[UCD]이 존재하는 모든 문자를 나타냅니다.
  • <numeric=1/2..3/2>와 같이 각괄호 안에 필요한 유니코드 문자 데이터베이스 속성의 값을 적어 문자를 선택할 수 있습니다.
  • 첫 글자가 "^"(caret)이면 안에 있는 모든 문자를 제외한 나머지 문자를 가리킵니다.
이 요소는 구문 분석 과정에만 쓸 수 있습니다.
<something>해당하는 문법 요소에 대한 설명. 형식적으로 표현하기 힘든 문법일 때 사용합니다.
(A)안에 있는 요소들을 하나의 요소로 간주함.
A BA가 먼저 나온 뒤에 B가 나옴.
A | BA나 B 중 하나.
A - BA에는 해당되나 B는 아님. 펄 정규표현식의 (?!B)A와 유사합니다. 이 요소는 편의를 위해 쓰이며 BNF가 나타낼 수 있는 언어를 확대하지는 않습니다.
A?A가 0번 또는 1번 나타남.
A*A가 0번 또는 그 이상 반복됨.
A+A가 1번 또는 그 이상 반복됨.

그 밖에 문법을 간단하게 유지하기 위하여, 한 문법 요소에 한꺼번에 나타나는 -OPENER-CLOSER로 끝나는 비단말 심볼들은 서로 짝이 맞아야 합니다. 다른 문법 요소(예를 들어 X-OPENER | X-CLOSER 같은 경우)에 걸쳐 있을 경우에는 이런 제한이 없습니다. 어떤 문자열이 어떤 문자열과 짝이 맞는지는 해당 심볼의 설명 또는 주석으로 쓰여집니다. 해당 비단말 심볼이 유한한 수의 문자열에 대응될 경우 이 조건은 BNF가 나타낼 수 있는 언어를 확대하지 않습니다.

실제로 이 규칙은 유니코드의 괄호와 같이 -OPENER-CLOSER가 수백개가 넘는 경우 편리하게 쓰일 수 있습니다. 그러나 뒤에서 볼 수 있듯이, 이렇게 쓰여진 문법도 본질적으로는 여전히 BNF로 풀어 쓰는 것이 가능합니다.

예를 들어,

(* 유니코드 문자 데이터베이스의 Bidi_Mirroring_Glyph 속성에 따라 짝을 정함. *)
TEST-OPENER ::= "(" | "{" | "["
TEST-CLOSER ::= ")" | "}" | "]"

expression ::= TEST-OPENER TEST-OPENER inner-expr TEST-CLOSER TEST-CLOSER

위의 생성 규칙들은 다음과 같이 다시 쓸 수 있습니다.

expression ::=
	"(" "(" inner-expr ")" ")"
	"(" "{" inner-expr "}" ")"
	"(" "[" inner-expr "]" ")"
	"{" "(" inner-expr ")" "}"
	"{" "{" inner-expr "}" "}"
	"{" "[" inner-expr "]" "}"
	"[" "(" inner-expr ")" "]"
	"[" "{" inner-expr "}" "]"
	"[" "[" inner-expr "]" "]"

1.4.3 터미널 세션

예제 터미널 세션의 모습은 다음과 같습니다.

$ naru
print "안녕 세상아!"
(EOF)
안녕 세상아!

모든 터미널 세션은 유닉스 또는 그와 유사한 운영체제를 가정합니다. (마이크로소프트 윈도의 경우 cygwin 등의 방법으로 이 세션을 흉내낼 수 있습니다.) 사용자가 입력한 내용과 프로그램이 출력하는 내용은 서로 다른 글자체 또는 모양으로 구별합니다.

모든 셸 프롬포트에는 "$"를 사용하며, 사용자가 입력한 파일의 끝(EOF) 문자는 "(EOF)"로 표기합니다.

2장: 프로그래밍 환경

나루는 프로그래머에게 제한되어 있으나 일관성을 갖춘 프로그래밍 환경을 제공합니다. 이 장은 그 프로그래밍 환경에 대해 설명합니다.

2.1 가상 기계

일반적으로 나루 코드는 바이트코드(bytecode)라 불리는 가상의 기계어로 컴파일되어 실행됩니다. 이 바이트코드는 가상 기계(virtual machine, VM)라 하는 가상의 프로세서에서 실행되는 것으로 생각할 수 있습니다.

나루 가상 기계는 무한한 저장 공간을 가지고 있으며, 이는 오로지 그를 흉내내는 진짜 기계 및 컴퓨터의 환경에만 제한을 받습니다. 또한 나루 가상 기계는 폰 노이만식 아키텍처를 따르지 않는데, 이는 실행될 수 있는 바이트코드와 그가 다루는 데이터는 별도의 공간에 저장된다는 의미입니다. 하지만 폰 노이만식 아키텍처에서도 동일한 작동을 흉내낼 수 있습니다.

나루 가상 기계가 다룰 수 있는 최소한의 데이터는 값(value)이라 합니다. 값은 서로 다른 방법으로 부호화된 내용을 담을 수 있으며, 이에는 다음과 같은 주요한 값들이 포함됩니다:

일부 실제 기계는 위의 값들을 직접적으로 지원하지 못 할 수도 있습니다. 예를 들어서 1의 보수 표기법을 사용하여 음수를 나타내거나, 시스템 기본 인코딩이 EBCDIC이어서 유니코드로 별도의 변환이 필요한 경우가 있을 수 있습니다. 이런 경우에도 가상 기계는 변환 과정을 투명하게 처리해야 합니다.

실제 기계와 가상 기계 사이의 괴리가 충분히 작은 경우, 바이트코드는 실제 기계에서 사용할 수 있는 기계어의 형태로 변환되어 사용될 수 있습니다. 이 과정은 인터프리터가 성능을 개선하기 위하여 자동으로 수행하거나, 프로그래머의 요청에 따라 실행 파일로 변환할 때 이루어집니다. 어느 경우에도 이렇게 변환된 기계어가 원래 바이트코드와 다른 일을 해서는 안 되며, 그 차이점은 문서화될 수 있을 정도로 충분히 적어야 합니다.

[TBW]

2.1.1 값들의 성질

나루 가상 기계가 지원하는 값들에는 다음과 같은 성질이 있습니다.

논리값
논리값은 참과 거짓을 포함하며, 이 두 값은 정수로 변환되었을 때 0과 1로 표시됩니다. 3진 논리값(tri-state boolean)과 같은 확장은 가상 기계에서 기본적으로 지원하는 논리값에 포함될 수 없습니다.
정수

정수는 2의 보수 표기법을 사용하며 가상 기계가 허락하는 한 임의로 크거나 작은 수를 담을 수 있습니다. 따라서 엄밀하게는 정수는 p=2일 때의 p진수(p-adic number)로 표기됩니다. 이는 2의 보수에서 가장 자리값이 큰 자리가 무한대로 복제되는 것과 유사합니다.

예를 들어, 정수 47903은,

…000010111011000111112

로 표현되고, -581은,

…111111111101101110112

로 표현되며, 이 두 값을 비트 논리곱 연산한 결과는,

…000010111001000110112

가 되고 이는 10진수 47387에 해당합니다.

p진수는 일부 유리수를 소숫점 없이 표기하는 것이 가능하지만 (예를 들어 1/3 = …101010112) 이는 구현의 복잡함 때문에 사용하지 않습니다. 즉, 나루가 지원하는 모든 p진수 값은 왼쪽으로 0 또는 1만을 무한히 반복하는, 10진수에서 소숫점 없이 표현되는 값입니다.

나루 가상 머신은 충분히 작은 정수(보통 절대값이 230 미만)에 대한 최적화를 제공할 수 있으며, 정수로 처리되는 일부 값들이 시스템이 지원하는 정수의 한계를 벗어 날 수 없음이 확실하다면 (리스트의 길이 등) 그 바깥의 값을 에러 처리하면서 시스템이 지원하는 정수로 표현할 수도 있습니다.

부정확한 실수

부정확한 실수는 지수부(exponent)와 2진 가수부(mantissa)로 구성되어 있고 가수부의 자릿수가 고정되어 있는 값입니다. 따라서 대부분의 10진수는 부정확한 실수에서 절삭되어 표현됩니다. 나루 가상 머신은 IEEE 754-1985 표준에서 정의한 형태의 2진 부동 소숫점 실수를 사용하며, 이 표준에 따라 부정확한 실수형은 다음과 같은 여러 제약에 따라 정의됩니다.

IEEE 754 부동 소숫점 표준의 사용에 따라 몇 가지 특징이 생깁니다. 이 표현에는 -0과 +0이 구별되어 있으며, 무한대 값과 "숫자가 아닌"(NaN; Not a Number) 값이 존재하고, 숫자의 실제 값에 따라서 유효자릿수가 바뀌는 경우가 생깁니다.

정확한 실수

정확한 실수는 지수부(exponent)와 가수부(mantissa)로 구성되어 있고 가수부의 자릿수가 고정되어 있지 않은 값입니다. 나루 가상 머신은 IEEE 854-1987 표준에서 정의한 형태의 10진 부동 소숫점 실수를 사용하며, 이 표준에 따라 정확한 실수형은 다음과 같은 여러 제약에 따라 정의됩니다.

IEEE 854 부동 소숫점 표준은 IEEE 754와 유사한 제약 사항을 암시합니다.

복소수
복소수는 부정확한 실수 두 개로 이루어진 수치형으로, 허수 단위 i를 사용하여 a + bi 형태로 나타냅니다. 여기서 허수 단위 i는 -1의 제곱근입니다. 복소수의 제약 사항은 부정확한 실수와 동일합니다.
문자와 문자열

나루의 문자는 유니코드 표준에서 각 문자 별로 정의한 정수, 즉 코드포인트(codepoint)에 해당하며, 문자열은 이 문자의 연속입니다. 문자는 U+FFFF 또는 U+FFFFFFFF까지의 값을 담을 수 있는 부호 없는 정수입니다. (하지만 여기에 포함되는 모든 값이 올바른 유니코드 문자는 아닙니다. 예를 들어 유니코드는 U+110000 이상의 코드 포인트를 정의하지 않습니다.)

유니코드에는 코드포인트에 대한 정의 외에도 여러 명세가 존재합니다. 여기에는 문자를 분류하고 그에 대한 정보를 담은 유니코드 문자 데이터베이스(UCD)와, 문자열을 비교하는 데 사용하는 유니코드 정렬 알고리즘(UCA), 유니코드 문자열을 정규화하기 위한 유니코드 정규화 알고리즘들 등이 포함됩니다.

유니코드 표준은 여러 제약 사항을 암시합니다. 예를 들어 여러 개의 유니코드 문자열은 정규화 과정에 따라 하나의 동일한 문자열로 변환될 수 있으며 (따라서 같은 값으로 처리되어야 하며), 여러 가지 특수한 저장 포맷을 사용해야 할 필요가 있습니다.

이진 배열
이진 배열은 문자열과 유사하지만 각 "문자"가 1바이트에 해당하는 바이트들의 연속입니다. 바이트는 비트들의 모임이며, 문자열의 각 원소에는 문자라는 새로운 형이 존재하지만 이진 배열은 단순히 바이트로 표현되는 정수의 연속으로 정의됩니다.
순서쌍(tuple)
순서쌍은 갯수가 정해져 있는 여러 값들을 묶는 특수한 값입니다.
리스트, 연관 배열, 집합
리스트, 연관 배열, 집합은 여러 값들을 묶기 위한 자료 구조입니다. 리스트는 이 중 가장 간단한 형태의 자료 구조로, 단순히 값들을 순서대로 배열한 것입니다. 연관 배열과 집합은 어떤 값이 해당 자료 구조에 들어 있는지 빠르게 알아 낼 수 있으며, 거기에 관련된 값을 빠르게 저장하고 불러 올 수 있는 알고리즘을 제공합니다.

[TBW: 객체 시스템. 이건 뒤로 가야 하나?]

2.1.2 제약 사항

다음은 나루 가상 기계에 요구되는 제약 사항들과 권장 사항들입니다.

다음은 알고리즘 상의 제약 사항입니다. 여기에 나타난 모든 시간 복잡도는 점진적인 값(amortized cost)을 기준으로 합니다.

[TBW]

2.2 소스 코드

나루 소스 코드는 나루 가상 기계가 해석하여 바이트코드로 컴파일할 수 있는, 프로그래머가 제공한 내용입니다. 소스 코드는 다음 과정을 거쳐서 실행됩니다.

  1. 소스 코드의 취득: 인터프리터는 소스 코드를 외부의 파일 시스템이나, 인터프리터에게 전달된 스트림이나, 또는 다른 소스 코드로부터 제공받습니다. 이 내용은 바이트들의 연속으로 표현됩니다.
  2. 구문 분석 (3장에서 설명): 인터프리터는 바이트들의 연속으로 표현된 소스 코드를 복호화하고 해석하여 토큰들의 연속으로 만듭니다. 토큰에 들어 있는 모든 문자열은 유니코드로 해석됩니다.
  3. 해석 (4장에서 설명): 토큰들의 연속은 해석기를 거쳐 추상 문법 트리(abstract syntax tree)로 변환됩니다. 이 과정에서 코드는 비로소 그 의미에 대응하는 형태로 바뀝니다.
  4. 의미 분석 (5장에서 설명): 추상 문법 트리는 그 자체로 많은 정보를 가지고 있지 않습니다. 인터프리터는 이 트리에 외부의 라이브러리 등의 정보를 이용해 실제로 실행이 가능하도록 정보를 추가합니다. 이 과정에서 서로 상반되는 분석 결과가 나오면 오류가 발생합니다.
  5. 바이트코드 생성: 추상 문법 트리를 효과적으로 실행하기 위하여 바이트코드를 생성하고 최적화합니다.
  6. 실행: 두 가지 가능성이 있습니다.
  7. 바이트코드 및 기계어 저장: 생성되어 실행된 바이트코드 및 기계어는 나중에 실행할 때를 위하여 자동으로 또는 수동으로 저장될 수 있습니다. 이 경우 다음 실행에서는 마지막 단계만 수행하게 됩니다.

나루 소스 코드 및 관련된 파일들은 다음과 같은 규칙을 쓰는 것이 좋습니다. 이는 구현체 사이의 차이를 줄이기 위한 것으로, 파일 시스템이 적용될 수 없는 경우 무시될 수 있습니다.

*.n
컴파일되지 않은 나루 소스 코드. 이 확장자는 이미 여러 용도로 사용되고 있으므로, use naru 전처리기 명령을 사용하여 나루 소스 코드임을 알려 주는 것이 좋습니다.
*.nx
나루 바이트코드. 바이트코드의 형식은 구현체마다 다를 수 있습니다. [TBD]
*.no
나루에서 사용할 수 있는 외부 모듈. 해당 시스템의 공유 객체(shared object) 형식을 따릅니다. 외부 함수 인터페이스는 7장에서 설명합니다.
*.np
나루 패키지. 나루 소스 코드, 바이트코드, 외부 모듈이 Phil Katz의 zip 형식으로 압축된 것입니다. 그 조직은 7장에서 설명합니다.

3장: 구문 분석 및 전처리

구문 분석은 파일 또는 스트림의 형태로 입력된, 바이트의 연속인 소스 코드를 최소한의 구문적 의미를 담는 토큰(token)으로 분리하고 처리하는 과정입니다. 나루의 구문 분석 과정은 입력된 소스 코드를 전처리하여 정규화된 형태로 만드는 과정이 포함됩니다.

나루의 구문 분석 및 전처리 과정은 동시에 일어납니다. 즉, 구문 분석을 마친 토큰들이 전처리기 명령이라면 명령을 읽은 즉시 전처리 과정에 영향을 주며, 그렇지 않으면 이미 적용되고 있는 전처리 명령들이 순차적으로 적용됩니다.

3.1 소스의 구성

나루 소스 코드는 문장들의 모임입니다.

program ::= SHEBANG-LINE? statements

statements ::= statement? (STATEMENT-DELIM statement?)*
block-statements ::= STATEMENT-DELIM (statements STATEMENT-DELIM)?

3.1.1 문장

하나의 문장은 하나의 지정한 역할을 수행하며, 그 안에는 다른 여러 문장들이 들어 있을 수도 있습니다. 각 문장은 보통 하나의 줄을 차지하며, 별도의 문장 구분자 없이 새 줄이 문자 구분자의 역할을 대신 합니다. 다만,

이 규칙은 파이썬 등의 규칙과 동일합니다. 루비와 같이 개행문자가 나올 수 없는 곳에서는 개행문자를 무시하는 규칙은 자칫 엉뚱한 코드가 올바른 것으로 인식되게 할 수 있습니다. 또한 일관성 면에서도 문제가 있는데, 예를 들어:

3 + 4 +
    5

와 같은 코드는 예상한 대로 동작하지만, 다음과 같은 코드는:

3 + 4
  + 5

예상한 대로 동작하지 않습니다. (두 문장으로 해석)

따라서 다음 코드는 두 개의 문장을 담고 있습니다.

	a = (3 + 4 +
		 5); b = 6 * \
				 7

문장이 아닌, 두 개의 물리적인 줄을 구분하는 줄 구분자는 유니코드 줄 바꿈 속성을 따릅니다. 여기에는 U+000D U+000A 연속을 하나의 줄 구분자로 보는 것도 포함합니다.

NEWLINE ::=
	[<Line_Break=CR>]? [<Line_Break=LF>]
	[<Line_Break=BK> <Line_Break=CR> <Line_Break=NL>]

STATEMENT-DELIM ::= ";" WHITESPACE* | NEWLINE

3.1.2 문자 인코딩

나루 소스 코드 안에 포함된 모든 문자들은 유니코드로 변환되어 해석됩니다. 이 과정은 전처리기에 의해 일어나며, 별도의 전처리기 명령이 나타나기 전까지는 기본 인코딩으로 해석됩니다. 기본 인코딩은 소스 코드의 첫 몇 바이트를 읽어서 결정됩니다:

표 3.1 바이트 패턴에 따른 기본 인코딩
바이트 패턴기본 인코딩대응되는 인코딩 선언
EF FB BF16UTF-8use encoding "utf-8"
FE FF16UTF-16 (빅 엔디언)use encoding "utf-16be"
FF FE16UTF-16 (리틀 엔디언)use encoding "utf-16le"
00 00 FE FF16UTF-32 (빅 엔디언)use encoding "utf-32be"
FF FE 00 0016UTF-32 (리틀 엔디언)use encoding "utf-32le"
기타ASCIIuse encoding "ascii"

구현체는 다른 인코딩을 별도로 지원할 수 있습니다. 하지만 구현체는 위에 나타난 모든 인코딩을 기본으로 지원해야 하며, 위의 바이트 패턴에 대응하는 인코딩 선언을 바꿀 수 없습니다. 또한 해당하는 바이트 패턴은 ASCII의 출력 가능한 문자, 즉 2016부터 7E16 범위에 있는 바이트로만 구성될 수 없습니다. 이 제한은 ASCII 호환 인코딩으로 쓰여진 코드가 잘못 읽히는 것을 방지하기 위해 만들어졌습니다.

소스 코드는 use encoding "encoding" 전처리기 명령을 써서 현재 인코딩을 바꿀 수 있습니다. 이 명령은 3.4.2 use encoding 명령에서 설명합니다.

3.1.3 소스 문자의 분류

나루에서는 소스 코드에 나타나는 문자를 여러 종류로 나눠서 취급합니다.

모든 문자

여기에는 소스 코드에 들어 갈 수 있는 모든 종류의 문자가 포함됩니다.

ANY ::= [^ <Cs>]
공백 문자
기호 문자

이 종류의 문자들은 유니코드 식별자 및 패턴 문법[UAX31]에서 서술하는 속성들을 기반으로 합니다.

WHITESPACE-CHAR ::= [<Pattern_White_Space>]
NEWLINE-CHAR ::= [<Line_Break=BK> <Line_Break=CR> <Line_Break=LF>
	<Line_Break=NL>]
HORIZ-WHITESPACE-CHAR ::= WHITESPACE-CHAR - NEWLINE-CHAR
PUNCT-CHAR ::= [<Pattern_Syntax>]
식별자에 사용할 수 있는 문자
이 종류의 문자들은 3.2.2 식별자에서 서술합니다. 이 문자들은 공백 문자와 기호 문자를 제외한 모든 문자들입니다.
2진법, 8진법, 10진법, 16진법, 36진법 자리로 쓰일 수 있는 문자

유니코드 표준에는 다양한 종류의 숫자가 존재하나, 나루는 독자의 혼동을 줄이기 위해 인도-아라비아 숫자만을 허용합니다. [TBD]

ZERO-DIGIT ::= "0"
BINARY-DIGIT ::= [0-1]
OCTAL-DIGIT ::= [0-7]
DECIMAL-DIGIT ::= [0-9]
HEXADECIMAL-DIGIT ::= DECIMAL-DIGIT | [a-f A-F]
ALPHADECIMAL-DIGIT ::= DECIMAL-DIGIT | [a-z A-Z]
괄호로 쓰이는 문자

괄호로 쓰이는 문자들은 유니코드에서 Ps/Pe 분류에 속하면서 유니코드 문자 데이터베이스의 Bidi_Mirroring_Glyph 속성이 존재하고 서로를 가리키는 문자들입니다. 다만 예외적으로 <>는 Ps/Pe 분류에 속하지 않지만 포함됩니다.

CLOSURE-CHAR ::= CLOSURE-OPENER | CLOSURE-CLOSER

(* Bidi_Mirroring_Glyph 속성에 따라 짝을 정한다. 단 "<"의 짝은 ">"이다. *)
CLOSURE-OPENER ::= "<" | [<Ps>] - [^ <Bidi_Mirroring_Glyph>]
CLOSURE-CLOSER ::= ">" | [<Pe>] - [^ <Bidi_Mirroring_Glyph>]

참고로 어떠한 문자가 Bidi_Mirroring_Glyph 속성을 가지고 있다면, 해당되는 문자의 Bidi_Mirroring_Glyph 속성은 원래 문자와 동일합니다. 따라서 위의 문법 정의는 항상 모든 짝을 정의합니다.

유니코드에서 괄호나 따옴표의 짝을 결정하는 방법은 여러 가지가 있지만, 가장 확실하고 정확한 방법은 Bidi_Mirroring_Glyph 속성입니다. Ps/Pe 분류에 속한 일부 문자들은 문자의 모양 때문에 정확한 짝을 찾기 힘들어 Bidi_Mirroring_Glyph 속성이 존재하지 않는 경우가 있는데, 이러한 문자를 소스 코드에 써서 얻을 수 있는 이득은 없는 것으로 보입니다.

위 설명의 예외로 서로 좌우 대칭이지만 Bidi_Mirroring_Glyph에 해당하지 않는 U+FD3E와 U+FD3F 문자가 있는데, 이는 유니코드 문자 데이터베이스의 문제로 보이며 실제로 소스 코드에 사용하기는 힘든 문자이기도 합니다.

따옴표로 쓰이는 문자

따옴표로 쓰이는 문자들은 유니코드에서 Pi/Pf 분류에 속하면서 유니코드 문자 데이터베이스의 Bidi_Mirroring_Glyph 속성이 존재하고 서로를 가리키는 문자들입니다. 다만 예외적으로 '"는 서로와 짝이 맞습니다.

QUOTE-CHAR ::= QUOTE-OPENER | QUOTE-CLOSER

(* Bidi_Mirroring_Glyph 속성에 따라 짝을 정한다. 단 '와 "는 서로와 짝이 맞는다. *)
QUOTE-OPENER ::= "'" | '"' | [<Pi>] - [^ <Bidi_Mirroring_Glyph>]
QUOTE-CLOSER ::= "'" | '"' | [<Pf>] - [^ <Bidi_Mirroring_Glyph>]

3.2 토큰

나루의 토큰은 크게 식별자와 예약어, 기호 및 연산자, 그리고 리터럴로 나눌 수 있습니다. 모든 토큰은 올바른 토큰이 될 수 있는 마지막 글자까지 확장됩니다. 예를 들어서 <<<가 올바른 토큰이라면, <<<라는 코드는 항상 두 개의 토큰 <<<로 해석됩니다.

두 토큰을 모호함 없이 구분하기 위해서 공백 문자를 하나 이상 사용할 수 있습니다. 모호함이 없다 하더라도 가독성을 높이고 관리를 쉽게 하기 위해서 공백 문자를 적절히 사용하는 것을 권장합니다.

3.2.1 공백 문자

WHITESPACE ::= WHITESPACE-CHAR | "\" WS NEWLINE
WS ::= HORIZ-WHITESPACE-CHAR*

[TBW]

3.2.2 주석

WHITESPACE ::= SHORT-COMMENT | LONG-COMMENT

SHORT-COMMENT ::=
	"--" (ANY - NEWLINE-CHAR)*
LONG-COMMENT ::=
	LC-OPENER (ANY - LC-CLOSER)* LC-CLOSER ("" - ")")

(* 갯수가 같은 괄호끼리 짝이 맞음. *)
LC-OPENER ::= "(--" | "((--" | "(((--" | ...
LC-CLOSER ::= "--)" | "--))" | "--)))" | ...

주석은 프로그램의 실행에는 영향을 미치지 않지만 그 코드를 읽는 대상에게 도움을 주기 위한 내용입니다.

나루는 --로 시작하고 줄의 끝까지만 무시하는 짧은 주석과, (--로 시작해서 --)로 끝나고 여러 줄에 걸칠 수 있는 긴 주석을 지원합니다. 나루는 주석을 완전히 무시하며 어떠한 해석 과정에 걸쳐서 주석에 대한 어떠한 처리도 하지 않습니다.

긴 주석은 공백 없이 0개 이상의 괄호로 묶일 수 있습니다. 즉, (-- … --), ((-- … --)), (((-- … --))) 등은 모두 긴 주석이며, () 등의 괄호를 남기지 않습니다. 긴 주석은 중첩이 부가능하지만 괄호 수가 적은 긴 주석들을 더 많은 괄호를 가진 긴 주석으로 감쌀 수는 있습니다.

주석은 하나의 공백에 해당합니다. 따라서 주석은 토큰과 다른 토큰을 구분하는 데 공백 대신 쓰일 수도 있습니다. 다만 긴 주석의 경우, 패턴 리터럴과 같이 리터럴이면서 공백을 포함한 토큰 안에서는 공백으로 처리되지 않습니다. (이러한 처리는 /w 옵션을 쓸 때 편리할 것입니다.) [TBD: unspace 도입?]

다음은 짧은 주석과 긴 주석의 사용을 나타내는 예제입니다.

ln = "Hello, "
print(-- 이 문장은 두 개의 토큰으로 해석된다. --)ln
println("world!" ((--
	이 주석 안에는 다른 주석이 포함될 수 있다.
	print(-- ... --)ln
	다만 더 괄호가 많은 주석으로 끝날 수는 없다.
--)))       -- 마지막 "--)))"는 "--))"와 ")"로 분리된다.
-- 짧은 주석은 긴 주석을 무시한다. ((--
exit 0
3.2.2.1 셔뱅(shebang) 줄
SHEBANG-LINE ::= "#!" (ANY - NEWLINE)* NEWLINE

셔뱅(shebang) 줄은 유닉스 계열의 운영체제에서 사용되는 특수한 줄로, 파일의 맨 처음에 위치해 있으며 해당 파일이 어떤 프로그램에 의해 실행되어야 할 지 결정합니다. 셔뱅 줄은 #!로 시작하고, 0개 이상의 공백 뒤에 해당 프로그램의 이름과 인자들이 나타납니다. 셔뱅 줄을 인식하는 셸은 마지막 인자 뒤에 파일 이름을 붙여서 파일을 실행하려 합니다.

나루는 #로 시작하는 줄을 주석으로 인식하지 않지만, 파일이 정확히 #!(23 2116)로 시작할 경우 그 줄을 셔뱅 줄로 인식하여 무시합니다. 구현체는 이 줄을 무시하지 않고 더 해석해서 코드에 필요한 옵션들을 얻어 낼 수 있습니다.

다음은 셔뱅 줄을 포함한 나루 프로그램의 예제입니다.

#!/usr/bin/env naru -eutf-8
println ARGV0

만약 나루 구현체가 셔뱅 줄을 해석할 수 있다면, 이 예제를 실행한 결과는 다음과 같을 것입니다.

$ naru -Iexample/ test.n
['/usr/bin/naru', '-eutf-8', '-Iexample/', 'test.n']

구현체가 셔뱅 줄을 해석하지 않는다면 다음과 같은 결과가 나올 것입니다.

$ naru -Iexample/ test.n
['/usr/bin/naru', '-Iexample/', 'test.n']
3.2.2.2 문서화 주석

문서화 주석은 공백 없이 |로 시작하는 짧은 주석이나 공백 없이 |로 시작하고 끝나는 긴 주석입니다. (다만 주석에 이어지는 하이픈 -은 포함하지 않습니다.) 이 주석은 나루의 주석 문법에 포함되며, 실제로 나루의 구문 분석 과정에서 어떠한 영향도 주지 않습니다. 하지만 이 주석들은 추후에 소스 코드로부터 문서를 뽑아 낼 수 있도록 예약되어 있습니다.

다음 예제는 문서화 주석과 일반 주석의 차이를 보이고 있습니다.

-- 일반 주석
--| 문서화 주석
-- | 일반 주석 ("--"와 "|"는 붙어 있어야 함)
----------| 문서화 주석
(-- 일반 주석 --)
(--| 문서화 주석 |--)
((-----| 문서화 주석 |---))
(--- --| 일반 주석 (하이픈에 공백이 포함됨) |---)

[TBW]

3.2.3 식별자

나루의 식별자는 변수, 자료형 등을 구분하는데 사용되는 이름입니다. 이 문법은 유니코드 식별자 및 패턴 문법[UAX31]에 기반을 두고 있습니다.

IDENTIFIER ::= "@"? (GENERIC-IDENTIFIER - KEYWORD) | SPECIAL-NAME

SIMPLE-IDENTIFIER ::= [<ID_Start>] [<ID_Continue>]*

GENERIC-IDENTIFIER ::= SIMPLE-IDENTIFIER [!?]?

SPECIAL-NAME ::=
	OPERATOR-NAME

OPERATOR-NAME ::=
	"#"? (PUNCT | KEYWORD)+ ("#" (PUNCT | KEYWORD)+)+ "#"?
	"#" (PUNCT | KEYWORD)+
	(PUNCT | KEYWORD)+ "#"

식별자는 유니코드 식별자 및 패턴 문법에서 지정하는 문자(ID_Start)로 시작하며 그 뒤에 0개 이상의 지정된 문자(ID_Continue)가 나타날 수 있습니다. 모든 식별자는 유니코드 정규화 형태 C(NFC)에 따라 정규화되어 저장되며 인식됩니다.

변수나 함수 등의 일반적인 이름은 이 뒤에 ?! 문자가 따라 올 수 있습니다. 전자는 일반적으로 참 또는 거짓을 반환하는 경우(predicate)에 사용하며, 후자는 주어진 객체 등을 변경하는, 파괴적인(destructive) 연산을 하는 데 사용되지만, 꼭 이런 규칙을 지켜야 하는 것은 아닙니다.

연산자 이름은 특정한 연산자를 정의하거나 호출할 때 내부적으로 쓰이는 이름입니다. 연산자는 문법의 각 피연산자를 #로 치환하고 한데 이어서 이루어집니다. 일반적으로 전위 연산자는 #op, 후위 연산자는 op#, 중위 연산자는 #op#의 형태로 나타납니다. 다만 전위/후위 괄호 문법의 경우 괄호 안에 들어 있는 것들을 하나의 인자로 처리하여 하나의 #로 치환하며, with 문법의 경우 그 뒤에 붙는 값들은 인자로 처리하지 않기 때문에(실제로는 인자에 달라 붙기 때문에) #with의 이름을 가집니다.

연산자 이름을 올바르게 토큰화하기 위해서는 #를 식별자의 일부로 취급하고 해석하는 것이 권장됩니다. 실제로 # 문자는 셔뱅 줄과 연산자 이름에서만 나타납니다.

3.2.4 예약어

예약어는 식별자에 포함되지만 특수한 용도로 사용되기 때문에 식별자로 쓰일 수 없는 토큰입니다. 나루의 예약어는 다음과 같습니다.

KEYWORD ::=
	SIMPLE-KEYWORD [!?]?

SIMPLE-KEYWORD ::=
	"and"
	"as"
	"assert"
	"begin"
	"break"
	"but"
	"class"
	"const"
	"else"
	"elseif"
	"end"
	"false"
	"finally"
	"for"
	"fun"
	"if"
	"import"
	"in"
	"is"
	"namespace"
	"nil"
	"not"
	"or"
	"raise"
	"retry"
	"return"
	"self"
	"true"
	"type"
	"undef"
	"undo"
	"unless"
	"until"
	"use"
	"var"
	"with"
	"yield"
	ADDITIONAL-KEYWORD
(* [TBD] *)

예약어는 아무 기호도 붙지 않은 형태와 ! 또는 ? 기호가 붙은 형태 모두 식별자로 쓰일 수 없습니다. 하지만 이들 형태는 서로 다른 의미로 쓰일 수 있습니다. 예를 들어 if …; yield; endif? …; yield!; end의 의미는 다를 수 있으며, 실제로 후자는 아예 울바른 나루 코드도 아닙니다.

3.2.5 기호와 연산자

기호는 예약어 문자에 포함되지 않는 문자들로 이루어진 토큰으로, 문법에서 특수한 역할을 하거나 연산자로 쓰이는 토큰입니다. 나루에서 쓰이는 기호는 기본적으로 다음과 같습니다.

MINUS ::= "-" | "−" (* U+2212 *)
TIMES ::= "*" | "×" (* U+00D7 *)
DIVIDES ::= "/" | "÷" (* U+00F7 *)
LESS-OR-EQUAL ::= "<=" | "≤" (* U+2264 *) | "≦" (* U+2266 *)
GREATER-OR-EQUAL ::= ">=" | "≥" (* U+2265 *) | "≧" (* U+2267 *)
NOT-EQUAL ::= "!=" | "≠" (* U+2260 *)
LEFT-ARROW ::= "<-" | "←" (* U+2190 *)
RIGHT-ARROW ::= "->" | "→" (* U+2192 *)
ELLIPSIS ::= "..." | "…" (* U+2026 *)
MAPS-TO ::= "=>" | "↦" (* U+21A6 *)
EMPTY-SET ::= "{/}" | "∅" (* U+2205 *)

PUNCT ::=
	"!<" | [\u226e]
	"!<="
	"!<>"
	"!<>="
	NOT-EQUAL
	"!>" | [\u226f]
	"!>="
	"%"
	"%="
	"&"
	"&="
	"&&"
	"&&="
	"("
	")"
	TIMES
	TIMES "="
	"**"
	"**="
	"+"
	"+="
	","
	MINUS
	MINUS "="
	RIGHT-ARROW
	"."
	".."
	"..!"
	ELLIPSIS
	DIVIDES
	DIVIDES "="
	"/%"
	"//"
	"//="
	":"
	":="
	";"
	"<"
	LEFT-ARROW
	"<<"
	"<<="
	LESS-OR-EQUAL
	"<=>"
	"<>"
	"<>="
	"="
	"=="
	MAPS-TO
	">"
	GREATER-OR-EQUAL
	">>"
	">>="
	"["
	"\" ("" - WS NEWLINE)
	"]"
	"^"
	"^="
	"^^"
	"^^="
	"{"
	EMPTY-SET
	"|"
	"|="
	"||"
	"||="
	"}"
	"~" | [\u00ac]
	"~="
	"~~"
	"~~="
	ADDITIONAL-PUNCT
(* [TBD] *)

3.3 리터럴

나루의 리터럴은 상수식으로 계산되는 기본 자료형에 대한 문법입니다. 리터럴은 그 결과 자료형에 따라 여러 종류로 나뉩니다.

literal ::=
	const-literal
	number-literal
	CHAR-LITERAL
	STRING-LITERAL
	SYMBOL-LITERAL

const-literal ::=
	NIL-LITERAL
	UNDEF-LITERAL
	BOOLEAN-LITERAL

number-literal ::=
	INTEGER-LITERAL
	RATIONAL-LITERAL
	DECIMAL-LITERAL
	FLOAT-LITERAL
	IMAGINARY-LITERAL

3.3.1 상수 리터럴

나루는 미리 지정된 상수로 계산되는 네 개의 리터럴을 지원합니다.

NIL-LITERAL ::= "nil"
UNDEF-LITERAL ::= "undef"
BOOLEAN-LITERAL ::= "true" | "false"

nil 리터럴은 naru.lang.type.Nil 형으로 평가되는 유일한 값이자, 그 형 자체를 나타냅니다. 이 값은 어떤 유용한 정보를 담고 있지 않으며 그러한 정보의 부재를 나타냅니다.

undef 리터럴은 정의되지 않았거나 사라진 변수에 들어 가는 값입니다. 이 값은 naru.lang.type.Undef 형으로 평가되는 유일한 값으로 볼 수 있으며, 그 형 자체를 나타내는 데도 쓰입니다.

truefalse 리터럴은 naru.core.Boolean 형으로 평가되는 두 개의 값으로 각각 참과 거짓을 나타냅니다.

3.3.2 정수 리터럴

정수 리터럴은 naru.core.Integer 형으로 평가되는 값입니다.

INTEGER-LITERAL ::=
	SIMPLE-INTEGER-LITERAL
	BASED-INTEGER-LITERAL
	EXTENDED-INTEGER-LITERAL

SIMPLE-INTEGER-LITERAL ::=
	DECIMAL-DIGIT ("_"? DECIMAL-DIGIT)*

BASED-INTEGER-LITERAL ::=
	ZERO-DIGIT "b" BINARY-DIGIT ("_"? BINARY-DIGIT)*
	ZERO-DIGIT "o" OCTAL-DIGIT ("_"? OCTAL-DIGIT)*
	ZERO-DIGIT "d" DECIMAL-DIGIT ("_"? DECIMAL-DIGIT)*
	ZERO-DIGIT "x" HEXADECIMAL-DIGIT ("_"? HEXADECIMAL-DIGIT)*

EXTENDED-INTEGER-LITERAL ::=
	DECIMAL-DIGIT+ QUOTE-OPENER INTEGER-SEQUENCE QUOTE-CLOSER

NUMBER-SIGN ::= PLUS-OPERATOR | MINUS-OPERATOR

INTEGER-SEQUENCE ::=
	WS INTEGER-SEQITEM (INTEGER-SEQDELIM INTEGER-SEQITEM)* WS

INTEGER-SEQITEM ::=
	CLOSURE-OPENER WS NUMBER-SIGN? DECIMAL-DIGIT+ WS CLOSURE-CLOSER
	ALPHADECIMAL-DIGIT

INTEGER-SEQDELIM ::= WS ("_" | "'" | ",")? WS

정수 문법은 대부분의 언어들이 비슷하기 때문에 특정 언어의 영향을 받았다고 보기 힘듭니다. 임의 진법을 위한 정수 문법은 문자열과 같은 문법으로 해석될 수 있도록, 따라서 소스 하이라이팅 등을 편하게 하기 위하여 정한 것입니다.

별도의 접두사가 없으면 숫자는 10진법으로 해석됩니다. 다음은 진법을 결정하는 접두사들입니다.

표 3.2 정수 리터럴 접두사 목록
접두사진법사용하는 자리들
0b2진법0, 1
0o8진법0, 1, 2, …, 7
0d10진법0, 1, 2, …, 7, 8, 9
0x16진법0, 1, 2, …, 7, 8, 9, A, B, C, D, E, F

숫자는 대소문자를 구별하지 않지만, 접두사는 항상 소문자로만 쓰여져야 합니다. 또한 전통적으로 8진법 접두사로 쓰이는 0(예를 들어서 0317)은 나루에서 특별한 의미를 가지지 않습니다.

접두사에 대문자를 허용하면 0O123이 8진법인지 10진법인지 구분하기가 힘들어집니다. 또한 0으로 시작하는 8진법 접두사를 허용해서 얻는 이득은 상대적으로 크지 않기 때문에 다른 진법과 동일하게 취급하는 데 무리는 없다 생각됩니다.

자리들 사이에 있는 _는 자리 구분자의 역할을 하며 무시됩니다. _는 두 개 이상 연속될 수 없습니다.

임의의 진법을 쓰기 위해서 radix'…' 형태의 확장 문법을 쓸 수 있습니다. 진법은 따옴표 앞에 십진법으로 주어지며, 0부터 9까지의 자리는 0부터 9로, 10부터 35까지의 자리는 A부터 Z로 씁니다. <59>와 같이 괄호로 묶인 십진수는 해당하는 자리를 나타냅니다. 자리와 자리 사이에는 공백이나 자리 구분자 _, ,, 또는 '가 사용될 수 있습니다.

표 3.3 정수 리터럴의 예시
접두사 표기확장 문법십진법 값
0b1_0010_0101_11102'1_0010_0101_1110' 또는 2'1 0010 0101 1110'4702
0o0560148'5 6 0  1 4'23564
290000 또는 0d29000010"290'000"290000
0x11DE784A16'11DE784A' 또는 16'<1>(1)[13]<14>«7»〔8〕4A'299792458
없음60' 3 (22) (45) (38) (16) (17) (46) (40) '9460000000000

정수 리터럴은 부호를 포함하지 않지만, 확장 문법의 각 자리가 음수가 될 수는 있습니다. 따라서 r진법 확장 문법의 각 자리에는 -(r-1)부터 r-1까지의 숫자가 들어 갈 수 있습니다. 예를 들어 16'1000<-1>'16'FFFF'와 동일합니다.

정수 리터럴이 부호를 포함하지 않는 이유는 문법의 충돌을 방지하기 위해서입니다. 간단한 예로 3-4*5는 정수 리터럴이 부호를 포함할 경우 해석 과정에서 오류가 발생할 것입니다. 이를 정수 리터럴의 정의를 고치지 않고 수정하는 것은 매우 어렵습니다.

3.3.3 분수 리터럴

분수 리터럴은 naru.core.Rational 형으로 평가되는 값입니다.

분수는 정수끼리 나눴을 때 나올 수 있는 값 중 하나로, 정수 나눗셈과 일반 나눗셈의 구분을 위해서 생겨난 자료형입니다. 이는 정확한 실수형과는 다른 목적으로 사용됩니다. (물론 상호 변환은 여전히 가능합니다.)

실용적으로도 분수는 정수보다는 작은 단위지만 여전히 정수(들)로 나타내는 게 유리한 값을 위해 필요합니다. 예를 들어 전자 출판에서 1포인트는 1/72인치로 정의되는데, 이를 나타내기 위하여 72를 곱한 값을 저장하기로 약속하는 것은 쉽지만 사용하기는 쉽지 않습니다.

RATIONAL-LITERAL ::=
	INTEGER-LITERAL ("r" | "_r_") INTEGER-LITERAL
	DECIMAL-DIGIT+ QUOTE-OPENER RATIONAL-SEQUENCE QUOTE-CLOSER

RATIONAL-SEQUENCE ::=
	INTEGER-SEQUENCE WS "/" WS INTEGER-SEQUENCE

분수 리터럴은 두 개의 정수 리터럴을 묶어서 하나는 분자, 하나는 분모로 만든 것입니다. 예를 들어 0xFAC_r_16'ADE'는 분자 0xFAC(4102)와 분모 16'ADE'(2782)로 이루어진 분수이며, 약분하면 2006r1391와 같습니다.

분수 리터럴의 분자와 분모는 r 또는 _r_로 서로 분리됩니다. (/ 연산자를 쓸 수도 있지만 하나의 리터럴이 아니며 연산자가 재정의되었을 경우 문제가 생길 수 있습니다.) 정수 리터럴의 진법 접두사와 마찬가지로, 구분자는 항상 소문자로 쓰여져야 합니다.

분수 리터럴에는 진법 접두사나 확장 문법을 쓴 정수 리터럴을 쓸 수 있습니다. 또한 하나의 확장 문법에 / 문자로 분자와 분모를 나눌 수도 있습니다. 예를 들어 16'FAC/ADE'16'FAC'_r_16'ADE'과 동일합니다.

3.3.4 정확한 실수 리터럴

정확한 실수 리터럴은 naru.core.Decimal 형으로 평가되는 값입니다.

DECIMAL-LITERAL ::=
	DECIMAL-DIGITS "." DECIMAL-DIGITS?
	DECIMAL-DIGITS ("." DECIMAL-DIGITS?)? DECIMAL-EXPONENT

DECIMAL-DIGITS ::=
	DECIMAL-DIGIT ("_"? DECIMAL-DIGIT)*

DECIMAL-EXPONENT ::=
	[Ee] NUMBER-SIGN? DECIMAL-DIGIT+

정확한 실수 리터럴은 소숫점(.)을 포함하거나 지수 문자(E 또는 e)를 포함한 리터럴로, 소숫점 및 맨 처음의 0을 제외한 나머지 숫자들은 유효 숫자로 쓰입니다. 예를 들어 3.14100e+4는 31410과 같은 값이면서 여섯 자리의 유효 숫자를 가지고 있는 정확한 실수 리터럴입니다.

정확한 실수 값은 리터럴에 나타난 모든 유효 숫자를 저장하고 있습니다. 따라서 맨 뒤에 붙는 0은 무시되지 않는데, 예를 들어 42.19542.19500은 값은 같지만 후자가 유효 자릿수가 더 많습니다.

3.3.5 부정확한 실수 리터럴

부정확한 실수 리터럴은 naru.core.Float 형으로 평가되는 값입니다.

FLOAT-LITERAL ::= (SIMPLE-INTEGER-LITERAL | DECIMAL-LITERAL) "f"

부정확한 실수 리터럴은 정확한 실수 리터럴과 같으나 뒤에 f 접미사를 붙인 것으로, 이 접미사는 정수 리터럴의 진법 접두사와 마찬가지로 소문자로만 쓰여져야 합니다.

부정확한 실수 리터럴은 정확한 실수 리터럴과는 달리 유효 숫자의 개념이 없습니다. 따라서 42.195f42.19500f는 완전히 동일합니다. 한편 부정확한 실수 리터럴은 표현할 수 있는 자릿수들에 제한이 있으며 그 기준은 IEEE 754의 이진 부동 소숫점 표준에 따릅니다. 만약 너무 많은 자리가 입력되어 절삭된다면 컴파일러는 이를 사용자에게 경고할 것입니다.

부정확한 실수 리터럴은 +0f-0f를 구분하며, 처음 생성될 때 0f는 전자로 해석됩니다. 또한 십진 표현으로 유한한 자리로 표현되는 숫자가 이진 표현으로 무한한 자리로 표현될 수도 있으며, 예를 들어 0.1f가 실제로는 0.10000 00000 00000 00555 11151 23125 78270 21181 58340 45410 15625로 저장될 수도 있습니다.

3.3.6 허수 리터럴

허수 리터럴은 naru.core.Complex 형으로 평가되는 값입니다. 완전한 복소수는 이 리터럴과 부정확한 실수 리터럴을 합하여 얻어집니다.

복소수는 실수만큼은 아니지만 다양한 과학적 계산에서 사용되기 때문에 언어 차원에서의 지원이 도움이 됩니다. 또한 서로 다른 여러 라이브러리들 사이의 복소수 형의 호환성을 보장할 수 있다는 장점도 있습니다.

IMAGINARY-LITERAL ::= (SIMPLE-INTEGER-LITERAL | DECIMAL-LITERAL) "j"

허수 리터럴 문법 numberj는 파이썬으로부터 왔습니다. j는 전기 전자에서 전류를 나타내는 i 대신 사용하는 허수 기호입니다.

허수 리터럴은 부정확한 실수 리터럴을 허수 단위 i, 즉 (−1)0.5에 곱한 것으로, 실수부가 0이고 허수부가 지정한 부정확한 실수 리터럴인 복소수 값을 표현합니다.

정확한 실수를 바탕으로 한 복소수는 실제 사용에 비춰 볼 때 불필요하다 보여 지원하지 않습니다. 물론 사용자는 필요하다면 이러한 복소수 클래스를 만들 수 있습니다.

허수 리터럴 및 복소수는 나루가 언어 차원에서 지원하는 가장 넓은 수치형입니다. 사원수와 같이 이보다 넓은 범위의 수치형을 다루기 위해서는 별도의 라이브러리가 필요합니다.

3.3.7 문자 리터럴

문자 리터럴은 naru.core.Char 형으로 평가되는 값으로, 하나의 유니코드 코드 포인트를 나타냅니다.

문자는 문자열과 구분되어 있으며, 숫자와도 구별됩니다. 전자에 대한 이유는 타입 체계를 좀 더 견실하게 만들려는 데 이유가 있고, 후자에 대한 이유는 문자에는 숫자값 말고도 이름이 있을 뿐만 아니라 올바른 값에 대한 범위도 존재하기 때문입니다.

CHARACTER-LITERAL ::= "?" CHARACTER-VALUE

CHARACTER-VALUE ::=
	ANY - (QUOTE-OPENER | "\")
	CHAR-ESCAPE-SEQ
	SIMPLE-IDENTIFIER
	BASIC-STRING-LITERAL

CHAR-ESCAPE-SEQ ::=
	"\" ([abefnrstv\] | QUOTE-OPENER | QUOTE-CLOSER)
	"\" DECIMAL-LITERAL+
	"\x" HEXA-OCTET
	"\u" HEXA-OCTET HEXA-OCTET
	"\U" HEXA-OCTET HEXA-OCTET HEXA-OCTET HEXA-OCTET

HEXA-OCTET ::= HEXADECIMAL-DIGIT HEXADECIMAL-DIGIT

문자 리터럴 문법 ?char는 루비에서 유래했습니다.

문자 리터럴은 여러 가지 방법으로 쓸 수 있습니다.

모든 탈출열은 \로 시작하며, 문자 리터럴 뿐만 아니라 문자열에서도 사용할 수 있습니다. 탈출열은 다음 중 하나에 속해야 합니다.

표 3.4 문자 탈출열들의 목록
탈출열의미유니코드
\decimal10진법 코드 포인트 (최대 일곱 자리)
\\역슬래시U+005C
\', \", …따옴표 문자U+0027, U+0022, …
\a벨 문자U+0007
\b백스페이스 문자U+0008
\e탈출 문자U+001B
\f폼 피드 문자U+000C
\n개행 문자U+000A
\s공백 문자U+0020
\r캐리지 리턴 문자U+000D
\t탭 문자U+0009
\u????16진법 코드 포인트 (네 자리)
\U????????16진법 코드 포인트 (여덟 자리)
\x??16진법 코드 포인트 (두 자리)

제시된 탈출열 중 길이가 가변적인 것은 \decimal이 유일하며, 올바른 유니코드 문자가 되는 (즉 1114112보다 작은) 최대한 많은 10진법 숫자를 읽습니다. 문자 리터럴의 경우에도 마찬가지기 때문에, 예를 들어 ?\1234567은 유니코드 코드 포인트가 123456인 문자 리터럴(?\U0001e240과 같음)과 숫자 7로 토큰화됩니다.

문자의 이름은 대소문자를 구별하지 않습니다. 예를 들어 ?AMPERSAND?Ampersand, ?aMPeRSaND는 모두 똑같은 문자 "&"를 나타내는 리터럴입니다. 문자의 이름은 기본적으로 유니코드 문자 데이터베이스의 이름을 따르고, 추가적으로 다음 이름을 사용할 수 있습니다.

이 이름들은 C0, C1 영역을 위한 것으로, 유니코드 문자 데이터베이스에는 이들에 대한 이름이 정해져 있지 않기 때문에 사용합니다. ^@와 같은 코드는 컨트롤 및 메타 조합에 대응합니다. 일부 다른 형태의 이름(space 등)은 R6RS를 따랐습니다.

표 3.5 추가된 문자 이름들
유니코드이름유니코드이름
U+0000NUL, Null, ^@U+0001SOH, Start of heading, ^A
U+0002STX, Start of text, ^BU+0003ETX, End of text, ^C
U+0004EOT, End of transmission, ^DU+0005ENQ, Enquiry, ^E
U+0006ACK, Acknowledge, ^FU+0007BEL, Bell, alarm, ^G
U+0008BS, Backspace, ^HU+0009HT, Horizontal tab, tab, ^I
U+000ALF, Line feed, linefeed, ^JU+000BVT, Vertical tab, vtab, ^K
U+000CFF, Form feed, formfeed, ^LU+000DCR, Carriage return, return, ^M
U+000ESO, Shift out, ^NU+000FSI, Shift in, ^O
U+0010DLE, Data link escape, ^PU+0011DC1, Device control 1, ^Q
U+0012DC2, Device control 2, ^RU+0013DC3, Device control 3, ^S
U+0014DC4, Device control 4, ^TU+0015NAK, Negative acknowledge, ^U
U+0016SYN, Synchronous idle, ^VU+0017ETB, End of transmission block, ^W
U+0018CAN, Cancel, ^XU+0019EM, End of medium, ^Y
U+001ASUB, Substitute, ^ZU+001BESC, Escape, ^[
U+001CFS, File separator, ^\U+001DGS, Group separator, ^]
U+001ERS, Record separator, ^^U+001FUS, Unit separator, ^_
U+0020SP, SpaceU+007FDEL, Delete, ^?
U+0080PAD, Padding characterU+0081HOP, High octet preset
U+0082BPH, Break permitted hereU+0083NBH, No break here
U+0084IND, IndexU+0085NEL, Next line
U+0086SSA, Start of selected areaU+0087ESA, End of selected area
U+0088HTS, Character tabulation setU+0089HTJ, Character tabulation with justification
U+008AVTS, Line tabulation setU+008BPLD, Partial line forward
U+008CPLU, Partial line backwardU+008DRI, Reverse line feed
U+008ESS2, Single-shift twoU+008FSS3, Single-shift three
U+0090DCS, Device control stringU+0091PU1, Private use one
U+0092PU2, Private use twoU+0093STS, Set transmit state
U+0094CCH, Cancel characterU+0095MW, Message waiting
U+0096SPA, Start of guarded areaU+0097EPA, End of guarded area
U+0098SOS, Start of stringU+0099SGCI, Single graphic character introducer
U+009ASCI, Single character introducerU+009BCSI, Control sequence introducer
U+009CST, String terminatorU+009DOSC, Operating system command
U+009EPM, Privacy messageU+009FAPC, Application program command

다음은 문자 리터럴의 예시입니다.

?a                      -- U+0061
?                       -- U+0020 (권장되지 않음)
?space                  -- U+0020 (권장됨)
?
                        -- U+000A (권장되지 않음)
?\n                     -- U+000A (권장됨, 또는 ?LF)
?:                      -- U+003A
?"RIGHT-TO-LEFT MARK"   -- U+200E
?'NuMBeR S\73GN'        -- U+0023
?\0                     -- U+0000
?\44032                 -- U+AC00
?\uD7A3                 -- U+D7A3
?\1234567               -- 에러 (?\123456, 7로 토큰화)
?\U12345678             -- 에러 (영역을 벗어난 유니코드 문자)

3.3.8 문자열 리터럴

문자열 리터럴은 naru.core.String 또는 naru.core.ByteSeq 형으로 평가되는 값입니다.

문자열(String)은 사람이 실제로 인식하는 문자들의 집합이며, 바이트 배열(ByteSeq)은 그런 문자열이 내부적으로 표기되는 이진 표기를 나타냅니다. 기본적으로 모든 입출력 작업은 바이트 배열로 이루어집니다.

STRING-LITERAL ::= SINGLE-STRING-LITERAL+
BYTESEQ-LITERAL ::= SINGLE-BYTESEQ-LITERAL+

SINGLE-STRING-LITERAL ::=
	BASIC-STRING-LITERAL
	RAW-STRING-LITERAL
	INTERPOLATING-STRING-LITERAL

SINGLE-BYTESEQ-LITERAL ::=
	BASIC-BYTESEQ-LITERAL
	RAW-BYTESEQ-LITERAL
	HEX-BYTESEQ-LITERAL

BASIC-STRING-LITERAL ::=
	QUOTE-OPENER (CHAR-SEQ - QUOTE-INVALID)* QUOTE-CLOSER
	QUOTE3-OPENER (CHAR-SEQ - QUOTE3-INVALID)* QUOTE3-CLOSER

RAW-STRING-LITERAL ::=
	[rR] QUOTE-OPENER (ANY - QUOTE-INVALID)* QUOTE-CLOSER
	[rR] QUOTE3-OPENER (ANY - QUOTE3-INVALID)* QUOTE3-CLOSER

INTERPOLATING-STRING-LITERAL ::=
	"#" QUOTE-OPENER (INTERP-CHAR-SEQ - QUOTE-INVALID)* QUOTE-CLOSER
	"#" QUOTE3-OPENER (INTERP-CHAR-SEQ - QUOTE3-INVALID)* QUOTE3-CLOSER

BASIC-BYTESEQ-LITERAL ::=
	[bB] QUOTE-OPENER (BYTE-SEQ - QUOTE-INVALID)* QUOTE-CLOSER
	[bB] QUOTE3-OPENER (BYTE-SEQ - QUOTE3-INVALID)* QUOTE3-CLOSER

RAW-BYTESEQ-LITERAL ::=
	[bB] [rR] QUOTE-OPENER (ANY - QUOTE-INVALID)* QUOTE-CLOSER
	[bB] [rR] QUOTE3-OPENER (ANY - QUOTE3-INVALID)* QUOTE3-CLOSER

HEX-BYTESEQ-LITERAL ::=
	(* [TBW] *)

HEX-BYTESEQ-SEQUENCE ::=
	WS? HEXA-BYTE-SEQ 
	[bB]? [xX] QUOTE-OPENER HEXA-BYTE-SEQUENCE QUOTE-CLOSER
	[bB]? [xX] QUOTE3-OPENER HEXA-BYTE-SEQUENCE QUOTE3-CLOSER

(* 세 문자는 모두 같아야 함. *)
QUOTE3-OPENER ::= QUOTE-OPENER QUOTE-OPENER QUOTE-OPENER
QUOTE3-CLOSER ::= QUOTE-CLOSER QUOTE-CLOSER QUOTE-CLOSER

QUOTE-INVALID ::= QUOTE-CLOSER | NEWLINE-CHAR
QUOTE3-INVALID ::= QUOTE3-CLOSER

CHAR-SEQ ::=
	[^ \ ]
	CHAR-ESCAPE-SEQ
	"\c{" [^{}]* "}"
	"\" (ANY - ([\abefnrstuUvx] | DECIMAL-DIGIT | QUOTE-OPENER | QUOTE-CLOSER))

INTERP-CHAR-SEQ ::=
	CHAR-SEQ
	"\#"
	"#" (variable-ref | tuple-form)

BYTE-SEQ ::=
	[^ \ ]
	"\" ("\" | QUOTE-OPENER | QUOTE-CLOSER)
	"\" DECIMAL-DIGIT+
	"\x" (HEXA-OCTET | "{" HEXADECIMAL-DIGIT+ "}")
	"\" (ANY - ([\x] | DECIMAL-DIGIT | QUOTE-OPENER | QUOTE-CLOSER))

문자열 리터럴은 크게 두 종류로 나뉩니다. 따옴표가 하나인 일반 문자열에는 개행 문자가 포함될 수 없으며, \n 등의 탈출열만을 쓸 수 있습니다. (하지만 \newline 탈출열은 포함될 수 있으며, 빈 문자열로 치환됩니다.) 따옴표가 세 개인 긴 문자열에는 개행 문자가 포함될 수 있습니다. 긴 문자열의 연속된 세 따옴표는 항상 같은 문자여야 합니다.

또한 문자열 리터럴은 앞에 붙는 접두사에 따라 여섯 종류로 나뉩니다. 따라서 총 12종류의 문자열 리터럴이 존재합니다. 모든 접두사는 대소문자를 구별하지 않습니다.

보통 문자열 및 바이트 배열은 탈출열을 포함할 수 있으며, 날 문자열 및 바이트 배열에는 어떤 탈출열도 들어 갈 수 없습니다. (즉 문자열은 입력된 그대로 나타납니다) 표현식을 확장하는 문자열(interpolating string)은 안에 들어 있는 표현식의 값이 문자열로 평가되어 치환됩니다. 16진수로 표현된 바이트 배열은 각 바이트를 16진수로 표현해야 합니다.

문자열 리터럴의 탈출열은 \로 시작하며, 문자 리터럴과 대부분 비슷하지만 일부가 변경되거나 추가되었습니다. 모든 탈출열들의 목록은 다음과 같습니다.

표 3.6 문자열 리터럴에서 사용할 수 있는 탈출열들의 목록
탈출열문자열에서의 의미바이트 배열에서의 의미
\newline공통: 뒤의 개행 문자를 무시
\#U+0023 (표현식을 확장할 때만)
\decimal10진법 코드 포인트10진법 바이트 값
\\U+005C5C16
\', \", …U+0027, U+0022, …2716, 2216, …
\aU+00070716
\bU+00080816
\c{name}이름에 해당하는 문자
\eU+001B1B16
\fU+000C0C16
\nU+000A0A16
\sU+00202016
\rU+000D0D16
\tU+00090916
\u????16진법 코드 포인트 (네 자리)
\U????????16진법 코드 포인트 (여덟 자리)
\x??16진법 코드 포인트 (두 자리)16진법 바이트 값 (두 자리)
\x{code}16진법 바이트 값
기타공통: 쓰여진 그대로 문자열에 입력됨 (예: "\q123" == r"\q123")

여기서 10진법 코드 포인트 및 바이트 값은 각각 올바른 유니코드 문자 및 바이트가 되는 한 최대한 많은 숫자를 읽습니다. (바이트의 경우 실제 자리수는 구현체에 따라 다를 수 있습니다.) 문자 이름은 안에 다른 탈출열이 들어 갈 수 없다는 점만 빼면 문자 리터럴에서 사용하는 이름과 동일합니다.

표현식을 확장하는 문자열에는 추가적으로 #asdf 또는 #(1 + 2) 형태의 탈출열이 존재합니다. 표현식은 평가된 뒤 naru.core.String 형으로 변환되어 원래 자리에 치환됩니다. 표현식에는 다른 문자열도 들어 갈 수 있으며, 변수 참조가 아닌 모든 표현식은 괄호로 묶어야 합니다. # 뒤에 변수 참조나 괄호가 오지 않는 경우 일반 문자열과 같이 처리합니다. 만약 # 뒤에 변수 참조나 괄호가 오지만 이를 치환하지 않고 싶다면, \# 탈출열을 사용해야 합니다. (이 탈출열은 표현식을 확장할 때만 유효합니다.)

16진수로 표현된 바이트 배열은… [TBD]

3.3.9 심볼 리터럴

심볼 리터럴은 naru.core.Symbol 형으로 평가되는 값입니다.

심볼은 다른 언어들에서 내재화된(interned) 문자열이라고 흔히 불리는 개념으로, 변수 이름과 같은 문자열은 흔히 쓰이기 때문에 비교를 편하게 하기 위하여 만들어진 개념입니다. 심볼을 별도의 자료형으로 두는 것은 이 문자열들이 보통 비교에만 사용되고, 사실상의 플래그 상수(O_CREAT 같은)와 같이 사용할 수도 있기 때문입니다.

SYMBOL-LITERAL ::= ":" SYMBOL-NAME

SYMBOL-NAME ::=
	IDENTIFIER
	BASIC-STRING-LITERAL

심볼 문법 :name은 루비에서 왔습니다.

심볼은 문자열의 특수한 형태로 같은 문자열이 여러 번 나타나는 경우가 많은 경우에 쓰입니다. 두 개의 같은 심볼은 같은 객체로 참조됩니다. 심볼은 보통 네임스페이스의 키값을 나타내기 위해 쓰입니다.

심볼은 식별자 앞에 공백 없이 :를 붙여서 만들어지거나, 공백 등을 포함할 경우 따옴표로 묶인 문자열을 붙여서 만들 수 있습니다. 여기에는 연산자 이름을 비롯한 특수한 식별자도 모두 쓰일 수 있습니다. 문자열을 사용할 경우 문자열은 탈출열을 처리하도록 설정됩니다.

3.3.10 패턴 리터럴

패턴 리터럴은 naru.core.Pattern 또는 naru.core.BytePattern 형으로 평가되는 값입니다.

패턴은 문자열 처리에 많이 쓰이며, 이런 처리가 빈번하게 등장하는 언어들(특히 펄)에서 흔히 문법의 일부로 자주 등장합니다. 나루는 일반 목적의 프로그래밍 언어를 지향하지만, 패턴이 실제로 많이 사용되며 패턴 문법이 바탕 언어의 문법과 많이 연관되어 있어 통합시키는 것이 이롭다는 판단으로 패턴 리터럴을 제공합니다.

바이트 패턴은 바이트 배열에 대한 패턴의 일반화입니다.

PATTERN-LITERAL ::= "/" PATTERN-EXPR "/" PATTERN-OPTION*
	(* [TBW: 바이트 패턴 b//, 16진 바이트 패턴 bx// or x//] *)

PATTERN-EXPR ::= PATTERN-ORDERED-ALT-EXPR

PATTERN-SEQUENCE ::= WS (PATTERN-ATOMIC-EXPR WS)+

PATTERN-ATOMIC-EXPR ::=
	PATTERN-LITERAL
	PATTERN-CHARACTER-CLASS
	PATTERN-ATOMIC-EXPR PATTERN-QUANTIFIER
	PATTERN-CAPTURING-GROUP
	PATTERN-NONCAPTURING-GROUP
	PATTERN-SPECIAL

패턴 문법은 펄, 루비, 펄 6의 문법을 상당 부분 변형시켜 만들어졌습니다. 주된 설계 목표는 기존 정규식 문법들에서 (?…)와 같이 너무 복잡하거나 이해하기 힘든 문법들을 간소화하는 데 있었습니다. (여기에 대해서는 래리 월[Larry Wall]이 펄 6을 설계할 때에도 지적한 바가 있습니다.)

나루의 패턴 리터럴은 항상 공백을 무시합니다. (기존 정규식에서 //x 옵션을 사용한 것과 비슷하다고 볼 수 있습니다.) 이렇게 한 이유는 지나치게 정규식이 짧아지면서 이해하기 힘들어지게 되는 현상을 다소 해소하기 위함입니다. 물론 기존과 같이 패턴에서 모든 공백을 없앨 수도 있습니다.

패턴과 바이트 패턴은 각각 특정한 종류의 문자열과 바이트 배열들을 명시하기 위한 특수한 문법입니다. 예를 들어, /asdf*/asd, asdf, asdff 등을 담고 있는 문자열의 집합을 나타냅니다.

패턴이 문자열에 대해 할 수 있는 작업은 다음과 같습니다. (바이트 배열도 유사합니다.)

여기서 볼 수 있듯이 모든 패턴 작업은 매칭된 문자열을 여러 가지 방법으로 다룬다 할 수 있습니다.

나루의 패턴 매칭은 기본적으로 정규 언어(regular language)를 기반으로, 문맥 독립 언어(context-free language)와 문맥 의존 언어(context-sensitive language)의 일부 특성을 추가한 것입니다. 일부 언어들은 이러한 패턴 매칭을 "정규식" 내지는 "정규 표현식"(regular expression)이라는 이름으로 부르지만 대부분 정규 언어보다 훨씬 넓은 언어를 표현할 수 있는 경우가 보통입니다.

정규 언어가 아닌 정규 표현식의 대표적인 예로 다음이 있습니다.

-- 다음 정규식은 문맥 독립 언어로 표현됨.
/ ((a+)) b \1 /

-- 다음 정규식들은 문맥 의존 언어로 표현됨.
/ ((a+)) b \1 b \1 /
/^ ({!->} (xx | ((xx+)) \1+) $)/    -- n이 소수일 때 "x"*n에 매칭됨

이런 이유로 일부 언어(펄 6 등)에서는 정규 표현식이라는 이름을 버리고 그 줄임말인 regex(p)와 같은 이름을 쓰기도 하지만, 나루에서는 좀 더 명확한 표현을 위해 패턴이라는 이름을 씁니다.

패턴 리터럴은 기본적으로 모든 공백을 무시하며, 주석도 쓰일 수 있습니다. 다만 패턴 옵션에 따라서 공백을 특수하게 처리하는 것도 가능합니다.

3.3.10a 패턴 옵션
PATTERN-OPTION ::=
	[iI]
	[nN]
	[wW]

패턴 뒤에는 다음과 같은 패턴 옵션이 붙을 수 있습니다. 모든 패턴 옵션은 대소문자를 구별하지 않습니다.

i (ignore case)

패턴에서 대소문자를 구별하지 않습니다.

다음 두 패턴은 동일한 의미입니다.

/ foo [bar] ({->} quu*x) /i
/ [Ff][Oo][Oo] [BbAaRr] ({->} [Qq][Uu][Uu]*[Xx]) /
n (explicit group name)
패턴의 모든 갈무리하는 그룹(capturing group)에 이름이 붙어 있지 않으면 에러를 발생시킵니다.
w (implicit whitespace)
패턴에 나타나는 하나 이상의 공백들의 연속을 \s*로 변환합니다.
3.3.10b 문자적 패턴
PATTERN-LITERAL ::=
	[^ \(){}?*+! ]

문자적 패턴은 주어진 그대로의 문자에 매칭하는 패턴입니다. 예를 들어 /a/라는 패턴은 a라는 문자에 매칭됩니다. 하지만 i와 같은 패턴 옵션이 쓰일 경우 문자적 패턴의 해석도 약간씩 달라질 수 있습니다.

3.3.10c 문자 집합
3.3.10d 한정자
PATTERN-QUANTIFIER ::=
	"?" "!"?
	"*" "!"?
	"+" "!"?
	"{" PATTERN-RANGE "}" "!"?

PATTERN-RANGE ::=
	SIMPLE-INTEGER-LITERAL
	range-operator SIMPLE-INTEGER-LITERAL
	SIMPLE-INTEGER-LITERAL range-operator
	SIMPLE-INTEGER-LITERAL range-operator SIMPLE-INTEGER-LITERAL

[TBD: longest-first/shortest-first 문법 변경]

한정자는 원자적 패턴 뒤에 붙어서 그 패턴이 반복될 수 있는 횟수를 지정합니다. 범위 한정자의 경우 ab 같은 숫자는 단순한 정수 리터럴로, 자리 구분자 _만 허용됩니다.

여기서 한정자에서 의미하는 '반복'은 같은 문자열의 반복이 아닌 패턴의 반복을 의미합니다. 예를 들어 /^(p|q)+$/ppppqqqq 뿐만 아니라 ppqqqpqp와 같은 문자열도 매칭시킬 수 있습니다.

3.3.10e 선택적 패턴
PATTERN-ORDERED-ALT-EXPR ::= PATTERN-ALT-EXPR ("||" PATTERN-ALT-EXPR)*

PATTERN-ALT-EXPR ::= PATTERN-SEQUENCE ("|" PATTERN-SEQUENCE)*

선택적 패턴은 | 또는 ||로 구분된 패턴들의 모음으로, 각 패턴들 중 하나에만 매칭될 경우 매칭되는 패턴입니다. 예를 들어 /a|b/는 a 또는 b가 들어 있는 문자열에 매칭됩니다.

|||의 차이는 패턴을 매칭시키는 방법에 있습니다. 각 선택 중 여러 개가 매칭 가능할 때, 전자는 그 중 해석 방법에 따라 아무 거나 매칭시킬 수 있지만 후자는 항상 가장 앞의 것을 우선한다는 차이가 있습니다. |||를 섞어 쓸 경우 후자가 우선순위가 더 낮습니다.

둘을 구분하는 이유는 다음과 같이 설명할 수 있습니다.

|의 경우, 예를 들어 문자열에서 해당 패턴이 매칭되는 가장 긴 부문자열을 찾을 경우 각 선택적 패턴에서 항상 처음으로 매칭되는 선택이 선택되지 않을 수도 있습니다.

||의 경우, 특정 선택에 대한 선호도를 반영할 수 있으며 부수 효과(side effect)가 있을 때 결과를 예측하기 쉽습니다.

3.3.10f 그룹
PATTERN-CAPTURING-GROUP ::=
	"((" PATTERN-CAPTURING-OPTION? PATTERN-EXPR "))"

PATTERN-NONCAPTURING-GROUP ::=
	"(" (PATTERN-NONCAPTURING-OPTION? PATTERN-EXPR - "(") ")"

그룹은 다른 패턴을 묶어서 하나의 원자적 패턴처럼 쓸 수 있도록 한 것입니다. 따라서 그룹 뒤에는 한정자와 같이 원자적 패턴에 사용되는 문법들이 붙을 수 있습니다. 그룹에는 두 종류가 있습니다.

각 그룹의 맨 앞에는 그룹 옵션이 올 수 있습니다. 그룹 옵션이 만약 나타난다면, 여는 괄호(((() 바로 뒤에 공백 없이 나타나야 합니다.

PATTERN-CAPTURING-OPTION ::= PATTERN-GROUP-NAME

PATTERN-GROUP-NAME ::= "{" SYMBOL-LITERAL "}"

갈무리하는 그룹에는 번호 또는 이름이 붙을 수 있습니다. 별도로 지정되지 않으면, 갈무리하는 그룹은 여는 괄호의 위치에 따라 순서대로 1부터 번호가 붙여집니다. 만약 {:name} 형태의 패턴 옵션이 주어지면, 해당 그룹에 대한 번호는 붙여지지 않고 대신 주어진 이름에 해당하는 심볼을 이름으로 사용합니다.

다음은 갈무리하는 그룹에 어떻게 번호나 이름이 붙는지 보여 주는 예시입니다.

/ ((a ((b)) ((c)) d)) /
--  ^^^^^^^^^^^^^^^ [1]
--     ^^^   ^^^
--     [2]   [3]

/ ((a (({:foo}b)) ((c)) d)) /
--  ^^^^^^^^^^^^^^^^^^^^^ [1]
--     ^^^^^^^^^   ^^^
--      [:foo]     [2]

한 패턴 안에서 그룹 이름은 중복될 수 없습니다. 갈무리하는 그룹이 한정자 등에 영향을 받아 여러 번 매칭될 경우, 마지막에 매칭된 문자열만이 기억됩니다.

3.3.10g 단언
PATTERN-NONCAPTURING-OPTION ::=
	"{" LEFT-ARROW "}"
	"{!" LEFT-ARROW "}"
	"{" RIGHT-ARROW "}"
	"{!" RIGHT-ARROW "}"
3.3.10h 탈출열
3.3.10i 특수 패턴
PATTERN-SPECIAL ::=
	"{!}"

3.4 전처리기 명령

전처리기의 입장에서 문장은 세 종류로 구분할 수 있습니다.

전처리기에서 블록은 블록을 시작하는 문법들 중 하나에서 시작하여 end에서 끝납니다. 따라서 변수의 영역(scope)이 서로 독립된 여러 블록이 전처리기에서는 하나의 블록으로 취급될 수 있습니다.

일부 전처리기 명령은 한 블록의 맨 앞에 나타나야 합니다. 이런 명령은 해당 블록 안에서만 효력을 가집니다.

statement ::= "use" preprocessor-directive

3.4.1 use naru 명령

preprocessor-directive ::=
	"naru" (ANY - STATEMENT-DELIM)*

use naru 명령은 이 코드가 나루 코드임을 나타내는 한 가지 방법입니다. 이 명령은 무시되며, 뒤에 붙는 모든 인자들도 후의 확장을 위하여 무시합니다.

이 명령은 각 블록의 맨 앞에만 나타날 수 있습니다.

3.4.2 use encoding 명령

preprocessor-directive ::=
	"encoding" STRING-LITERAL

use encoding 명령은 코드를 읽는 데 사용하는 현재 문자 인코딩을 바꾸는 데 사용합니다. 이 명령은 문장 경계 사이에 여러 번 나타날 수 있으며, 나타날 때마다 현재 인코딩이 바뀝니다.

인코딩 선언은 현재 인코딩으로 읽혀지며, 명령 뒤의 문장 구분자 직후부터 해당 인코딩이 적용됩니다. 다만 주석은 공백으로 처리되기 때문에 해당 명령과 같은 줄에 있는 주석들은 이전 인코딩이 적용됩니다.

다음은 이런 특성을 보여 주는 한 예입니다.

use encoding "utf-8"
-- 이 부분의 코드는 utf-8로 읽혀집니다.
use encoding "cp949";      -- 주석의 시작인 "--"부터 cp949로 읽혀집니다.
-- 아래 문장은 인코딩 선언을 포함하고 있지 않습니다.
print """use encoding "iso-8859-1";"""
use encoding "shift-jis"   -- 문장이 끝나지 않았으므로 여전히 cp949로 읽혀집니다.
                           -- 이 부분부터는 shift-jis로 읽혀집니다.

구현체는 소스 코드가 해석되는 환경에서 지원하는 모든 인코딩을 use encoding 명령에서 쓸 수 있도록 해야 합니다.

3.4.3 use operator 명령

preprocessor-directive ::=
	"operator" operator-decl ("," operator-decl)*

operator-decl ::=
	STRING-LITERAL+ "(" operator-decl-args ")"

operator-decl-args ::=
	SIMPLE-INTEGER-LITERAL "," SYMBOL-LITERAL "," SYMBOL-LITERAL ","?

[TBD: 완전히 재작성해야 함. 대략의 디자인은 use operator "\u2211", "(", singleton, ")", yield-1 = yield 정도로?]

use operator 전처리 명령은 연산자를 정의하는 용도로 쓰입니다. 이 명령은 파서에게 새로운 생성 문법을 동적으로 만들 것을 지시하며, 문법 분석기에게도 해당 연산자가 새로운 토큰을 필요로 한다는 것을 알립니다.

연산자는 문자열로 주어집니다. 만약 연산자가 여러 글자로 이루어져 있을 경우 공백(HORIZ-WHITESPACE로 정의되는)으로 토큰을 구분합니다. 한 예로, "+-"는 하나의 토큰으로 구성된 연산자지만 "+ -"는 두 개의 토큰으로 구성된 연산자입니다. 연산자가 여러 부분으로 구성되어 있을 경우 문자열을 여러 개 쓸 수 있습니다.

연산자 뒤에 함수 호출과 같은 문법으로 연산자의 성질을 정의합니다. 이 '가상의 함수'는 세 개의 고정된 인자를 가지며, 다음과 같이 정의됩니다.

fun (priority: Integer, type: Symbol, assoc: Symbol)

각 인자의 의미는 다음과 같습니다.

priority
연산자의 우선 순위를 나타냅니다. 우선 순위는 정수로 나타나며, 숫자가 작을 수록 더 늦게 결합하며, 클 수록 더 먼저 결합합니다. 기본 연산자들은 미리 정의된 우선 순위 값을 가지고 있습니다.
type
연산자의 주 분류를 나타냅니다. 전위 연산자(:prefix), 중위 연산자(:infix), 후위 연산자(:postfix), 중위 n항 연산자(:chained), 괄호 연산자(:closure), 전위 괄호 연산자(:prefix_closure), 중위 괄호 연산자(:infix_closure), 후위 괄호 연산자(:postfix_closure) 중 하나를 쓸 수 있습니다.
assoc
연산자의 결합 방법을 나타냅니다. [TBD]

연산자는 이미 존재하는 문법들과 서로 충돌할 수 없습니다. 예를 들어 리스트 안에서 다른 반복자 값을 펼치는 역할을 하는 \ 문법은 엄밀하게 말해 연산자가 아니지만 전위 \ 연산자와 충돌하기 때문에 전처리 명령에 지정할 수 없습니다.

이 전처리 명령은 해당 블록 안에서만 적용되며, 각 블록의 맨 앞에 나타나야 합니다.

3.4.4 use precision 명령

preprocessor-directive ::=
	"precision" SIMPLE-INTEGER-LITERAL

use precision 전처리 명령은 정확한 실수 리터럴의 기본 유효자리수를 결정합니다. 예를 들어서 기본 유효자리수가 소수점 아래 5자리라면, 3.143.14000과 똑같이 해석됩니다. 기본 유효자리수보다 더 많은 유효자리수를 가진 리터럴에는 영향을 미치지 않습니다.

유효자리수는 정수 n으로 주어지며, 10-n보다 큰 부정확도(uncertainty)를 가진 리터럴의 부정확도를 이 값으로 고정시키게 됩니다.

이 전처리 명령은 해당 블록 안에서만 적용되며, 각 블록의 맨 앞에 나타나야 합니다.

3.4.5 use pragma 명령

preprocessor-directive ::=
	"pragma" (ANY - STATEMENT-DELIM)*

[TBW]

4장: 문법

나루의 문법은 구문 분석 과정에서 얻어진 토큰들의 연속을 대상으로 합니다.

토큰에 대한 나루의 문법은 문맥 독립 문법(context-free grammar)으로 표현할 수 있으며, 재귀 하강(recursive descent) LL(1) 파서로 모호함 없이 해석할 수 있습니다. 다만 이 문서에서는 표현의 편의를 위하여 LL(k) 문법을 담고 있으며, 일부 문법은 토큰의 해석과 동시에 일어 나야 하는 경우도 있습니다.

4.1 수식 문법

수식은 다른 수식이나 리터럴들을 연산자로 묶어서 하나의 값으로 계산하는 문법 구조입니다. 수식은 다른 문장들에서 쓰일 수 있을 뿐만 아니라 부수 작용(side effect)을 가질 수 있기 때문에 홀로 문장으로 쓰일 수 있습니다.

statement ::= expression

수식 문법은 그 해석 순서에 따라 우선 순위를 가지고 있습니다. 예컨대 (3 + 4) * 5 형태의 수식에서 +는 본래 *보다 우선 순위가 낮기 때문에 괄호 없이는 *가 먼저 실행됩니다. 이 절에서는 우선 순위가 가장 높은 문법들부터 순서대로 설명합니다.

4.1.1 원자적 수식

원자적 수식은 가장 높은 우선 순위를 가지며, 리터럴과 같이 내부 구조가 수식으로 해석되지 않거나, 우선 순위가 낮은 다른 수식들을 묶어서 먼저 계산하기 위해서 쓰입니다.

4.1.1a 변수 참조
atom ::= variable-ref

variable-ref ::=
	IDENTIFIER
	"self"

변수를 수식에 사용할 수 있으며, 그 평가 결과는 해당 변수가 가리키는 값에 대한 참조입니다. 특수한 변수 self는 현재 코드가 실행되는 문맥에서 다루는 "자기 객체"에 대한 참조를 반환합니다.

4.1.1b 리터럴
atom ::= literal

모든 리터럴들은 수식에 사용될 수 있으며, 그 평가 결과는 리터럴이 가리키는 그 값 자체입니다.

4.1.1c 괄호로 묶은 수식
atom ::= tuple-form

tuple-form ::=
	"(" ")"
	"(" expression ")"

closure-form ::=
	"(" singleton-expression ")"

괄호는 수식들의 우선순위를 바꾸는 문법적인 역할을 하며, 순서쌍(tuple)을 생성하는 데도 쓰입니다. 순서쌍은 naru.core.Tuple[T,U,…] 형에 대한 문법입니다.

순서쌍과 단순 괄호는 콤마로 구분됩니다. 만약 괄호로 묶인 수식 안에 콤마가 들어 있다면 그 수식은 순서쌍이며, 아니면 단순히 우선순위만을 바꿉니다. 여기서 콤마는 수식의 맨 마지막에 나오는 여분의 콤마도 포함됩니다.

다음은 순서쌍 및 괄호 문법의 예시입니다. 여기서 볼 수 있듯이 길이가 1인 순서쌍은 수식 뒤에 콤마를 붙여야만 표기할 수 있습니다.

()              -- 빈 순서쌍
(3)             -- 단순 괄호
(3,)            -- 길이가 1인 순서쌍
(3, 4)          -- 길이가 2인 순서쌍
(3, 4, 5, 6,)   -- 길이가 4인 순서쌍

[TBD: singleton-tuple을 없앨까?]

4.1.1d 리스트 생성자
atom ::= list-form

list-form ::=
	"[" "]"
	"[" expression "]"
	"[" singleton-expression expander-form "]"

expander-form ::=
	"for" generator (STATEMENT-DELIM (generator | guard))*

guard ::= expression

리스트 생성자는 naru.core.List[T] 형에 대응하는 문법으로, 주어진 수식을 원소로 하는 리스트를 만들거나, 주어진 조건에 알맞는 원소들로 채워진 리스트를 만듭니다. 빈 리스트 생성자는 원소가 하나도 없는 리스트를 만듭니다.

원소를 나열해서 리스트를 생성할 경우 * 접두사를 사용하여 다른 리스트를 중간에 끼워 넣을 수 있습니다. 이런 동작은 콤마 연산자와 동일합니다.

리스트 생성자에 for로 시작하는 조건을 넣을 수 있습니다. 여기 들어 가는 조건은 for 문장의 동작과 유사하여 그 의미도 똑같지만, 생성자(generator)로 불리는 열거 가능한 기반 값 이외에도 해당 값이 만족해야 하는 별도의 조건(guard)을 지정할 수 있다는 점이 다릅니다. 이러한 리스트 생성자의 형태를 리스트 해석(list comprehension)이라 부릅니다.

예를 들어 다음의 리스트 해석은,

-- 모든 복소수 x + yi를 구하되, x와 y가 2의 거듭제곱인 것은 무시한다.
a = [x+y*1j for x <- 1..!10; not x & (x-1) == 0
                y <- 1..!10; not y & (y-1) == 0]

for 문장을 사용한 다음 코드와 같으며,

a = []
for x <- 1..!10
	if not x & (x-1) == 0
		for y <- 1..!10
			if not y & (y-1) == 0
				a.append(x+y*1j)
			end
		end
	end
end

다음과 같은 동일한 결과를 변수 a에 저장합니다.

a = [3+3j, 3+5j, 3+6j, 3+7j, 3+9j,
     5+3j, 5+5j, 5+6j, 5+7j, 5+9j,
     6+3j, 6+5j, 6+6j, 6+7j, 6+9j,
     7+3j, 7+5j, 7+6j, 7+7j, 7+9j,
     9+3j, 9+5j, 9+6j, 9+7j, 9+9j]

리스트 해석의 각 생성자 부분은 그 뒤에 있는 생성자 및 조건들에 새로운 지역 변수를 선언하며, 실제로 들어 갈 수식은 모든 생성자들의 지역 변수를 적용받습니다. 이들 지역 변수는 해당 리스트 해석에서만 유효합니다.

4.1.1e 집합 생성자
atom ::= set-form

set-form ::=
	EMPTY-SET
	"{" expression "}"
	"{" singleton-expression expander-form "}"

집합 생성자는 naru.core.Set[T] 형에 대응하는 문법으로, 주어진 수식을 원소로 하는 집합을 만들거나, 주어진 조건에 알맞는 원소들로 채워진 집합을 만듭니다. 빈 집합 생성자는 원소가 하나도 없는 집합을 만듭니다.

집합 생성자는 문법을 제외하고 리스트 생성자와 완전히 똑같이 작동합니다. 다만 리스트와는 달리 집합은 순서가 없다는 점이 다릅니다.

4.1.1f 연관 배열 생성자
atom ::= map-form

map-form ::=
	"{" "}"
	"{" map-item ("," map-item)* ","? "}"
	"{" pair-form expander-form "}"    (* -- =>만 허용? *)

map-item ::=
	pair-form
	flattening-prefix singleton-expression

pair-form ::=
	(IDENTIFIER | literal) ":" singleton-expression
	singleton-expression MAPS-TO singleton-expression

연관 배열 생성자는 naru.core.Map[T, U] 형에 대응하는 문법으로, 주어진 수식을 키와 값으로 하는 연관 배열을 만들거나, 주어진 조건에 알맞는 키와 값들로 채워진 연관 배열을 만듭니다. 빈 연관 배열 생성자는 키와 값이 하나도 없는 연관 배열을 만듭니다.

연관 배열 생성자는 각 원소 별로 키와 값을 나타내는 수식이 따로 있다는 점에서 리스트 생성자나 집합 생성자와 다릅니다. 키와 값은 쌍으로 쓰여지며 기본적으로 key => value 문법을 사용하지만, 콜론을 사용하여 key: value 문법을 쓸 경우 key는 심볼이나 정수 등으로 해석됩니다.

예를 들어, 다음의 두 연관 배열은 동일합니다.

{1: 2, 3j: 4j, 5r6: 7r8, 9.012: 3.456, 7.89f: 0.12f,
	nil: nil, 'some': "thing", anything_else: "symbol"}
{1 => 2, 3j => 4j, 5r6 => 7r8, 9.012 => 3.456, 7.89f => 0.12f,
	nil => nil, 'some' => "thing", :anything_else => "symbol"}

4.1.2 후위 괄호 수식

postfix-closure-expr ::=
	atom
	postfix-closure-expr postfix-closure

후위 괄호 수식은 원자적 수식 뒤에 바로 달라 붙는 연산자들을 담고 있으며, 원자적 수식을 빼고 가장 우선순위가 높습니다.

4.1.2a 멤버 참조
postfix-closure ::= member-reference

member-reference ::=
	"." IDENTIFIER
	"." "class"
	"." "self"

[TBW]

4.1.2b 원소 참조
postfix-closure ::= index-reference

index-reference ::=
	"[" "]"
	"[" expression "]"

원소 참조 문법은 리스트나 문자열의 원소를 참조하기 위한 문법입니다.

참조하고자 하는 키 값은 원자적 수식 뒤에 []로 묶어서 명시합니다. 이 키 값은 흔히 순번(숫자)나 연관된 키 값, 범위 형식 등이 될 수 있으며, 여러 인자를 주기 위하여 콤마 연산자를 쓸 수도 있습니다. 참조하고자 하는 키 값이 없는, 즉 []와 같은 문법도 허용됩니다.

다음은 모두 올바른 원소 참조 문법입니다.

foo[1]
foo[3..!9]
foo[/bar/i]
foo["bar"]
foo[true, 42.195]         -- foo[(true, 42.195)]와는 다름
4.1.2c 함수 호출
postfix-closure ::= call-expr

call-expr ::=
	"(" ")"
	"(" argument-expr ("," argument-expr)* "," ")"

argument-expr ::=
	flattening-prefix singleton-expression
	pair-form
	colon-pair-form
	singleton-expression

[TBW]

4.1.3 상수화 수식

constantize-expr ::=
	postfix-closure-expr
	"const" postfix-closure-expr

상수화 수식은 주어진 수식을 상수화합니다. 상수화된 값은 원래 값의 상수 복사로, 원래 값에는 영향을 미치지 않습니다. 만약 값이 이미 상수라면 원래 값을 그대로 반환합니다. [TBW]

4.1.4 거듭제곱 수식

exponentation-expr ::=
	constantize-expr
	constantize-expr exponentation-operator prefix-unary-expr

exponentation-operator ::=
	"**"

거듭제곱 연산자 **는 주어진 숫자들의 거듭제곱을 구합니다. 예를 들어 2**3는 8로 평가되며, 2.0f**0.5f는 1.4142…f로 평가됩니다. 거듭제곱 연산의 결과형은 값에 따라 다른데, 만약 지수가 음수이면 항상 실수형으로 변환되며, 아니면 연산의 밑과 같은 형을 가집니다.

거듭제곱 연산자는 다른 대부분의 연산자와 달리 오른쪽에서 왼쪽으로 결합합니다. 예를 들어 2**3**42**(3**4)로 해석되며 (2**3)**4와 다릅니다.

(2**3)**42**(3*4)와 같습니다.

4.1.5 전위 단항 연산자 수식

prefix-unary-expr ::=
	exponentation-expr
	prefix-unary-operator prefix-unary-expr

전위 단항 연산자 수식은 거듭제곱 수식 다음으로 결합하는 연산자들을 포함합니다.

4.1.5a 산술 단항 연산자
prefix-unary-operator ::=
	"+"
	MINUS

부호 유지 연산자 +는 피연산자의 부호를 바꾸지 않고 반환합니다. 일반적으로 이 연산자는 의미가 없지만, 일부 클래스의 경우 피연산자의 정규화를 강제하는 등에 쓰일 수 있습니다.

부호 반전 연산자 -는 피연산자의 부호를 바꾸어 반환합니다. 기본 자료형에서 이 연산자는 -1로 곱한 것과 같은 효과를 냅니다.

4.1.5b 비트 단항 연산자
prefix-unary-operator ::=
	"~" | [\u00ac]

비트 반전 연산자 ~는 피연산자를 비트 문자열로 보아 모든 비트를 1에서 0으로, 0에서 1로 뒤집은 결과를 반환합니다. 이 연산자는 정수에만 적용할 수 있습니다.

나루는 기본 정수형이 2의 보수 형태로 저장된다고 가정하기 때문에, 정수 n에 대해서 ~n-n-1과 같습니다.

4.1.6 곱셈적 수식

multiplicative-expr ::=
	prefix-unary-expr
	multiplicative-expr multiplicative-operator prefix-unary-expr

곱셈적 수식은 곱셈, 나눗셈과 같은 산술 이항 연산자들을 포함합니다.

4.1.6a 곱셈 연산자
multiplicative-operator ::=
	TIMES

곱셈 연산자 *는 두 피연산자를 곱한 결과를 반환합니다. 이 연산자는 반복 연산자 ~~와는 다릅니다.

4.1.6b 나눗셈 연산자
multiplicative-operator ::=
	DIVIDES

나눗셈 연산자 /는 왼쪽 피연산자를 오른쪽 피연산자로 나눕니다. 이 연산은 항상 정확하게 행해집니다. 즉, 두 피연산자가 모두 정수이면 결과는 정수가 아닌 유리수가 됩니다. 정수 나눗셈(몫)을 위해서는 // 연산자를 씁니다.

4.1.6c 정수 나눗셈 연산자
multiplicative-operator ::=
	"//"

정수 나눗셈 연산자 //는 왼쪽 피연산자를 오른쪽 피연산자로 나눈 몫을 반환합니다. 이 연산자는 몫 및 나머지 연산자 /%의 몫 부분으로 정의되어 있습니다.

4.1.6d 나머지 연산자
multiplicative-operator ::=
	"%"

나머지 연산자 %는 왼쪽 피연산자를 오른쪽 피연산자로 나눈 나머지를 반환합니다. 이 연산자는 몫 및 나머지 연산자 /%의 나머지 부분으로 정의되어 있습니다.

4.1.6e 몫 및 나머지 연산자
multiplicative-operator ::=
	"/%"

몫 및 나머지 연산자 /%는 왼쪽 피연산자를 오른쪽 피연산자로 나눈 몫과 나머지의 순서쌍을 반환합니다. 이 연산자는 정수 나눗셈 연산자 //나머지 연산자 %를 정의하는 데 쓰입니다.

어떤 수 xy로 나눈 몫과 나머지는 정수와 유리수, 그리고 실수에 대해 정의되어 있으며, 다음 조건을 만족하는 유일한 순서쌍입니다. (다만 나눔수 y는 0이 될 수 없습니다.)

  1. x//y과 나머지 x%yx = (x//y) * y + (x%y)를 만족합니다.
  2. 나머지는 항상 0보다 크거나 같으며 y의 절대값보다 작습니다. (다만 부정확한 실수의 경우 정확도 때문에 나머지가 y과 같을 수도 있습니다.)

따라서 몫의 부호는 x의 부호와 같으며, 나머지는 항상 양수가 됩니다. 또한 임의의 xy에 대해서 x/%(-y) == (x//(-y), x%(-y))가 항상 성립합니다.

표 3.6 몫과 나머지의 예시
나눠지는 수 x나눔수 yx//y나머지 x%y
42582
-425-93
42-5-82
-42-593
7.32.43.00.1

복소수에서는 몫과 나머지가 정의되지 않습니다. 따라서 //, %, /% 연산자는 복소수를 대상으로 쓸 수 없습니다.

4.1.7 덧셈적 수식

additive-expr ::=
	multiplicative-expr
	additive-expr additive-operator multiplicative-expr

덧셈적 수식은 덧셈, 뺄셈과 같은 산술 이항 연산자들을 포함합니다.

4.1.7a 덧셈 연산자
additive-operator ::=
	"+"

덧셈 연산자 +는 피연산자를 더합니다. 이 연산자는 문자열을 합치는 데 쓸 수 없으며, 이 용도로는 결합 연산자 ~를 씁니다.

4.1.7b 뺄셈 연산자
additive-operator ::=
	MINUS

뺄셈 연산자 -는 왼쪽 피연산자로부터 오른쪽 피연산자를 뺀 결과를 반환합니다.

4.1.8 비트 시프트 수식

bitwise-shift-expr ::=
	additive-expr
	bitwise-shift-expr bitwise-shift-operator additive-expr

bitwise-shift-operator ::=
	"<<"
	">>"

비트 시프트 연산자 <<>>는 왼쪽 피연산자를 비트열로 보아 오른쪽 피연산자에 쓰인 만큼 비트열을 왼쪽 또는 오른쪽으로 이동합니다. 오른쪽 피연산자는 항상 음이 아닌 정수여야 합니다. 정수를 오른쪽 시프트할 때 소수점 아래로 넘어가는 비트들은 절삭됩니다.

일부 프로그래밍 언어는 오른쪽 비트 시프트 연산자를 부호를 유지하는지의 여부에 따라 >>>>>로 나누지만, 나루의 정수형에는 범위 제한이 없기 때문에 이런 구분은 없습니다. 나루의 오른쪽 비트 시프트 연산자는 항상 부호를 유지하며, 부호를 무시하기 위해서는 비트 논리곱 연산자 &를 사용해야 합니다.

의미상으로, 기본 자료형에 대해서 a << ba >> b는 각각 a * 2**ba // 2**b와 같습니다.

4.1.9 비트 논리곱 수식

bitwise-and-expr ::=
	bitwise-shift-expr
	bitwise-and-expr bitwise-and-operator bitwise-shift-expr

bitwise-and-operator ::=
	"&"

비트 논리곱 연산자 &는 피연산자를 비트열로 보고 서로 대응시켜, 두 비트가 모두 1이면 결과에 1을, 아니면 0을 설정합니다.

나루의 정수는 한 쪽으로 무한한 비트열로 처리되며, 이 비트열은 음수의 경우 1, 양수의 경우 0으로 채워져 있습니다. 따라서 두 피연산자가 모두 음수이면 비트 논리곱의 결과는 음수이고, 아니면 양수가 됩니다.

4.1.10 비트 배타적 논리합 수식

bitwise-xor-expr ::=
	bitwise-and-expr
	bitwise-xor-expr bitwise-xor-operator bitwise-and-expr

bitwise-xor-operator ::=
	"^"

비트 배타적 논리합 연산자 ^는 피연산자를 비트열로 보고 서로 대응시켜, 두 비트가 서로 같으면 결과에 0을, 아니면 1을 설정합니다.

나루의 정수는 한 쪽으로 무한한 비트열로 처리됩니다. 따라서 두 피연산자의 부호가 서로 같다면 비트 배타적 논리합의 결과는 양수이고, 아니면 음수가 됩니다.

4.1.11 비트 논리합 수식

bitwise-or-expr ::=
	bitwise-xor-expr
	bitwise-or-expr bitwise-or-operator bitwise-xor-expr

bitwise-or-operator ::=
	"|"

비트 논리합 연산자 |는 피연산자를 비트열로 보고 서로 대응시켜, 두 비트가 모두 0이면 결과에 0을, 아니면 1을 설정합니다.

나루의 정수는 한 쪽으로 무한한 비트열로 처리됩니다. 따라서 두 피연산자가 하나라도 음수이면 비트 논리합의 결과는 음수이고, 아니면 양수가 됩니다.

4.1.12 범위 형식

range-expr ::=
	bitwise-or-expr
	range-operator bitwise-or-expr
	bitwise-or-expr range-operator
	bitwise-or-expr range-operator bitwise-or-expr

range-operator ::=
	".."
	"..!"

범위 형식은 naru.core.Range[T] 형에 대한 생성자로, 왼쪽 피연산자부터 시작해서 오른쪽 피연산자로 끝나는 범위를 나타냅니다. ..!..와 동일하지만 오른쪽 피연산자를 포함하지 않는 범위를 나타냅니다. 각 피연산자가 생략되면 nil로 가정합니다.

두 문법은 각각 다음과 같은 다른 문법으로 쓸 수 있습니다.

a .. b  == naru.core.Range(a, b, :inclusive)
a ..! b == naru.core.Range(a, b, :exclusive)

범위 형식은 두 인자가 같은 공통 형으로 변환될 수 있을 것을 요구하고 있기 때문에 모든 범위 형식이 올바르지는 않습니다.

4.1.13 반복 연산자

repeat-expr ::=
	range-expr (repeat-operator range-expr)*

repeat-operator ::=
	"~~"

반복 연산자 ~~는 주어진 문자열이나 리스트와 같은 피연산자를 지정된 횟수만큼 반복합니다. 횟수는 항상 오른쪽 피연산자로 주어져야 하며, 음이 아닌 정수로 평가되어야 합니다. 횟수가 0이면 결과는 빈 리스트나 빈 문자열 등이 됩니다.

만약 두 피연산자가 모두 문자열이나 리스트 등일 경우, 반복 연산자는 카테시안 곱(Cartesian product)을 반환합니다. 예를 들어 [1, 2] ~~ [3, 4][(1,3), (1,4), (2,3), (2,4)]와 같으며, 리스트 해석 [(a,b) for a <- [1, 2]; b <- [3, 4]]과 동일합니다. 카테시안 곱은 여러 개의 피연산자의 경우에도 적용 가능하며 피연산자 개수와 같은 길이의 순서쌍을 만들어 냅니다.

반복 연산자는 결합 순서가 없는 다항 연산자이며, 항상 위에서 제시한 둘 중 한 가지 경우에만 해당해야 합니다. 예를 들어 [1, 2] ~~ [3, 4] ~~ 5와 같은 식은 오류로, [1, 2] ~~ ([3, 4] ~~ 5) 또는 ([1, 2] ~~ [3, 4]) ~~ 5로 쓰여야 합니다.

4.1.14 결합 연산자

concat-expr ::=
	repeat-expr
	concat-expr concat-operator repeat-expr

concat-operator ::=
	"~"

결합 연산자 ~는 문자열이나 리스트 등을 연결하는 데 사용합니다. 이 동작은 산술적으로 두 값을 더하는 덧셈 연산자 +와는 구분됩니다.

4.1.15 산술 비교 연산자

comparison-expr ::=
	concat-expr (comparison-operator concat-expr)*

comparison-operator ::=
	"!<" | [\u226e]
	"!<="
	"!<>"
	"!<>="
	NOT-EQUAL
	"!>" | [\u226f]
	"!>="
	"<"
	LESS-OR-EQUAL
	"<>"
	"<>="
	"=="
	">"
	GREATER-OR-EQUAL

산술 비교 연산자들은 수치형을 산술적으로 비교하는 데 쓰입니다. NaN과 같은 특수한 값의 존재 때문에 나루에는 14종류의 산술 비교 연산자가 존재하며, 고급 비교 연산자 <=>의 결과에 따라 다음과 같은 결과를 반환합니다.

표 3.7 산술 비교 연산자의 반환값
연산자a <=> b의 결과이름
-10+1nil
!<truefalsetruetrue비약소 비교
!<=truefalsefalsetrue비약소 및 비동등 비교
!<>falsefalsetruetrue비부등 비교
!<>=falsefalsefalsetrue비교 불가능
!=truetruefalsetrue비동등 비교
!>falsetruetruetrue비강대 비교
!>=falsetruefalsetrue비강대 및 비동등 비교
<falsetruefalsefalse*약소 비교
<=falsetruetruefalse*약소 또는 동등 비교
<>truetruefalsefalse*부등 비교
<>=truetruetruefalse*비교 가능
==falsefalsetruefalse동등 비교
>truefalsefalsefalse*강대 비교
>=truefalsetruefalse*강대 또는 동등 비교

여기서 별표(*)가 붙은 값들은 현재 부동 소숫점 환경에 따라서 예외를 발생시킬 수도 있고 그냥 값을 반환할 수도 있음을 의미합니다. 동등/비동등 비교 연산자 ==/!=는 절대로 예외를 발생시키지 않음을 참고하십시오.

산술 비교 연산자들은 여럿이 결합할 수 있습니다. 예를 들어 op1op2가 산술 비교 연산자일 때, a op1 b op2 c는 각 피연산자가 한 번씩만 연산된다는 점만 제외하면 a op1 b && b op2 c와 동일합니다.

4.1.16 고급 비교 연산자

rich-comparison-expr ::=
	comparison-expr
	comparison-expr rich-comparison-operator comparison-expr

rich-comparison-operator ::=
	"<=>"

고급 비교 연산자 <=>는 수치형을 산술적으로 비교한 결과를 반환합니다. 이 연산자는 결합 방향이 없으며 따라서 a <=> b <=> c와 같은 문법은 지원하지 않습니다. 반환값은 다음과 같이 정의됩니다.

부정확한 실수형의 경우 그 정의상 +0과 -0은 동일하기 때문에 +0f <=> -0f와 같은 코드는 0을 반환합니다.

4.1.17 객체 비교 연산자

identity-expr ::=
	rich-comparison-expr (identity-operator rich-comparison-expr)*

identity-operator ::=
	"is"
	"is" "not"

객체 비교 연산자 is는 두 피연산자가 가리키는 객체가 동일한 객체인지 확인합니다. 동일한 객체는 내부 상태를 공유하며, 한 쪽이 바뀌면 다른 쪽도 바뀌는 성질을 가지고 있습니다. (하지만 이런 성질을 가진 객체가 모두 동일하지는 않습니다.) is not 연산자는 is 연산자의 반대로 정의됩니다.

여러 개의 객체 비교 연산자를 이어 쓸 수 있으며, 이는 산술 비교 연산자와 동일합니다. 예를 들어 a is b is not c는 각 피연산자가 한 번씩만 평가되는 걸 제외하면 a is b && b is not