구문 유틸리티

구문 유틸리티 (Syntax Utilities)

이 섹션은 구문 객체를 다룰 때 쓰는 보조 함수들을 모아둔 racket/syntax 라이브러리를 다뤄요. 포맷된 식별자 만들기, 패턴 변수, 오류 보고, 사라진 사용 기록, 그 외 잡다한 유틸리티를 담고 있어요.

출처: Racket Reference

본문

(require racket/syntax)  ; package: base

이 섹션에서 다루는 바인딩은 racket/baseracket이 아니라 racket/syntax 라이브러리가 제공해요.

12.12.1 포맷된 식별자 만들기

(format-id lctx fmt v ...
           [ #:source src
             #:props props
             #:cert ignored
             #:subs? subs?
             #:subs-intro subs-introducer]) → identifier?
  lctx : (or/c syntax? #f)
  fmt : string?
  v : (or/c string? symbol? keyword? char? number?
       (syntax/c (or/c string? symbol? keyword? char? number?)))
  src : (or/c syntax? #f) = #f
  props : (or/c syntax? #f) = #f
  ignored : (or/c syntax? #f) = #f
  subs? : boolean? = #f
  subs-introducer : (-> syntax? syntax?)
                 = (if (syntax-transforming?) syntax-local-introduce values)

format과 같지만, 어휘 문맥으로 lctx를, 소스 위치로 src를, 속성으로 props를 사용해 식별자를 만들어내요. #:cert로 제공된 인자는 무시돼요. (datum->syntax 참고.)

포맷 문자열은 ~a 자리표시자만 사용해야 해요. 인자 목록의 구문 객체는 자동으로 언랩됩니다(예: 식별자는 자동으로 심볼로 변환돼요).

예제:

> (define-syntax (make-pred stx)
    (syntax-case stx ()
      [(make-pred name)
       (format-id #'name "~a?" (syntax-e #'name))]))
> (make-pred pair)
#<procedure:pair?>
> (make-pred none-such)
none-such?: undefined;
  cannot reference an identifier before its definition
  in module: top-level
> (define-syntax (better-make-pred stx)
    (syntax-case stx ()
      [(better-make-pred name)
       (format-id #'name #:source #'name
                  "~a?" (syntax-e #'name))]))
> (better-make-pred none-such)
none-such?: undefined;
  cannot reference an identifier before its definition
  in module: top-level

(Scribble은 보여주지 않지만, DrRacket은 두 번째 오류의 위치는 짚어주고 첫 번째 오류의 위치는 짚어주지 않아요.)

subs?#t이면, 결과에 'sub-range-binders 구문 속성이 추가되어 v 안의 각 식별자 위치를 기록해요. subs-intro 프로시저가 각 식별자에 적용되고, 그 결과가 하위 범위 바인더 기록에 포함돼요. 이 속성 값은 props에서 복사된 'sub-range-binders 속성을 재정의해요.

예제:

> (syntax-property (format-id #'here "~a/~a-~a" #'point 2 #'y #:subs? #t)
                   'sub-range-binders)
'(#(#<syntax point/2-y> 8 1 0.5 0.5 #<syntax:eval:8:0 y> 0 1 0.5 0.5)
  #(#<syntax point/2-y> 0 5 0.5 0.5 #<syntax:eval:8:0 point> 0 5 0.5 0.5))

변경 사항: base 패키지 7.4.0.5 버전에서 #:subs?#:subs-intro 인자가 추가됐어요. 8.7.0.7 버전에서 v가 문자열, 키워드, 문자, 또는 숫자를 감싼 구문 객체가 될 수 있게 됐어요.

(format-symbol fmt v ...) → symbol?
  fmt : string?
  v : (or/c string? symbol? keyword? char? number?
       (syntax/c (or/c string? symbol? keyword? char? number?)))

format과 같지만 심볼을 만들어내요. 포맷 문자열은 ~a 자리표시자만 사용해야 해요. 인자 목록의 구문 객체는 자동으로 언랩됩니다(예: 식별자는 자동으로 심볼로 변환돼요).

예제:

> (format-symbol "make-~a" 'triple)
'make-triple

base 패키지 8.7.0.7 버전에서 v가 문자열, 키워드, 문자, 또는 숫자를 감싼 구문 객체가 될 수 있게 됐어요.

12.12.2 패턴 변수

(define/with-syntax pattern stx-expr)
  stx-expr : syntax?

with-syntax의 정의 형식이에요. 즉 stx-expr의 구문 객체 결과를 pattern에 매칭해, pattern의 패턴 변수들에 대한 패턴 변수 정의를 만들어내요.

예제:

> (define/with-syntax (px ...) #'(a b c))
> (define/with-syntax (tmp ...) (generate-temporaries #'(px ...)))
> #'([tmp px] ...)
#<syntax:eval:12:0 ((a9 a) (b10 b) (c11 c))>
> (define/with-syntax name #'Alice)
> #'(hello name)
#<syntax:eval:14:0 (hello Alice)>

12.12.3 오류 보고

(current-syntax-context) → (or/c syntax? #f)
(current-syntax-context stx) → void?
  stx : (or/c syntax? #f)

현재 문맥 구문 객체로, 기본값은 #f예요. wrong-syntax가 만드는 구문 오류 앞에 붙는 특수 형식 이름을 결정해요.

(wrong-syntax stx format-string v ...) → any
  stx : syntax?
  format-string : string?
  v : any/c

(current-syntax-context)의 결과를 "주요(major)" 구문 객체로, 제공된 stx를 특정 구문 객체로 사용해 구문 오류를 발생시켜요. (후자의 stx는 보통 DrRacket이 강조 표시하는 것이에요.) 오류 메시지는 포맷 문자열과 인자로 구성되고, current-syntax-context 아래에서 설명한 대로 특수 형식 이름이 접두사로 붙어요.

예제:

> (wrong-syntax #'here "expected ~s" 'there)
eval:15:0: ?: expected there
  at: here
> (parameterize ([current-syntax-context #'(look over here)])
    (wrong-syntax #'here "expected ~s" 'there))
eval:16:0: look: expected there
  at: here
  in: (look over here)

wrong-syntax를 사용하는 매크로는 변환의 맨 처음에 구문 문맥을 다음과 같이 설정할 수 있어요:

(define-syntax (my-macro stx)
  (parameterize ([current-syntax-context stx])
    (syntax-case stx ()
      __)))

그러면 매크로 변환 중에 wrong-syntax를 호출하면 my-macro를 가리키게 돼요(좀 더 정확히는, 매크로가 사용된 자리에서 my-macro를 가리켰던 이름. 재명명(renaming)이나 접두사 붙이기 등으로 다를 수 있어요).

12.12.4 사라진 사용 기록

(current-recorded-disappeared-uses)
  → (or/c (listof identifier?) #f)
(current-recorded-disappeared-uses ids) → void?
  ids : (or/c (listof identifier?) #f)

사라진 사용(disappeared uses)을 추적하기 위한 파라미터예요. 이 파라미터가 참이 아닌 값을 가질 때 추적이 "활성화"돼요. with-disappeared-uses 같은 형식이 자동으로 이렇게 해요.

(with-disappeared-uses body-expr ... stx-expr)
  stx-expr : syntax?

body-expr들과 stx-expr를 평가하면서, syntax-local-value/record로 조회된 식별자들을 잡아내요. 잡아낸 식별자들을 stx-expr이 만들어낸 구문 객체의 'disappeared-use 구문 속성에 추가해요.

base 패키지 6.5.0.7 버전에서 body-expr들을 포함하는 옵션이 추가됐어요.

(syntax-local-value/record id predicate) → any/c
  id : identifier?
  predicate : (-> any/c boolean?)

구문 환경에서 id를 조회해요(syntax-local-value처럼). 조회가 성공하고 predicate를 만족하는 값을 반환하면, 그 값을 반환하고 record-disappeared-uses를 호출해 id를 사라진 사용으로 기록해요. 조회가 실패하거나 값이 predicate를 만족하지 않으면 #f를 반환하고 식별자를 사라진 사용으로 기록하지 않아요.

(record-disappeared-uses id [intro?]) → void?
  id : (or/c identifier? (listof identifier?))
  intro? : boolean? = (syntax-transforming?)

id(current-recorded-disappeared-uses)에 추가해요. id가 목록이면 모든 식별자에 같은 작업을 수행해요. intro?가 참이면 식별자들에 먼저 syntax-local-introduce를 호출해요.

with-disappeared-uses 형식이나 그와 유사한 형식의 범위 안에서 쓰이지 않으면 효과가 없어요.

변경 사항: base 패키지 6.5.0.7 버전에서 목록 대신 단일 식별자를 전달하는 옵션이 추가됐어요. 7.2.0.11 버전에서 intro? 인자가 추가됐어요.

12.12.5 잡다한 유틸리티

(generate-temporary [name-base]) → identifier?
  name-base : any/c = 'g

새 식별자 하나를 만들어내요. generate-temporaries의 단수 형태예요. name-base가 제공되면 그 값을 식별자 이름의 기반으로 사용해요.

(internal-definition-context-apply intdef-ctx stx) → syntax?
  intdef-ctx : internal-definition-context?
  stx : syntax?

(internal-definition-context-introduce intdef-ctx stx 'add)와 동등해요. internal-definition-context-apply 함수는 하위 호환성을 위해 제공되며, internal-definition-context-add-scopes 함수를 선호해요.

(syntax-local-eval stx [intdef-ctx]) → any
  stx : any/c
  intdef-ctx : (or/c internal-definition-context? #f
                    (listof internal-definition-context?)) = '()

현재 변환기 환경(즉, 단계 수준 1)에서 stx를 표현식으로 평가해요. intdef-ctx#f가 아니면, 제공된 intdef-ctx 값은 local-expand의 네 번째 인자와 같은 방식으로 stx의 어휘 정보를 풍부하게 하고 지역 바인딩 문맥을 확장하는 데 사용돼요.

예제:

> (define-syntax (show-me stx)
    (syntax-case stx ()
      [(show-me expr)
       (begin
         (printf "at compile time produces ~s\n"
                 (syntax-local-eval #'expr))
         #'(printf "at run time produces ~s\n"
                   expr))]))
> (show-me (+ 2 5))
at compile time produces 7
at run time produces 7
> (define-for-syntax fruit 'apple)
> (define fruit 'pear)
> (show-me fruit)
at compile time produces apple
at run time produces pear

base 패키지 6.90.0.27 버전에서 intdef-ctx가 단일 내부 정의 문맥이나 #f 외에 내부 정의 문맥의 목록도 받도록 바뀌었어요.

(with-syntax* ([pattern stx-expr] ...)
  body ...+)
  stx-expr : syntax?

with-syntax와 비슷하지만, 각 패턴의 패턴 변수들이 뒤따르는 절의 stx-expr들과 body들에도 바인딩되고, 패턴들이 서로 다른 패턴 변수를 바인딩할 필요가 없어요. 나중의 바인딩이 이전의 바인딩을 가려요(shadow).

예제:

> (with-syntax* ([(x y) (list #'val1 #'val2)]
                 [nest #'((x) (y))])
    #'nest)
#<syntax:eval:22:0 ((val1) (val2))>

더 알아보기

  • with-syntax: 일반적인 형태
  • local-expand, generate-temporaries
  • 구문 객체 속성: 'disappeared-use, 'sub-range-binders