syntax-objects / Summer2021

Syntax Parse Bee 2021
11 stars 3 forks source link

`define/with-datum` -- defining pattern variables for datums #21

Open shhyou opened 3 years ago

shhyou commented 3 years ago

Macro

The original macro: https://github.com/racket/racket/blob/8e83dc25f7f5767d9e975f20982fdbb82f62415a/racket/collects/racket/syntax.rkt#L22-#L59

A new implementation using syntax-parse:

#lang racket/base

(require syntax/parse/define
         syntax/datum
         (for-syntax racket/base
                     racket/syntax
                     racket/private/sc))

(provide define/with-datum)

(begin-for-syntax
  (define-syntax-class (fresh-temporary fresh-stx)
    #:attributes (fresh-var)
    (pattern name
             #:with fresh-var (format-id fresh-stx "~a" (generate-temporary #'name))))

  (define (count-ellipses-depth var...^n)
    (for/fold ([var...^n var...^n]
               [depth 0])
              ([current-depth (in-naturals 1)]
               #:break (not (pair? var...^n)))
      (values (car var...^n) current-depth))))

#|
Source:
  https://github.com/racket/racket/blob/8e83dc25f7f5767d9e975f20982fdbb82f62415a/racket/collects/racket/syntax.rkt#L22-#L59

  racket/collects/racket/syntax.rktracket/collects/racket/syntax.rkt
  Commit SHA: 8e83dc25f7f5767
  Line: 22-59
|#
(define-syntax-parse-rule (define/with-datum pattern rhs)
  #:attr matched-vars (get-match-vars #'define/with-datum
                                      this-syntax
                                      #'pattern
                                      '())
  #:with (((~var pvar (fresh-temporary #'here)) . depth) ...)
  (for/list ([x (attribute matched-vars)])
    (define-values (var depth)
      (count-ellipses-depth x))
    (cons var depth))

  (begin
    (define-values (pvar.fresh-var ...)
      (with-datum ([pattern rhs])
        (values (pvar-value pvar) ...)))
    (define-syntax pvar
      (make-s-exp-mapping 'depth (quote-syntax pvar.fresh-var)))
    ...))

;; auxiliary macro
(define-syntax-parse-rule (pvar-value pvar:id)
  #:attr mapping (syntax-local-value #'pvar)
  #:do [(unless (s-exp-pattern-variable? (attribute mapping))
          (raise-syntax-error #f "not a datum variable" #'pvar))]
  #:with value-var (s-exp-mapping-valvar (attribute mapping))
  value-var)

Example

(define/with-datum (x ((y z ...) ...))
  '("X" (("Y1" "Z11" "Z12")
         ("Y2" "Z21"))))

This code uses define/with-datum, the counterpart of define/with-syntax, to define pattern variables bound to plain data. Just like define/with-syntax, define/with-datum allows arbitrary patterns (with ellipses) that syntax-case supports.

In this example, the pattern variable x matches "X" in the list while the pattern ((y z ...) ...) defines the pattern variables y, z and matches (("Y1" "Z11" "Z12") ("Y2" "Z21")). The variable y at ellipsis depth 1 is bound to the value ("Y1" "Y2") and the variable z at ellipsis depth 2 is bound to the value (("Z11" "Z12") ("Z21")).

Here are a few examples using the defined pattern variables in datum templates:

(datum x)

(with-datum ([w "W"]) ;; currently supported
  (datum ((y w) ...)))

(datum
 ((^ z ... $) ...))

Before and After

The original macro: https://github.com/racket/racket/blob/8e83dc25f7f5767d9e975f20982fdbb82f62415a/racket/collects/racket/syntax.rkt#L22-#L59

All the procedural code in the original macro can be expressed in the pattern directives, #:attr, #:do and #:with. These pattern directives allow escaping into arbitrary expansion-time computation while retaining appropriate semantical meanings such as binding a pattern variable (#:with) or performing an imperative action (#:do).

Licence

I hereby license the code in this issue under the same MIT License that the Racket language uses: https://github.com/racket/racket/blob/master/racket/src/LICENSE-MIT.txt

All the associated text in this issue is licensed under the Creative Commons Attribution 4.0 International License http://creativecommons.org/licenses/by/4.0/