Hi Guile Users! In previous e-mails I inquired about macros defining procedures.
Afterwards I noticed, that I need my macro to check whether some regular expression is inside the name of an identifier. For example I have an two identifiers (not strings and no values bound to them yet): --------8<--------8<--------8<-------- /containers/json /containers/<some string in here>/json -------->8-------->8-------->8-------- I seem unable to find a way using syntax-rules to distinguish between these two, when they are only given as identifiers. So I looked into syntax-case, which has guards and thought, that I might be able to use guard clauses to check for substrings in the name of the identifier there. However, so far my attempts have failed to get it right and I am having difficulties finding any example for such a thing. I believe in principle it should be possible, however. It is just that I do not know how. Here is my current code, simplified: --------8<--------8<--------8<-------- (define-syntax identifier-name->string (lambda (stx) (syntax-case stx () ((_ id) (identifier? #'id) (datum->syntax #'id (symbol->string (syntax->datum #'id))))))) (define-syntax define-api-route (lambda (stx) (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH) [(_ route GET my-content-type) (string-match "<[^>]+>" (identifier-name->string (syntax route))) (syntax (quote aaa))]))) -------->8-------->8-------->8-------- When calling that I get an error: --------8<--------8<--------8<-------- (define-api-route /containers/<id>/json GET application/x-www-form-urlencoded) While compiling expression: In procedure symbol->string: Wrong type argument in position 1 (expecting symbol): #<syntax /containers/<id>/json> -------->8-------->8-------->8-------- I have to wrap `route` into (syntax …) though, because otherwise it complains about a reference to a pattern variable outside of a syntax form. I do not really understand this, but in all examples I have seen for guard clauses, pattern variables are wrapped in a call to syntax. However, identifier-name->string does not work with syntax objects. I am not sure how I can get the name of the identifier inside the guard clause as string, so that I can check for occurences of anything in angle braces <>. I've also tried multiple other approaches over the last 2 days, but always there was some error that made it seem like it was the wrong approach and fixing the errors led to other errors. For example the following also does not work (whole transcript): --------8<--------8<--------8<-------- GNU Guile 2.2.4 Copyright (C) 1995-2017 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(guile-user)> (use-modules (web uri) (web client) (json) (ice-9 iconv) (ice-9 regex)) scheme@(guile-user)> (define-syntax identifier-name->string (lambda (stx) (syntax-case stx () ((_ id) (identifier? #'id) (datum->syntax #'id (symbol->string (syntax->datum #'id))))))) scheme@(guile-user)> (define-syntax define-api-route-with-template-variables (syntax-rules (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH) [(_ route GET my-content-type) (syntax (define* (route url-temp-vars docker-socket #:key (data #f)) (let* ([route-as-string (identifier-name->string route)] [match-count (fold-matches "<[^>]+>" route-as-string 0 (λ (curr-match count) (+ count 1)))]) (cond [(= match-count (length url-template-vars)) ;; build the final request url ;; loop over available url template variables (let ([request-url (let loop ([constructed-route-string route-as-string] [remaining-url-template-vars url-temp-vars]) (cond ;; return the whole constructed request url [(null? remaining-url-template-vars) constructed-route-string] [else ;; replace another url template var and recur (loop (regexp-substitute #f (string-match "<[^>]+>" constructed-route-string) 'pre (car remaining-url-template-vars) 'post) (cdr remaining-url-template-vars))]))]) ;; do the call with the built request url (send-request-to-docker-socket request-url docker-socket my-content-type #:data data))] [else (error "wrong number of URL template variables" route route-as-string url-template-vars)]))))])) scheme@(guile-user)> (define-syntax define-api-route (lambda (stx) ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods ;; All HTTP methods are literals. (syntax-case stx (GET HEAD POST PUT DELETE CONNECT OPTIONS TRACE PATH) [(_ route GET my-content-type) (let ([route-as-string (identifier-name->string route)]) (string-match "<[^>]+>" route-as-string)) (define-api-route-with-template-variables route GET my-content-type)] [(_ route GET my-content-type) ;; optional guard clause, already implied by the position of this case of syntax (not (string-match "<[^>]+>" (identifier-name->string route))) (syntax (define* (route docker-socket #:key (data #f)) (call-with-values (lambda () (http-get (identifier-name->string route) #:port docker-socket #:version '(1 . 1) #:keep-alive? #f #:headers `((host . ("localhost" . #f)) (content-type . (my-content-type (charset . "utf-8")))) #:body (scm->json-string data) #:decode-body? #t #:streaming? #f)) (lambda (response response-text) (let ([resp-text-as-string (bytevector->string response-text "utf-8")]) (cons response resp-text-as-string))))))]))) scheme@(guile-user)> (define-api-route /containers/json GET application/x-www-form-urlencoded) scheme@(guile-user)> (define-api-route /containers/<id>/json GET application/x-www-form-urlencoded) scheme@(guile-user)> /containers/<id>/json $2 = #<procedure /containers/<id>/json (docker-socket #:key data)> scheme@(guile-user)> -------->8-------->8-------->8-------- As you can see, the /containers/<id>/json does not take template variable after it is defined, which means, that the syntax-case macro always goes into the other case and the check for <id> does not work. Apparently the regex is in this code checked for literally the string "route", which is not what I want, as route is only supposed to stand for whatever the user gives as identifier when calling the macro and I want to check inside that. My question now is: How can I check for the regular expression inside a name of an identifier inside a guard clause? If that is impossible, what other ways are there to distinguish between two macro expansions, where one has some <> part in the identifier and the other does not and get 2 different expansions? How is this usually done? I know I already took up some time of you before with my questions. I just cannot find examples for such things on my searches and do not have a lot experience writing macros. So thank you, if you can help me out again with this problem. I will also include a link to my repository, which should be publicly available, where a few versions of my attempts are in the commits: https://gitlab.com/ZelphirKaltstahl/guile-scheme-macros/tree/dev/procedure-defining Regards, Zelphir