Macro that checks string value at compile time

I'm playing around with the idea of a macro that can, at compile time, find syntax errors in SQL queries. I'm very new to Racket in general and so for now I'm simply trying to write a macro that won't compile if I provide a string that contains a specific substring. I can't figure out how to do this and would love some pointers:

#lang racket

(define-for-syntax (contains-secret? str)
  (regexp-match? #rx"secret" str))

; How can I use contains-secret? in here?
; When I try and check `s` I get the error: s: pattern variable cannot be used outside of a template in: s
(define-syntax (foo str)
  (syntax-case str ()
    [(_ s) (syntax "redacted")]
    [(_ _) (syntax str)]))
    
(foo "secret") ; Should return "redacted"
(foo "anything else") ; Should return "anything else"

I was thinking I could use fender expression but I can't get that to work.

1 Like

Here's a version using fender expressions, and a version using syntax-parse which I think is nicer:

#lang racket

(require (for-syntax syntax/parse))

(define-for-syntax (contains-secret? str)
  (regexp-match? #rx"secret" str))

; How can I use contains-secret? in here?
; When I try and check `s` I get the error: s: pattern variable cannot be used outside of a template in: s
(define-syntax (foo str)
  (syntax-case str ()
    [(_ s)
     (and (string? (syntax-e #'s)) (contains-secret? (syntax-e #'s)))
     (syntax "redacted")]
    [(_ s) (syntax s)]))
    
(foo "secret") ; Should return "redacted"
(foo "anything else") ; Should return "anything else"


(define-syntax (foo* stx)
  (syntax-parse stx
    [(_ s:str)
     #:when (regexp-match? #rx"secret" (syntax-e #'s))
     #'"redacted"]
    [(_ s:str) #'s]))

(foo* "secret") ; Should return "redacted"
(foo* "anything else") ; Should return "anything else"
3 Likes

Thanks! The first answer using syntax-case works but the second using syntax-parse fails with this error:

 _: wildcard not allowed as an expression
  after encountering unbound identifier (which is possibly the real problem):
   syntax-parse in: (_ s:str)

I think you need to include the addition require that I added to the program.

2 Likes

Inspired by the solution of samth:

#lang racket

(require (for-syntax syntax/parse))

(begin-for-syntax
  (define (contains-secret? str)
    (regexp-match? #rx"secret" str))

  (define-syntax-class secret-str 
    #:description "string containing secret"
    (pattern s:str
             #:fail-when (not (contains-secret? (syntax-e #'s))) 
                         "did not contain secret")))

(define-syntax (foo stx)
  (syntax-parse stx
    [(_ s:secret-str) #'"redacted"]
    [(_ s:str)        #'s]))

(foo "secret") ; Should return "redacted"
(foo "anything else")