R6RS Scheme: 매크로 변환기
R6RS Scheme: 매크로 변환기
매크로 변환기(macro transformer)를 만드는 두 가지 기본 수단인 syntax-rules와 identifier-syntax를 설명해요. 위생적(hygenic) 패턴 매칭과 템플릿 전개 규칙을 빠짐없이 다뤄요.
출처: 문서
본문
이 절은 매크로 변환기를 만드는 데 사용되는 문법을 설명해요.
(syntax-rules (<literal> ...) <syntax rule> ...) — syntax (expand)
_ — auxiliary syntax (expand)
... — auxiliary syntax (expand)
문법: 각 <literal>은 식별자여야 해요. 각 <syntax rule>은 다음 형식이어야 해요:
(<srpattern> <template>)
<srpattern>은 제한된 형태의 <pattern>인데, 즉 아래 네 가지 괄호 형식 중 하나인 비-빈 <pattern>으로, 그 첫 하위 형식(subform)이 식별자이거나 밑줄 _이에요. <pattern>은 식별자, 상수, 또는 다음 중 하나예요:
(<pattern> ...)
(<pattern> <pattern> ... . <pattern>)
(<pattern> ... <pattern> <ellipsis> <pattern> ...)
(<pattern> ... <pattern> <ellipsis> <pattern> ... . <pattern>)
#(<pattern> ...)
#(<pattern> ... <pattern> <ellipsis> <pattern> ...)
<ellipsis>는 식별자 "..."(마침표 세 개)예요.
<template>은 패턴 변수, 패턴 변수가 아닌 식별자, 패턴 데이터(datum), 또는 다음 중 하나예요:
(<subtemplate> ...)
(<subtemplate> ... . <template>)
#(<subtemplate> ...)
<subtemplate>은 0개 이상의 생략부호(ellipsis)가 뒤따르는 <template>이에요.
의미: syntax-rules의 인스턴스는 매크로 확장 시점에 위생적 재작성 규칙들의 수열을 명시함으로써 새로운 매크로 변환기로 평가돼요. syntax-rules로 명시된 변환기에 키워드가 연관된 매크로의 사용은 가장 왼쪽 <syntax rule>부터 시작하여 <syntax rule>들에 들어 있는 패턴들에 대응(match)시켜 봐요. 일치를 찾으면, 매크로 사용은 템플릿에 따라 위생적으로 전사(transcribe)돼요. 일치하는 것이 없으면 그것은 문법 위반(syntax violation)이에요.
<pattern> 안에 나타나는 식별자는 밑줄(_), 리터럴 목록 (<literal> ...)에 나열된 리터럴 식별자, 또는 생략부호(...)일 수 있어요. <pattern> 안에 나타나는 다른 모든 식별자는 패턴 변수예요. 생략부호 또는 밑줄이 (<literal> ...) 안에 나타나면 그것은 문법 위반이에요.
<srpattern>의 첫 하위 형식은 식별자일 수 있지만, 그 식별자는 일치에 관여하지 않고 패턴 변수나 리터럴 식별자로 간주되지 않아요.
패턴 변수는 임의의 입력 하위 형식과 일치하고, 입력의 원소들을 가리키는 데 사용돼요. 같은 패턴 변수가 <pattern>에 두 번 이상 나타나면 문법 위반이에요.
밑줄도 임의의 입력 하위 형식과 일치하지만 패턴 변수가 아니므로 그 원소들을 가리키는 데 사용할 수 없어요. 여러 밑줄이 <pattern>에 나타날 수 있어요.
리터럴 식별자는, 입력 하위 형식이 식별자이고, 입력 표현식에서의 그것의 출현과 리터럴 목록에서의 그것의 출현이 같은 어휘적 바인딩(lexical binding)을 가지거나, 또는 그 두 식별자가 같은 이름을 가지며 둘 다 어휘적 바인딩이 없을 때, 그 입력 하위 형식과 일치해요.
뒤에 생략부호가 붙은 하위 패턴은 입력의 0개 이상의 원소와 일치할 수 있어요.
더 형식적으로, 입력 형식 F가 패턴 P와 일치하는 것은 다음 중 하나가 성립할 때 그리고 오직 그때예요:
- P는 밑줄(
_)이에요. - P는 패턴 변수예요.
- P는 리터럴 식별자이고, F는 P와 F 둘 다가 매크로의 출력에 (매크로의 출력에 삽입된 어떤 바인딩도 밖에서) 나타난다면 둘 다가 같은 바인딩을 가리킬 그러한 식별자예요. (같은 이름의 두 식별자가 어떤 바인딩도 가리키지 않으면, 즉 둘 다 미정의이면, 그들은 같은 바인딩을 가리키는 것으로 간주돼요.)
- P는
(P1 ... Pn)형식이고, F는 P1부터 Pn까지와 일치하는n개의 원소를 가진 리스트예요. - P는
(P1 ... Pn . Px)형식이고, F는 P1부터 Pn까지와 일치하는 첫n개 원소를 가진,n개 이상의 원소를 가진 리스트 또는 비정상 리스트이고, 그n번째 cdr은 Px와 일치해요. - P는
(P1 ... Pk Pe <ellipsis> Pm+1 ... Pn)형식인데, 여기서<ellipsis>는 식별자...이고, F는 P1부터 Pk까지와 일치하는 첫k개 원소, 각각 Pe와 일치하는 그다음m−k개 원소, Pm+1부터 Pn까지와 일치하는 나머지n−m개 원소를 가진n개 원소의 리스트예요. - P는
(P1 ... Pk Pe <ellipsis> Pm+1 ... Pn . Px)형식인데, 여기서<ellipsis>는 식별자...이고, F는 P1부터 Pk까지와 일치하는 첫k개 원소, 각각 Pe와 일치하는 그다음m−k개 원소, Pm+1부터 Pn까지와 일치하는 그다음n−m개 원소, 그리고 Px와 일치하는n번째이자 마지막 cdr을 가진n개 원소의 리스트 또는 비정상 리스트예요. - P는
#(P1 ... Pn)형식이고, F는 P1부터 Pn까지와 일치하는n개 원소를 가진 벡터예요. - P는
#(P1 ... Pk Pe <ellipsis> Pm+1 ... Pn)형식인데, 여기서<ellipsis>는 식별자...이고, F는 P1부터 Pk까지와 일치하는 첫k개 원소, 각각 Pe와 일치하는 그다음m−k개 원소, 그리고 Pm+1부터 Pn까지와 일치하는 나머지n−m개 원소를 가진n개 이상의 원소를 가진 벡터예요. - P는 패턴 데이터(리스트가 아니고 벡터가 아니고 기호가 아닌 어떤 데이터)이고 F는
equal?프로시저의 의미에서 P와 같은 것이에요.
매크로 사용이 일치하는 <syntax rule>의 템플릿에 따라 전사될 때, 템플릿에 나타나는 패턴 변수는 그것들이 입력에서 일치한 하위 형식들로 대체돼요.
패턴 변수나 생략부호가 아닌 패턴 데이터와 식별자는 출력으로 복사돼요. 뒤에 생략부호가 붙은 하위 템플릿은 하위 템플릿의 0개 이상의 출현으로 확장돼요. 하나 이상의 생략부호가 뒤따르는 하위 패턴에 나타나는 패턴 변수는 (적어도) 그만큼의 생략부호가 뒤따르는 하위 템플릿에만 나타날 수 있어요. 이 패턴 변수들은 출력에서 그것들이 바인딩된 입력 하위 형식들로, 명시된 대로 분배되어 대체돼요. 패턴 변수가 하위 템플릿에서 연관된 하위 패턴에서보다 더 많은 생략부호 뒤에 온다면, 입력 형식은 필요에 따라 복제돼요. 하위 템플릿은 생략부호가 뒤따르는 하위 패턴에서 적어도 하나의 패턴 변수를 포함해야 하고, 그런 패턴 변수 중 적어도 하나에 대해 하위 템플릿은 그 패턴 변수가 나타나는 하위 패턴만큼 정확히 많은 생략부호가 뒤따라야 해요. (그렇지 않으면 확장기가 하위 형식이 출력에서 몇 번 반복되어야 하는지 결정할 수 없어요.) 이 문단의 제약들이 충족되지 않으면 문법 위반이에요.
(... <template>) 형식의 템플릿은 <template>과 동일한데, 템플릿 안의 생략부호가 특별한 의미를 갖지 않는다는 점만 달라요. 즉 <template> 안에 들어 있는 어떤 생략부호도 보통의 식별자로 취급돼요. 특히 템플릿 (... ...)은 단일 생략부호 ...를 만들어내요. 이것은 문법 추상화가 생략부호를 포함하는 형식으로 확장될 수 있게 해줘요.
(define-syntax be-like-begin
(syntax-rules ()
((be-like-begin name)
(define-syntax name
(syntax-rules ()
((name expr (... ...))
(begin expr (... ...))))))))
(be-like-begin sequence)
(sequence 1 2 3 4) ⇒ 4
보조 식별자의 위생적 사용에 대한 예로서, let과 cond가 11.16절과 부록 B에 정의된 대로 정의된다면, 그것들은 (요구된 대로) 위생적이고 다음은 오류가 아니에요.
(let ((=> #f))
(cond (#t => 'ok))) ⇒ ok
cond의 매크로 변환기는 =>를 지역 변수로, 따라서 표현식으로 인식하고, 매크로 변환기가 구문 키워드로 취급하는 식별자 =>로는 인식하지 않아요. 그래서 이 예는 다음과 같이 확장돼요:
(let ((=> #f))
(if #t (begin => 'ok)))
다음과 같이 확장되는 대신이에요:
(let ((=> #f))
(let ((temp #t))
(if temp ('ok temp))))
후자는 단언 위반(assertion violation)을 낳을 거예요.
(identifier-syntax <template>) — syntax (expand)
(identifier-syntax (<id> (<template1>) ((set! <id> <e>) <template2>))) — syntax (expand)
set! — auxiliary syntax
문법: <id>들은 식별자여야 해요. <template>들은 syntax-rules에서와 같아야 해요.
의미: 키워드가 identifier-syntax의 첫 번째 형식이 만든 변환기에 바인딩되면, 바인딩의 유효범위 안에서 키워드에 대한 참조들이 <template>으로 대체돼요.
(define p (cons 4 5))
(define-syntax p.car (identifier-syntax (car p)))
p.car ⇒ 4
(set! p.car 15) ⇒ &syntax exception
identifier-syntax의 두 번째, 더 일반적인 형식은 변환기가 set!이 사용될 때 무슨 일이 일어나는지 결정할 수 있게 해줘요. 이 경우, 식별자 그 자체의 사용은 <template1>으로 대체되고, 그 식별자와 함께 set!을 사용하는 것은 <template2>로 대체돼요.
(define p (cons 4 5))
(define-syntax p.car
(identifier-syntax
(_ (car p))
((set! _ e) (set-car! p e))))
(set! p.car 15)
p.car ⇒ 15
p ⇒ (15 5)