I would love to see some library provide better abstractions for fairly
common cases of keywords like this, but here is how I might write this:

#lang racket

(require (for-syntax syntax/parse
                     syntax/define
                     ))

(define-syntax (register-nvim-function! stx)
  (displayln stx)
  #`(begin))

(define-syntax (define-nvim-function stx)
  (define-syntax-rule (attr? kw pat-id)
    (and (attribute pat-id) (list #'kw #'pat-id)))
  (define-splicing-syntax-class options
    #:attributes (name [opt 1])
    (pattern (~seq (~alt (~once (~seq #:name name:expr))
                         (~optional (~seq #:range range?:expr))
                         (~optional (~seq #:eval eval?:expr))
                         (~optional (~seq #:sync sync?:expr)))
                   ...)
             #:with (opt ...)
             #`(#,@(for*/list ([clause (in-list
                                        (list (attr? #:range range?)
                                              (attr? #:eval eval?)
                                              (attr? #:sync sync?)))]
                               #:when clause
                               [term (in-list clause)])
                     term))))
  (syntax-parse stx
    [(_ raw-lhs:expr opts:options
        body:expr ...+)
     #:do [(define-values (ident-stx rhs-stx)
             (normalize-definition #'(define raw-lhs body ...)
                                   #'λ
                                   #t
                                   #t))]
     #:with ident ident-stx
     #:with rhs rhs-stx
     #'(begin
         (define ident rhs)
         (register-nvim-function! ident opts.name opts.opt ...))]))

(define-nvim-function (foo bar baz) #:name "foo" #:sync #f
  (display bar)
  (display baz))

You would probably then want to add contracts using `expr/c`.

I also wonder if you really want to have a mandatory "name" argument or if
you should infer it based on the identifier being defined (perhaps with an
option to override).

-Philip

On Tue, Aug 29, 2017 at 11:27 AM, <hiph...@openmailbox.org> wrote:

> Hello,
>
> I have a working macro written using `syntax-parse`, but it still has
> some kinks that need to be worked out. What I want the macro to do is
> define a function and as it as input to another function. Example:
>
>   (define-nvim-function (foo bar baz) #:name "foo" #:sync #f
>     (display bar)
>     (display baz))
>
> This would expand to
>
>   (define (foo bar baz)
>     (display bar)
>     (display baz))
>   (register-nvim-function! foo "foo" #:sync #f)
>
> The `register-nvim-function!` function is defined like this:
>
>   (define (register-nvim-function! proc name
>                                    #:range [range     #f]
>                                    #:eval  [eval  (void)]
>                                    #:sync  [sync?     #f])
>     ...)
>
> The point is that `proc` and `name` are mandatory, but the keyword
> arguments are optional. I want the keyword arguments of the macro to be
> optional as well, except for the name, and their order should not
> matter. Here is what I got so far:
>
>
>   (define-syntax (define-nvim-function stx)
>     (syntax-parse stx
>       [(define-function (head:id arg-id:id ...)
>                         (~alt (~once     (~seq #:name name:expr)
>                                          #:name "#:name name")
>                               (~optional (~seq #:range range:expr)
>                                          #:name "#:range range"
>                                          #:defaults ([range #'#f]))
>                               (~optional (~seq #:eval eval:expr)
>                                          #:name "#:eval eval"
>                                          #:defaults ([eval #'(void)]))
>                               (~optional (~seq #:sync sync?:expr)
>                                          #:name "#:sync sync?"
>                                          #:defaults ([sync? #'#f])))
>                         ...
>          body:expr ...+)
>        #'(define-function head #:name name #:range range #:eval eval
> #:sync sync?
>            (λ (arg-id ...) body ...)) ]
>       [(define-function head:id
>                         (~alt (~once     (~seq #:name name:expr)
>                                          #:name "#:name name")
>                               (~optional (~seq #:range range:expr)
>                                          #:name "#:range range"
>                                          #:defaults ([range #'#f]))
>                               (~optional (~seq #:eval eval:expr)
>                                          #:name "#:eval eval"
>                                          #:defaults ([eval #'(void)]))
>                               (~optional (~seq #:sync sync?:expr)
>                                          #:name "#:sync sync?"
>                                          #:defaults ([sync? #'#f])))
>                         ...
>          body:expr)
>        #'(begin
>            (define head body)
>            (register-function! head name #:range range #:eval eval #:sync
> sync?))]))
>
> The macros supports two variants, similar to how `define` allows two
> ways of defining a function. The problems with this implementation are:
>
> - The giant blob of keyword arguments is repeated twice
> - The template is listing all the keyword arguments the function can
>   take
>
> I was thinking of a macro more like this:
>
>   (define-syntax (define-nvim-function stx)
>     (define-splicing-syntax-class option
>       (pattern (#:name    name:expr))
>       (pattern (#:range range?:expr))
>       (pattern (#:eval   eval?:expr))
>       (pattern (#:sync   sync?:expr)))
>     (syntax-parse stx
>       [(define-function (head:id arg-id:id ...) option:option ...
>          body:expr ...+)
>        #'(define-function head option ...
>            (λ (arg-id ...) body ...)) ]
>       [(define-function head:id option.option ...
>          body:expr)
>        #'(begin
>            (define head body)
>            (register-function! head option))]))
>
> This won't work though. First I lose the optional/unique specifications
> from above, second the keyword-value pairs are wrapped in parentheses
> for some reason. Expansion becomes
>
>   (register-function! foo (#:name "foo") (#:sync #f))
>   ;; instead of
>   (register-function! foo #:name "foo" #:sync #f)
>
> What am I doing wrong here? I have a pretty solid grasp of Racket thanks
> to the great guide, but when it comes to macros I am still totally lost.
>
> --
> You received this message because you are subscribed to the Google Groups
> "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to racket-users+unsubscr...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.
>

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to