리더

리더 (The Reader)

Racket의 리더는 재귀 하강 파서(recursive-descent parser)로, 읽기 테이블(readtable)과 다양한 다른 파라미터로 구성할 수 있어요. 이 절은 기본 읽기 테이블을 사용할 때의 리더 파싱을 설명해요.

출처: Racket Reference

본문

1.3 리더

Racket의 리더는 재귀 하강 파서로, 읽기 테이블(readtable)과 다양한 다른 파라미터로 구성할 수 있어요. 이 절은 기본 읽기 테이블을 사용할 때의 리더 파싱을 설명해요.

스트림에서 읽으면 하나의 데이터(datum)가 산출돼요. 결과 데이터가 복합 값이면, 데이터를 읽기 위해 보통 리더가 구성 데이터를 읽도록 재귀적으로 자신을 호출해야 해요.

리더는 두 가지 모드 중 하나로 호출될 수 있어요: read 모드 또는 read-syntax 모드. read-syntax 모드에서 결과는 항상 문법 객체(syntax object)이며, read 모드가 산출하는 데이터 주위에 소스 위치와 (처음엔 빈) 어휘 정보를 감쌉니다. 페어, 벡터, 박스의 경우 내용도 재귀적으로 문법 객체로 감싸져요. 달리 명시되지 않으면, 이 절은 read 모드에서의 리더 동작을 설명하며, read-syntax 모드는 최종 결과를 감싸는 것만 제외하면 동일해요.

읽기는 유니코드 문자로 정의돼요. 바이트 스트림이 문자 스트림으로 어떻게 변환되는지는 포트(Ports)를 보세요.

리더가 read-syntax 모드에서 산출한 심볼, 키워드, 문자열, 바이트 문자열, regexp, 문자, 숫자는 인턴(intern)돼요. 즉 read-syntax의 결과에서 그런 값들은 equal?일 때 항상 eq?라는 뜻이에요(같은 호출이든 다른 호출이든). 심볼과 키워드는 readread-syntax 모드 둘 다에서 인턴돼요. 인용된 값이 쓰여졌다 다시 읽히는 컴파일된 코드에 있을 때(컴파일된 코드 출력(Printing Compiled Code) 참고), 코드를 읽을 때는 문자열과 바이트 문자열만 인턴돼요. 인턴된 값을 place 채널로 보내면 받는 place에서 인턴된 값을 반드시 만들어 내지는 않아요. datum-intern-literaldatum->syntax도 함께 보세요.

인턴된 값은 리더의 내부 테이블에 약하게(weakly)만 보유되므로, 다른 방식으로 더 이상 도달할 수 없으면 가비지 컬렉션될 수 있어요. 이 약함은 eq?, eqv?, equal? 테스트 결과에 절대 영향을 주지 않지만, 인턴된 값은 약한 박스(Weak Boxes 참고)에 넣거나, 약한 해시 테이블의 키(해시 테이블 참고)로, 또는 ephemeron 키(Ephemerons 참고)로 사용되면 사라질 수 있어요.

1.3.1 구분자와 디스패치 (Delimiters and Dispatch)

공백과 BOM 문자와 함께, 다음 문자들은 구분자(delimiter)예요.

( ) [ ] { } " , ' `
;

다른 문자로 시작하는 구분된 수열은 보통 심볼, 숫자, 또는 extflonum으로 파싱되지만, 몇몇 비구분자 문자가 특별한 역할을 해요.

  • #는 구분된 수열에서 초기 문자로 특별한 의미를 가져요. 그 의미는 뒤따르는 문자에 따라 달라져요(아래 참고).
  • |는 구분된 수열에 그대로(verbatim) 포함될 문자 하위 수열을 시작해요(즉 구분자로 취급되지 않고, 대소문자 비구분이 활성화되어 있어도 대소문자 접기(case-folding)가 일어나지 않음). 하위 수열은 또 다른 |로 끝나며, 초기 |와 종결 |는 하위 수열의 일부가 아니에요.
  • | 쌍 밖에서의 \는 다음 문자를 구분된 수열에 그대로 포함시켜요.

더 정확히, 공백과 #\uFEFF BOM 문자를 건너뛴 뒤, 리더는 입력 스트림의 다음 문자(들)에 따라 다음과 같이 디스패치해요.

  • ` — 페어나 리스트를 시작해요. 페어와 리스트 읽기(Reading Pairs and Lists) 참고.
  • `[` — 페어나 리스트를 시작해요.
  • `{` — 페어나 리스트를 시작해요.
  • `)``(`와 일치하거나 exn:fail:read를 발생시켜요.
  • `]``[`와 일치하거나 exn:fail:read를 발생시켜요.
  • `}``{`와 일치하거나 exn:fail:read를 발생시켜요.
  • `"` — 문자열을 시작해요. 문자열 읽기 참고.
  • `'` — 인용(quote)을 시작해요. 인용 읽기(Reading Quotes) 참고.
  • ` `` — 준인용(quasiquote)을 시작해요.
  • `,` — [스플라이스] 언인용(unquote)을 시작해요.
  • `;` — 라인 주석을 시작해요. 주석 읽기(Reading Comments) 참고.
  • `#t` 또는 `#T` — 참. 불리언 읽기(Reading Booleans) 참고.
  • `#f` 또는 `#F` — 거짓.
  • `#(` — 벡터를 시작해요. 벡터 읽기(Reading Vectors) 참고.
  • `#[` — 벡터를 시작해요.
  • `#{` — 벡터를 시작해요.
  • `#fl(` 또는 `#Fl(` — flvector를 시작해요.
  • `#fl[` 또는 `#Fl[` — flvector를 시작해요.
  • `#fl{` 또는 `#Fl{` — flvector를 시작해요.
  • `#fx(` 또는 `#Fx(` — fxvector를 시작해요.
  • `#fx[` 또는 `#Fx[` — fxvector를 시작해요.
  • `#fx{` 또는 `#Fx{` — fxvector를 시작해요.
  • `#s(` — 구조체 리터럴을 시작해요. 구조체 읽기(Reading Structures) 참고.
  • `#s[` — 구조체 리터럴을 시작해요.
  • `#s{` — 구조체 리터럴을 시작해요.
  • `#\` — 문자를 시작해요. 문자 읽기(Reading Characters) 참고.
  • `#"` — 바이트 문자열을 시작해요.
  • `#%` — 심볼을 시작해요. 심볼 읽기(Reading Symbols) 참고.
  • `#:` — 키워드를 시작해요. 키워드 읽기(Reading Keywords) 참고.
  • `#&` — 박스를 시작해요. 박스 읽기(Reading Boxes) 참고.
  • `#|` — 블록 주석을 시작해요.
  • `#;` — S-표현식 주석을 시작해요.
  • `#'` — 문법 인용(syntax quote)을 시작해요.
  • `#!` — 라인 주석을 시작해요.
  • `#!/` — 라인 주석을 시작해요.
  • `#!` — 리더 확장을 시작할 수 있어요. 확장을 통한 읽기 참고.
  • `#` — 문법 준인용(quasisyntax)을 시작해요.
  • `#,` — 문법 [스플라이스] 언인용(unsyntax)을 시작해요.
  • `#~` — 컴파일된 코드를 시작해요. 컴파일된 코드 출력 참고.
  • `#i` 또는 `#I` — 숫자를 시작해요. 숫자 읽기(Reading Numbers) 참고.
  • `#e` 또는 `#E` — 숫자를 시작해요.
  • `#x` 또는 `#X` — 숫자나 extflonum을 시작해요.
  • `#o` 또는 `#O` — 숫자나 extflonum을 시작해요.
  • `#d` 또는 `#D` — 숫자나 extflonum을 시작해요.
  • `#b` 또는 `#B` — 숫자나 extflonum을 시작해요.
  • `#<<` — 문자열을 시작해요.
  • `#rx` — 정규 표현식을 시작해요. 정규 표현식 읽기 참고.
  • `#px` — 정규 표현식을 시작해요.
  • `#ci`, `#cI`, `#Ci`, 또는 `#CI` — 대소문자 구분을 전환해요. 심볼 읽기 참고.
  • `#cs`, `#cS`, `#Cs`, 또는 `#CS` — 대소문자 구분을 전환해요.
  • `#hash` — 해시 테이블을 시작해요. 해시 테이블 읽기(Reading Hash Tables) 참고.
  • `#reader` — 리더 확장 사용을 시작해요. 확장을 통한 읽기 참고.
  • `#lang` — 리더 확장 사용을 시작해요.
  • `#`‹digit10›+`(` — 벡터를 시작해요.
  • `#`‹digit10›+`[` — 벡터를 시작해요.
  • `#`‹digit10›+`{` — 벡터를 시작해요.
  • `#fl`‹digit10›+`(` — flvector를 시작해요.
  • `#fl`‹digit10›+`[` — flvector를 시작해요.
  • `#fl`‹digit10›+`{` — flvector를 시작해요.
  • `#Fl`‹digit10›+`(` — flvector를 시작해요.
  • `#Fl`‹digit10›+`[` — flvector를 시작해요.
  • `#Fl`‹digit10›+`{` — flvector를 시작해요.
  • `#fx`‹digit10›+`(` — fxvector를 시작해요.
  • `#fx`‹digit10›+`[` — fxvector를 시작해요.
  • `#fx`‹digit10›+`{` — fxvector를 시작해요.
  • `#Fx`‹digit10›+`(` — fxvector를 시작해요.
  • `#Fx`‹digit10›+`[` — fxvector를 시작해요.
  • `#Fx`‹digit10›+`{` — fxvector를 시작해요.
  • `#`‹digit10›{1,8}`=` — 그래프 태그를 바인딩해요. 그래프 구조 읽기 참고.
  • `#`‹digit10›{1,8}`#` — 그래프 태그를 사용해요.
  • 그 외 — 심볼을 시작해요.

package base 7.8.0.9에서 변경: 주석이 허용되는 곳과 같은 위치에서 BOM 문자가 공백처럼 취급되도록 BOM 문자 처리 변경.

1.3.2 심볼 읽기 (Reading Symbols)

Racket 가이드의 "심볼(Symbols)" 절이 심볼 문법을 소개해요.

구분자나 #로 시작하지 않는 수열은 심볼, 숫자(숫자 읽기), 또는 extflonum(extflonum 읽기(Reading Extflonums))으로 파싱돼요. 단, . 단독은 심볼이나 숫자로 절대 파싱되지 않아요(read-accept-dot 파라미터가 #f로 설정되지 않은 한). 성공적인 숫자나 extflonum 파싱은 심볼 파싱보다 우선해요. #%도 심볼을 시작해요. 결과 심볼은 인턴돼요. 심볼 파싱에서 |\에 대한 정보는 구분자와 디스패치 시작 부분을 보세요.

read-case-sensitive 파라미터가 #f로 설정되면, 수열에서 |\로 인용되지 않은 문자들이 먼저 대소문자 정규화돼요. 리더가 #ci, #CI, #Ci, #cI를 만나면 대소문자 비구분 모드로 다음 데이터를 재귀적으로 읽어요. 리더가 #cs, #CS, #Cs, #cS를 만나면 대소문자 구분 모드로 다음 데이터를 재귀적으로 읽어요.

예시:

  • `Apple`(string->symbol "Apple")과 같게 읽힘
  • `Ap#ple`(string->symbol "Ap#ple")과 같게 읽힘
  • `Ap ple`(string->symbol "Ap")과 같게 읽힘
  • `Ap| |ple`(string->symbol "Ap ple")과 같게 읽힘
  • `Ap\ ple`(string->symbol "Ap ple")과 같게 읽힘
  • `#ci Apple`(string->symbol "apple")과 같게 읽힘
  • `#ci |A|pple`(string->symbol "Apple")과 같게 읽힘
  • `#ci \Apple`(string->symbol "Apple")과 같게 읽힘
  • `#ci#cs Apple`(string->symbol "Apple")과 같게 읽힘
  • `#%Apple`(string->symbol "#%Apple")과 같게 읽힘

1.3.3 숫자 읽기 (Reading Numbers)

Racket 가이드의 "숫자(Numbers)" 절이 숫자 문법을 소개해요.

구분자로 시작하지 않는 수열은, ‹number10›(십진)에 대해 다음 문법을 대소문자 무시로 일치하면 숫자로 파싱돼요. 여기서 n은 문법의 메타-메타 변수예요. 결과 숫자는 read-syntax 모드에서 인턴돼요.

숫자는 선택적으로 정확도 지정자 #e(정확) 또는 #i(부정확)로 접두사될 수 있어요. 이는 정확한 숫자나 부정확한 숫자로 파싱함을 지정해요. 숫자 정확도에 대한 정보는 숫자(Numbers)를 보세요. 비종단 이름이 시사하듯, 정확도 지정자가 없고 ‹inexact-numbern›에만 일치하는 숫자는 보통 부정확한 숫자로 파싱되고, 그 외에는 정확한 숫자로 파싱돼요. read-decimal-as-inexact 파라미터가 #f로 설정되면 정확도 지정자 없는 모든 숫자가 대신 정확한 것으로 파싱돼요.

리더가 #b(이진), #o(팔진), #d(십진), #x(십육진)를 만나면, 뒤에 구분자나 파일 끝으로 끝나는 수열이 와야 해요. 그 수열은 extflonum이거나, 각각 ‹general-number2›, ‹general-number8›, ‹general-number10›, 또는 ‹general-number16› 문법과 일치해야 해요.

#e#i 뒤에 바로 #b, #o, #d, #x가 오는 것은 역순(#b, #o, #d, #x 뒤에 #e#i)과 같게 취급돼요.

부정확 숫자의 ‹exponent-markn›은 지수를 지정하면서 동시에 숫자 정밀도를 지정해요. 단일 flonum이 지원되고(숫자 참고) read-single-flonum 파라미터가 #t이면, 표시 fs가 단일 flonum을 지정해요. read-single-flonum#f이거나 다른 표시에서는 배정밀도 flonum이 만들어져요. 단일 flonum이 지원되지 않고 read-single-flonum#t이면, 단일 flonum이 만들어질 상황에서 exn:fail:unsupported 예외가 발생해요. 특수 무한대와 not-a-number flonum·단일 flonum은 구별돼요. +nan.0 같은 .0 접미사가 있는 특수값은 배정밀도 flonum이고, +nan.f 같은 .f 접미사가 있는 특수값은 read-single-flonum을 통해 활성화되면 단일 flonum이에요.

‹inexactn› 숫자의 #0과 같지만, #는 해당 숫자의 실제 값을 알 수 없음을 시사하는 데 사용될 수 있어요.

숫자 표현의 모든 글자는 read-case-sensitive 파라미터와 무관하게 대소문자 무시로 파싱돼요. 예를 들어 #I#D+InF.F+3I#i#d+inf.f+3i와 같게 파싱돼요. 아래 문법에서 각 소문자 리터럴 글자는 자신과 대문자 형태 둘 다를 나타내요.

‹numbern›      ::=  ‹exactn›  |  ‹inexactn›
‹exactn›       ::=  ‹exact-rationaln›  |  ‹exact-complexn›
‹exact-rationaln› ::=  [‹sign›] ‹unsigned-rationaln›
‹unsigned-rationaln› ::=  ‹unsigned-integern›
                  |  ‹unsigned-integern› / ‹unsigned-integern›
‹exact-integern› ::=  [‹sign›] ‹unsigned-integern›
‹unsigned-integern› ::=  ‹digitn›+
‹exact-complexn› ::=  [‹exact-rationaln›] ‹sign› [‹unsigned-rationaln›] i
‹inexactn›     ::=  ‹inexact-realn›  |  ‹inexact-complexn›
‹inexact-realn› ::=  [‹sign›] ‹inexact-normaln›
                  |  ‹sign› ‹inexact-specialn›
‹inexact-unsignedn› ::=  ‹inexact-normaln›  |  ‹inexact-specialn›
‹inexact-normaln› ::=  ‹inexact-simplen› [‹exp-markn› ‹exact-integern›]
‹inexact-simplen› ::=  ‹digits#n› [.] #*
                  |  [‹unsigned-integern›] . ‹digits#n›
                  |  ‹digits#n› / ‹digits#n›
‹inexact-specialn› ::=  inf.0  |  nan.0  |  inf.f  |  nan.f
‹digits#n›     ::=  ‹digitn›+ #*
‹inexact-complexn› ::=  [‹inexact-realn›] ‹sign› [‹inexact-unsignedn›] i
                  |  ‹inexact-realn› @ ‹inexact-realn›
‹sign›         ::=  +  |  -
‹digit16›      ::=  ‹digit10›  |  a  |  b  |  c  |  d  |  e  |  f
‹digit10›      ::=  ‹digit8›  |  8  |  9
‹digit8›       ::=  ‹digit2›  |  2  |  3  |  4  |  5  |  6  |  7
‹digit2›       ::=  0  |  1
‹exp-mark16›   ::=  s  |  l
‹exp-mark10›   ::=  ‹exp-mark16›  |  d  |  e  |  f
‹exp-mark8›    ::=  ‹exp-mark10›
‹exp-mark2›    ::=  ‹exp-mark10›
‹general-numbern› ::=  [‹exactness›] ‹numbern›
‹exactness›    ::=  #e  |  #i

예시:

  • `-1`-1과 같게 읽힘
  • `1/2`(/ 1 2)과 같게 읽힘
  • `1.0`(exact->inexact 1)과 같게 읽힘
  • `1+2i`(make-rectangular 1 2)과 같게 읽힘
  • `1/2+3/4i`(make-rectangular (/ 1 2) (/ 3 4))과 같게 읽힘
  • `1.0+3.0e7i`(exact->inexact (make-rectangular 1 30000000))과 같게 읽힘
  • `2e5`(exact->inexact 200000)과 같게 읽힘
  • `#i5`(exact->inexact 5)과 같게 읽힘
  • `#e2e5`200000과 같게 읽힘
  • `#x2e5`741과 같게 읽힘
  • `#b101`5와 같게 읽힘

1.3.4 extflonum 읽기 (Reading Extflonums)

extflonum은 ‹exp-markn›을 포함하는 ‹inexact-realn›과 같은 문법을 가지지만, ‹exp-markn› 대신 t 또는 T가 붙어 있어요. 또한 +inf.t, -inf.t, +nan.t, -nan.t는 extflonum이에요. #b(이진), #o(팔진), #d(십진), #x(십육진) 기수 지정이 extflonum에 접두사될 수 있지만, #i#e는 그럴 수 없고, extflonum은 복소수를 만드는 데 사용될 수 없어요. read-decimal-as-inexact 파라미터는 extflonum 읽기에 효과가 없어요.

1.3.5 불리언 읽기 (Reading Booleans)

구분자가 뒤따르는 #true, #t, #T는 불리언 상수 "참"의 입력 문법이고, 구분자가 뒤따르는 #false, #f, #F는 불리언 상수 "거짓"의 완전한 입력 문법이에요.

1.3.6 페어와 리스트 읽기 (Reading Pairs and Lists)

리더가 (, [, {를 만나면 페어나 리스트 파싱을 시작해요. 페어와 리스트에 대한 정보는 페어와 리스트(Pairs and Lists)를 보세요.

페어나 리스트를 파싱하기 위해 리더는 일치하는 ), ], }(각각)을 찾을 때까지 데이터를 재귀적으로 읽고, 구분자로 둘러싸인 .을 특별히 처리해요. 페어 (), [], {}는 같은 방식으로 취급되므로, 이 절의 나머지는 단순히 "괄호"로 이 페어들 중 어떤 것도 뜻해요.

리더가 괄호 사이 원소 중 구분된 .을 찾지 못하면, 재귀 읽기 결과를 담은 리스트를 만들어 내요.

리더가 일치하는 괄호 사이에 구분된 .으로 분리된 데이터 두 개를 찾으면 페어를 만들어요. 더 일반적으로, 마지막 데이터 앞에 구분된 .이 있는 데이터 두 개 이상을 찾으면 중첩 페어를 구성해요: 뒤에서 두 번째 원소를 마지막과 페어링하고, 뒤에서 세 번째 데이터를 그 페어와 페어링하는 식이에요.

리더가 일치하는 괄호 사이에 데이터 세 개 이상을 찾고, 구분된 . 쌍이 첫 번째와 마지막 원소가 아닌 원소를 둘러싸면, 결과는 .으로 둘러싸인 원소를 첫 원소로 하고, 나머지를 읽은 순서로 뒤따르게 하는 리스트예요. 이 관례는 리더 수준에서 일종의 중위(infix) 표기를 지원해요.

read-syntax 모드에서 페어/리스트 원소에 대한 재귀 읽기도 read-syntax 모드이므로, 결과는 문법 객체의 리스트나 페어이며 그 자체가 문법 객체로 감싸져요. 리더가 단일 구분된 .이 포함되어 중첩 페어를 구성하면, 가장 안쪽 페어와 가장 바깥쪽 페어만 문법 객체로 감싸져요.

페어나 리스트를 감쌀 때, 그 페어나 리스트가 []로 만들어졌으면 결과에 값 #\['paren-shape 속성이 붙어요. read-square-bracket-with-tag 파라미터가 #t이면 결과 페어나 리스트는 (cons '#%brackets pair-or-list)와 동등한 것으로 감싸져요.

마찬가지로 리스트나 페어가 {}로 만들어졌으면 결과에 값 #\{'paren-shape 속성이 붙어요. read-curly-brace-with-tag 파라미터가 #t이면 결과 페어나 리스트는 (cons '#%braces pair-or-list)와 동등한 것으로 감싸져요.

구분된 .이 다른 어떤 구성에도 나타나면 exn:fail:read 예외가 발생해요. 마찬가지로 리더가 파싱 중인 리스트를 끝내지 않는 ), ], }를 만나면 exn:fail:read 예외가 발생해요.

예시:

  • `()`(list)과 같게 읽힘
  • `(1 2 3)`(list 1 2 3)과 같게 읽힘
  • `{1 2 3}`(list 1 2 3)과 같게 읽힘
  • `[1 2 3]`(list 1 2 3)과 같게 읽힘
  • `(1 (2) 3)`(list 1 (list 2) 3)과 같게 읽힘
  • `(1 . 3)`(cons 1 3)과 같게 읽힘
  • `(1 . (3))`(list 1 3)과 같게 읽힘
  • `(1 . 2 . 3)`(list 2 1 3)과 같게 읽힘

read-square-bracket-as-parenread-square-bracket-with-tag 파라미터가 #f로 설정되면, 리더가 []를 만날 때 exn:fail:read 예외가 발생해요. 마찬가지로 read-curly-brace-as-parenread-curly-brace-with-tag 파라미터가 #f로 설정되면, 리더가 {}를 만날 때 exn:fail:read 예외가 발생해요.

read-accept-dot 파라미터가 #f로 설정되면 구분된 .exn:fail:read 예외를 촉발해요. read-accept-infix-dot 파라미터가 #f로 설정되면 여러 구분된 .이 중위 변환 대신 exn:fail:read 예외를 촉발해요.

1.3.7 문자열 읽기 (Reading Strings)

Racket 가이드의 "문자열(Unicode)(Strings (Unicode))" 절이 문자열 문법을 소개해요.

리더가 "를 만나면 문자열을 형성하기 위한 문자 파싱을 시작해요. 문자열은 다른 "(\로 이스케이프되지 않은)로 끝날 때까지 계속돼요. 결과 문자열은 read-syntax 모드에서 인턴돼요.

문자열 수열 안에서 다음 이스케이프 시퀀스가 인식돼요.

  • \a: 경보(ASCII 7)
  • \b: 백스페이스(ASCII 8)
  • \t: 탭(ASCII 9)
  • \n: 라인피드(ASCII 10)
  • \v: 세로 탭(ASCII 11)
  • \f: 폼피드(ASCII 12)
  • \r: 캐리지 리턴(ASCII 13)
  • \e: 이스케이프(ASCII 27)
  • \": 큰따옴표(문자열을 끝내지 않음)
  • \': 인용(즉 백슬래시가 효과 없음)
  • \\: 백슬래시(즉 두 번째는 이스케이프 백슬래시가 아님)
  • \‹digit8›{1,3}: digit8{1,3}(즉 1~3개의 ‹digit8›)로 지정된 팔진 수에 대한 유니코드. 여기서 각 ‹digit8›은 0, 1, 2, 3, 4, 5, 6, 7이에요. 긴 형태가 짧은 형태보다 우선하고, 결과 팔진 수는 십진 0에서 255 사이여야 하며, 그렇지 않으면 exn:fail:read 예외가 발생해요.
  • \x‹digit16›{1,2}: ‹digit16›{1,2}로 지정된 십육진 수에 대한 유니코드. 여기서 각 ‹digit16›은 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f(대소문자 무시)예요. 긴 형태가 짧은 형태보다 우선해요.
  • \u‹digit16›{1,4}: \x와 같지만 최대 네 자리 십육진 수(긴 수열이 우선). 결과 십진 수는 integer->char의 유효한 인자여야 하며, 그렇지 않으면 exn:fail:read 예외가 발생해요—인코딩이 서러게이트 스타일 인코딩을 형성하기 위해 다른 \u로 계속되지 않는 한.
  • \u‹digit16›{4,4}\u‹digit16›{4,4}: \u와 같지만 두 십육진 수용. 첫 번째는 #xD800#xDBFF 범위, 두 번째는 #xDC00#xDFFF 범위여야 해요. 결과 문자는 이 수들이 UTF-16 서러게이트 쌍으로 나타내는 문자예요.
  • \U‹digit16›{1,8}: \x와 같지만 최대 여덟 자리 십육진 수(긴 수열이 우선). 결과 십육진 수는 integer->char의 유효한 인자여야 하며, 그렇지 않으면 exn:fail:read 예외가 발생해요.
  • \‹newline›: 생략. 여기서 ‹newline›은 라인피드, 캐리지 리턴, 또는 캐리지 리턴-라인피드 조합이에요. 이 관례는 단일 줄 문자열이 소스에서 여러 줄에 걸쳐 있을 수 있게 해요.

리더가 문자열 상수에서 백슬래시의 다른 사용을 만나면 exn:fail:read 예외가 발생해요.

Racket 가이드의 "바이트와 바이트 문자열(Bytes and Byte Strings)" 절이 바이트 문자열 문법을 소개해요.

#가 앞에 붙은 문자열 상수는 바이트 문자열로 파싱돼요. (즉 #"가 바이트 문자열 리터럴을 시작해요.) 바이트 문자열에 대한 정보는 바이트 문자열(Byte Strings)을 보세요. 결과 바이트 문자열은 read-syntax 모드에서 인턴돼요. 바이트 문자열 상수는 \u\U를 제외하고 문자 문자열과 같은 이스케이프 시퀀스를 지원해요. 그 외에는 바이트 문자열 인용 내의 각 문자가 0에서 255 범위의 유니코드 코드 포인트 수를 가져야 하며, 이는 해당 바이트의 값으로 사용돼요. 문자가 그 범위에 없으면 exn:fail:read 예외가 발생해요.

리더가 #<<를 만나면 here 문자열 파싱을 시작해요. #<< 뒤의 개행 문자까지의 문자들이 문자열의 종결자를 정의해요. 문자열 내용은 #<< 줄과 내용이 지정된 종결자뿐인 줄 사이의 모든 문자를 포함해요. 더 정확히, 문자열 내용은 #<< 뒤의 개행 뒤에서 시작하고, 종결자(자체가 개행이나 파일 끝이 뒤따르는)가 뒤따르는 개행 앞에서 끝나요. 시작과 종결 줄 사이에서는 어떤 이스케이프 시퀀스도 인식되지 않아요. 모든 문자가 문자열(및 종결자)에 그대로 포함돼요. 이 문맥에서 캐리지 리턴은 줄 구분자로 취급되지 않아요. #<<와 개행 또는 파일 끝 사이에 문자가 없거나, 종결 줄 전에 파일 끝을 만나면 exn:fail:read 예외가 발생해요.

예시:

  • `"Apple"`"Apple"과 같게 읽힘
  • `"\x41pple"`"Apple"과 같게 읽힘
  • `"\"Apple\""`"\x22Apple\x22"과 같게 읽힘
  • `"\\"`"\x5C"과 같게 읽힘
  • `#"Apple"`(bytes 65 112 112 108 101)과 같게 읽힘

1.3.8 인용 읽기 (Reading Quotes)

리더가 '를 만나면 데이터 하나를 재귀적으로 읽고, 심볼 'quote와 뒤따르는 데이터를 담은 새 리스트를 형성해요. 이 관례는 주로 's(quote s)의 약어로 사용될 수 있는 Racket 코드를 읽는 데 유용해요.

몇몇 다른 수열도 비슷한 방식으로 인식되고 변환돼요. 긴 접두사가 짧은 것보다 우선해요.

  • `'`quote를 추가
  • `` —quasiquote`를 추가
  • `,`unquote를 추가
  • `,@`unquote-splicing을 추가
  • `#'`syntax를 추가
  • `#`quasisyntax를 추가
  • `#,`unsyntax를 추가
  • `#,@`unsyntax-splicing을 추가

예시:

  • `'apple`(list 'quote 'apple)과 같게 읽힘
  • (1 ,2) `` — (list 'quasiquote (list 1 (list 'unquote 2)))과 같게 읽힘

`, ,, ,@ 폼은 read-accept-quasiquote 파라미터가 #f로 설정되면 비활성화되며, 그 경우 exn:fail:read 예외가 대신 발생해요.

1.3.9 주석 읽기 (Reading Comments)

;는 라인 주석을 시작해요. 리더가 ;를 만나면 다음 라인피드(ASCII 10), 캐리지 리턴(ASCII 13), next-line(유니코드 133), 라인 구분자(유니코드 8232), 또는 단락 구분자(유니코드 8233) 문자까지 모든 문자를 건너뛰어요.

#|는 중첩 가능한 블록 주석을 시작해요. 리더가 #|를 만나면 닫는 |#까지 모든 문자를 건너뛰어요. 일치하는 #||# 쌍은 중첩될 수 있어요.

#;는 S-표현식 주석을 시작해요. 리더가 #;를 만나면 데이터 하나를 재귀적으로 읽고, 그런 다음 그것을 버려요(읽기 결과를 위해 다음 데이터로 계속).

#!(공백이 뒤따르는 #!) 또는 #!/는, 줄을 \로 끝내면 다음 줄로 계속될 수 있는 라인 주석을 시작해요. 이 형태의 주석은 보통 유닉스 스크립트 파일의 시작에 나타나요.

예시:

  • `; comment` — 아무것도 아니게 읽힘
  • `#| a |# 1`1과 같게 읽힘
  • `#| #| a |# 1 |# 2`2와 같게 읽힘
  • `#;1 2`2와 같게 읽힘
  • `#!/bin/sh` — 아무것도 아니게 읽힘
  • `#! /bin/sh` — 아무것도 아니게 읽힘

1.3.10 벡터 읽기 (Reading Vectors)

리더가 #(, #[, #{를 만나면 벡터 파싱을 시작해요. 벡터에 대한 정보는 벡터(Vectors)를 보세요. # 대신 #fl이 flvector를 시작하지만 read-syntax 모드에서는 허용되지 않아요. flvector에 대한 정보는 Flonum 벡터(Flonum Vectors)를 보세요. # 대신 #fx가 fxvector를 시작하지만 read-syntax 모드에서는 허용되지 않아요. fxvector에 대한 정보는 Fixnum 벡터(Fixnum Vectors)를 보세요. #[, #{, #fl[, #fl{, #fx[, #fx{ 폼은 read-square-bracket-as-parenread-curly-brace-as-paren 파라미터로 비활성화될 수 있어요.

리스트와 마찬가지로(페어와 리스트 읽기 참고), 일치하는 ), ], }를 찾을 때까지 벡터의 원소를 재귀적으로 읽어요. 벡터 원소 사이에 구분된 .은 허용되지 않아요. flvector의 경우 원소에 대한 재귀 읽기에 암시적으로 #i가 접두사되고 flonum을 만들어야 해요. fxvector의 경우 원소에 대한 재귀 읽기에 암시적으로 #e가 접두사되고 fixnum을 만들어야 해요.

선택적 벡터 길이는 #, #fl, #fx(, [, { 사이에 지정될 수 있어요. 크기는 십진 숫자 수열로 지정되며, 벡터에 제공되는 원소 수는 지정된 크기보다 많을 수 없어요. 더 적은 원소가 제공되면 마지막으로 제공된 원소가 나머지 벡터 슬롯에 사용돼요. 원소가 제공되지 않으면 모든 슬롯에 0이 사용돼요.

read-syntax 모드에서 벡터 원소에 대한 각 재귀 읽기도 read-syntax 모드이므로, 감싸진 벡터의 원소도 문법 객체로 감싸지고, 벡터는 불변(immutable)이에요.

예시:

  • `#(1 apple 3)`(vector 1 'apple 3)과 같게 읽힘
  • `#3("apple" "banana")`(vector "apple" "banana" "banana")과 같게 읽힘
  • `#3()`(vector 0 0 0)과 같게 읽힘

1.3.11 구조체 읽기 (Reading Structures)

리더가 #s(, #s[, #s{를 만나면 prefab 구조체 타입의 인스턴스 파싱을 시작해요. 구조체 타입에 대한 정보는 구조체(Structures)를 보세요. #s[#s{ 폼은 read-square-bracket-as-parenread-curly-brace-as-paren 파라미터로 비활성화될 수 있어요.

리스트와 마찬가지로(페어와 리스트 읽기 참고), 일치하는 ), ], }를 찾을 때까지 구조체의 원소를 재귀적으로 읽어요. 원소 사이에 단일 구분된 .은 허용되지 않지만, 리스트에서처럼 중위 변환을 위해 두 개의 .을 사용할 수 있어요.

첫 원소는 구조체 기술자로 사용되며, (인용되었을 때) make-prefab-struct의 가능한 인자 형태여야 해요. 가장 단순한 경우 심볼일 수 있어요. 나머지 원소는 구조체 안의 필드 값에 대응해요.

read-syntax 모드에서 구조체 타입은 가변(mutable) 필드를 가져선 안 돼요. 구조체 원소는 read-syntax 모드로 읽히므로, 감싸진 구조체의 원소도 문법 객체로 감싸져요.

첫 구조체 원소가 유효한 prefab 구조체 타입 키가 아니거나, 제공된 필드 수가 표시된 prefab 구조체 타입과 일치하지 않으면 exn:fail:read 예외가 발생해요.

1.3.12 해시 테이블 읽기 (Reading Hash Tables)

#hashequal? 기반 키 일치의 불변 해시 테이블 상수를 시작해요. hash 뒤의 문자는 구분된 .을 특정 방식으로 사용하는 페어 리스트(페어와 리스트 읽기 참고)로 파싱되어야 해요. .은 리스트의 각 페어 원소 사이에 나타나야 하고 리스트 원소 수열의 다른 곳에는 나타나지 않아야 해요. 각 페어의 첫 원소는 테이블 항목의 키로, 각 페어의 두 번째 원소는 연결된 값으로 사용돼요.

#hashalw#hash와 같은 해시 테이블을 시작하지만, equal? 대신 equal-always? 기반으로 해시 테이블을 구성해요.

#hasheq#hash와 같은 해시 테이블을 시작하지만, equal? 대신 eq? 기반으로 구성해요.

#hasheqv#hash와 같은 해시 테이블을 시작하지만, equal? 대신 eqv? 기반으로 구성해요.

모든 경우 테이블은 각 매핑을 왼쪽에서 오른쪽으로 해시 테이블에 추가해 구성되므로, 이후 매핑이 키가 동등하면 이전 매핑을 가릴 수 있어요.

make-...make-immutable-hash를 뜻하는 예시:

  • `#hash()`(make-... '())과 같게 읽힘
  • `#hasheq()`(make-...eq '())과 같게 읽힘
  • `#hash(("a" . 5))`(make-... '(("a" . 5)))과 같게 읽힘
  • `#hasheq((a . 5) (b . 7))`(make-...eq '((b . 7) (a . 5)))과 같게 읽힘
  • `#hasheq((a . 5) (a . 7))`(make-...eq '((a . 7)))과 같게 읽힘

1.3.13 박스 읽기 (Reading Boxes)

리더가 #&를 만나면 박스 파싱을 시작해요. 박스에 대한 정보는 박스(Boxes)를 보세요. 박스 내용은 다음 데이터를 재귀적으로 읽어 결정돼요.

read-syntax 모드에서 박스 내용에 대한 재귀 읽기도 read-syntax 모드이므로, 감싸진 박스의 내용도 문법 객체로 감싸지고, 박스는 불변이에요.

예시:

  • `#&17`(box 17)과 같게 읽힘

1.3.14 문자 읽기 (Reading Characters)

Racket 가이드의 "문자(Characters)" 절이 문자 문법을 소개해요.

#\는 다음 형태 중 하나를 가진 문자 상수를 시작해요.

  • #\nul 또는 #\null: NUL(ASCII 0); 다음 문자는 알파벳이 아니어야 해요.
  • #\backspace: 백스페이스(ASCII 8); 다음 문자는 알파벳이 아니어야 해요.
  • #\tab: 탭(ASCII 9); 다음 문자는 알파벳이 아니어야 해요.
  • #\newline 또는 #\linefeed: 라인피드(ASCII 10); 다음 문자는 알파벳이 아니어야 해요.
  • #\vtab: 세로 탭(ASCII 11); 다음 문자는 알파벳이 아니어야 해요.
  • #\page: 페이지 나눔(ASCII 12); 다음 문자는 알파벳이 아니어야 해요.
  • #\return: 캐리지 리턴(ASCII 13); 다음 문자는 알파벳이 아니어야 해요.
  • #\space: 공백(ASCII 32); 다음 문자는 알파벳이 아니어야 해요.
  • #\rubout: 삭제(ASCII 127); 다음 문자는 알파벳이 아니어야 해요.
  • #\‹digit8›{3,3}: 문자열 이스케이프(문자열 읽기 참고)에서처럼, 세 개의 팔진 숫자로 지정된 팔진 수에 대한 유니코드. 단 정확히 세 자리로 제한돼요.
  • #\u‹digit16›{1,4}: 문자열 이스케이프(문자열 읽기 참고)에서처럼, ‹digit16›{1,4}로 지정된 십육진 수에 대한 유니코드.
  • #\U‹digit16›{1,8}: #\u와 같지만 최대 여덟 자리 십육진 수(실제로 유용한 것은 여섯 자리지만).
  • #\‹c›: 문자 ‹c›. 단 `#\`‹c›와 뒤따르는 문자들이 이전 경우 중 어떤 것과도 일치하지 않고, ‹c› 또는 ‹c› 다음의 문자가 알파벳이 아니며, ‹c›가 팔진 숫자가 아니거나 팔진 숫자가 뒤따르지 않는(즉 두 팔진 숫자면 세 번째에 고정됨) 경우에만.

예시:

  • `#\newline`(integer->char 10)과 같게 읽힘
  • `#\n`(integer->char 110)과 같게 읽힘
  • `#\u3BB`(integer->char 955)과 같게 읽힘
  • `#\λ`(integer->char 955)과 같게 읽힘

1.3.15 키워드 읽기 (Reading Keywords)

#:는 키워드를 시작해요. #: 뒤의 키워드 파싱은 심볼과 같으며, 대소문자 비구분 모드에서의 대소문자 접기 포함. 단 #: 뒤의 부분은 숫자로 절대 파싱되지 않아요. 결과 키워드는 인턴돼요.

예시:

  • `#:Apple`(string->keyword "Apple")과 같게 읽힘
  • `#:1`(string->keyword "1")과 같게 읽힘

1.3.16 정규 표현식 읽기 (Reading Regular Expressions)

#rx 또는 #px는 정규 표현식을 시작해요. #rx 또는 #px 바로 뒤의 문자는 문자열이나 바이트 문자열(문자열 읽기 참고)로 파싱되어야 해요. #rx 접두사는 regexp가 구성하는 것과 같은 정규 표현식을, #pxpregexp가 구성하는 것과 같은 것을 시작해요. #rx#byte-regexp가, #px#byte-pregexp가 구성하는 것과 같아요. 결과 정규 표현식은 read-syntax 모드에서 인턴돼요.

예시:

  • `#rx".*"`(regexp ".*")과 같게 읽힘
  • `#px"[\\s]*"`(pregexp "[\\s]*")과 같게 읽힘
  • `#rx#".*"`(byte-regexp #".*")과 같게 읽힘
  • `#px#"[\\s]*"`(byte-pregexp #"[\\s]*")과 같게 읽힘

1.3.17 그래프 구조 읽기 (Reading Graph Structure)

`#`‹digit10›{1,8}`=`는 뒤따르는 데이터를 `#`‹digit10›{1,8}`#`로의 참조를 위해 태그를 붙여, 리더가 그래프 구조를 가진 데이터를 만들어 낼 수 있게 해요.

read 모드에서, 단일 읽기 결과의 특정 ‹digit10›{1,8}에 대해 각 `#`‹digit10›{1,8}`#` 참조는 해당 `#`‹digit10›{1,8}`=`에 대해 읽힌 데이터로 대체돼요. 정의 `#`‹digit10›{1,8}`=`는 또한 뒤따르는 데이터만 만들어 내요. `#`‹digit10›{1,8}`=` 정의는 최대 한 번 나타날 수 있고, `#`‹digit10›{1,8}`#` 참조가 나타나기 전에 `#`‹digit10›{1,8}`=` 정의가 나타나야 해요. 그렇지 않으면 exn:fail:read 예외가 발생해요. read-accept-graph 파라미터가 #f로 설정되면 `#`‹digit10›{1,8}`=` 또는 `#`‹digit10›{1,8}`#`exn:fail:read 예외를 촉발해요.

read-syntax 모드에서 그래프 구조는 read 모드와 같은 방식으로 파싱돼요. 그러나 일반 S-표현식으로 만든 문법 객체는 순환을 포함할 수 없으므로, 각 `#`‹digit10›{1,8}`=` 정의와 `#`‹digit10›{1,8}`#` 참조가 참조된 값을 포함하는 플레이스홀더로 결과에서 대체돼요. 그런 문법 객체는 직접 유용하지 않으므로(컴파일된 코드로 마샬할 수 없어 기본 컴파일 핸들러가 거부함), read-syntax 모드에서 그래프 구조 파싱은 처음에 #f로 설정된 별도의 read-syntax-accept-graph 파라미터로 제어돼요.

#;로 파싱된 주석은 이후에 데이터를 버리지만, 버려진 데이터의 `#`‹digit10›{1,8}`=` 정의는, 주석과 참조가 다른 폼(즉 어떤 재귀 읽기)으로 함께 묶여 있다면 여전히 리더 입력의 다른 부분에서 참조될 수 있어요. 최상위 #; 주석은 다른 최상위 폼에 대해 그래프 태그를 정의하지도 사용하지도 않아요.

예시:

  • `(#1=100 #1# #1#)`(list 100 100 100)과 같게 읽힘
  • `#0=(1 . #0#)` — 다음과 같게 읽힘
(let* ([ph (make-placeholder #f)]
       [v (cons 1 ph)])
  (placeholder-set! ph v)
  (make-reader-graph v))

package base 8.4.0.8에서 변경: read-syntax-accept-graph로 활성화하면 read-syntax 모드에서 그래프 구조 읽기 지원 추가.

1.3.18 확장을 통한 읽기 (Reading via an Extension)

Racket 가이드의 "리더 확장(Reader Extensions)" 절이 리더 확장을 소개해요.

리더가 #reader를 만나면 외부 리더 프로시저를 로드하고 그것을 현재 입력 스트림에 적용해요.

리더는 #reader 뒤의 다음 데이터를 재귀적으로 읽어, current-reader-guard 파라미터 값인 프로시저에 전달해요. 결과는 모듈 경로로 사용돼요. 모듈 경로는 namespace-call-with-registry-lock으로 레지스트리 잠금을 잡고 있는 동안 dynamic-require'read 또는 'read-syntax와 함께(리더가 read 모드인지 read-syntax 모드인지에 따라) 전달돼요. 모듈은 현재 네임스페이스의 루트 네임스페이스에 로드돼요.

결과 프로시저의 arity는 추가 소스 위치 정보를 받는지 결정해요: read 프로시저는 한 인자(입력 포트) 또는 다섯 인자를 받고, read-syntax 프로시저는 두 인자(이름 값과 입력 포트) 또는 여섯 인자를 받아요. 어느 경우든 네 개의 선택적 인자는 리더의 모듈 경로(read-syntax 모드에서는 문법 객체) 뒤에 #reader 폼 시작의 줄(양의 정확한 정수 또는 #f), 열(음이 아닌 정확한 정수 또는 #f), 위치(양의 정확한 정수 또는 #f)가 따라와요. 입력 포트는 스트림에 #reader가 포함된 포트로, 스트림 위치는 재귀적으로 읽은 모듈 경로 바로 뒤예요.

프로시저는 데이터 결과를 만들어야 해요. read 모드에서 결과가 문법 객체이면 syntax->datum으로 데이터로 변환돼요. read-syntax 모드에서 결과가 문법 객체가 아니면 datum->syntax로 변환돼요. 프로시저 결과에 대한 정보는 리더 확장 프로시저(Reader-Extension Procedures)도 함께 보세요.

read-accept-reader 파라미터가 #f로 설정되면, 리더가 #reader를 만날 때 exn:fail:read 예외가 발생해요.

Racket 가이드의 "The #lang Shorthand" 절이 #lang을 소개해요.

`#lang` 리더 폼은 `#reader`와 비슷하지만 더 제약적이에요. `#lang`은 단일 공백(ASCII 32)이 뒤따르고, 그런 다음 공백이나 파일 끝으로 끝나는 알파벳·숫자 ASCII, +, -, _, // 문자의 비어 있지 않은 수열이 뒤따라야 해요. 수열은 /로 시작하거나 끝나선 안 돼요. 수열 `#lang`‹name› 는 `#reader (submod` ‹name› `reader)` 또는 `#reader` ‹name›/lang/reader``와 동등하며, 전자가module-declared?검사로 먼저 시도돼요(그러나current-reader-guard로 필터링된 뒤이므로, 후자가 사용되면 둘 다 current-reader-guard` 값에 전달돼요). 외부 읽기 프로시저가 호출되기 전에 종결 공백(있는 경우)이 소비되지 않는다는 점에 주의하세요.

Racket 가이드의 "Defining new #lang Languages" 절이 #lang용 언어 생성을 소개해요.

마지막으로 `#!`은, `#!` 뒤에 알파벳·숫자 ASCII, +, -, _가 올 때 공백이 뒤따르는 `#lang`의 별칭이에요. 이 별칭 사용은 R6RS[Sperber07] 같은 특정 문법을 따르는 프로그램을 구성할 필요가 있을 때를 제외하고는 권장되지 않아요.

syntax/module-reader 라이브러리는 언어 리더를 작성하기 위한 도메인 특화 언어를 제공해요.

관례상 `#lang`은 보통 파일의 시작에, 주석 폼 뒤에 나타나 모듈의 문법을 지정해요.

read-accept-reader 또는 read-accept-lang 파라미터가 #f로 설정되면, 리더가 `#lang` 또는 동등한 `#!`을 만날 때 exn:fail:read 예외가 발생해요.

package base 8.2.0.2에서 변경: `#reader``#lang`의 리더 모듈 로딩이 현재 네임스페이스 레지스트리의 잠금을 잡도록 변경.

1.3.19 C 스타일 중위 점 표기로 읽기 (Reading with C-style Infix-Dot Notation)

read-cdot 파라미터가 #t로 설정되면 리더에서 여러 변화가 일어나요.

첫째, 심볼은 더 이상 . 문자를 포함할 수 없어요. .|\로 인용되지 않는 한요.

둘째, 숫자는 더 이상 . 문자를 포함할 수 없어요. 숫자가 #e, #i, #b, #o, #d, #x숫자 읽기에 논의된 동등한 접두사로 접두사되지 않는 한요. 이 숫자들 뒤에 C 스타일 중위 점으로 읽히려는 .이 오면, . 앞에 구분자가 있어야 해요.

마지막으로, 데이터 x를 읽은 후 리더는 공백, BOM 문자, 주석을 소비해 . 뒤에 다른 데이터 y가 오는 수열을 0개 이상 찾아요. 그런 다음 xy'#%dot으로 묶어 x.y(#%dot x y)을 읽는 것과 같게 읽히게 해요.

x.y 뒤에 .이 또 있으면 리더는 더 많은 .으로 구분된 데이터를 누적해 왼쪽에서 오른쪽으로 묶어요. 예를 들어 x.y.z(#%dot (#%dot x y) z)을 읽는 것과 같게 읽혀요.

read-syntax 모드에서 '#%dot 심볼은 . 문자의 소스 위치 정보를 갖고, 전체 리스트는 x의 시작부터 y의 끝까지 걸친 소스 위치 정보를 가져요.

1.3.19.1 S-표현식 리더 언어

#lang s-exp package: base

Racket 가이드의 "Using #lang s-exp" 절이 s-exp 메타언어를 소개해요.

s-exp "언어"는 일종의 메타언어예요. `#lang s-exp` 뒤의 S-표현식을 read해 그것을 module 폼의 언어로 사용해요. 또한 파일 끝까지 나머지 모든 S-표현식을 읽어 생성된 module의 본문으로 사용해요.

즉,

#lang s-exp module-path
form ...

는 다음과 동등해요.

(module name-id module-path
  form ...)

여기서 name-id는 소스 입력 포트의 이름에서 파생돼요. 포트 이름이 파일 이름 경로이면 디렉터리 경로와 확장자가 없는 파일 이름이 name-id에 사용되고, 그렇지 않으면 name-idanonymous-module이에요.

1.3.19.2 연결 리더 언어

#lang reader package: base

Racket 가이드의 "Using #lang reader" 절이 reader 메타언어를 소개해요.

reader "언어"는 일종의 메타언어예요. `#lang reader` 뒤의 S-표현식을 read해 그것을(읽히는 모듈에 상대적인) 모듈 경로로 사용해요. 그 경로는 효과적으로 reader를 대신해요. 다시 말해 reader 메타언어는 #lang 뒤에 지정된 모듈의 문법을 모듈 경로로 일반화하고, 경로에 `//lang//reader`의 암시적 추가 없이 generalize해요.

더 알아보기