I've been using Guile for about a year, but am fairly inexperienced in using macros in Scheme. Although I have got some more complicated examples to work satisfactorily, I'm getting stuck on what (to me) feels like a really simple use case akin to simple substitution similar to what can be achieved with #define in C.
I have a function that uses cond to test several conditions, some which have a general-form. For example:
(define (file->list filename)
"Read input and split into list of
coordinates and folds."
(let ((lst (call-with-input-file filename
(λ (p)
(list-ec (:port line p read-line)
(cond
((string-any (cut eqv? <> #\,) line)
(string-split line #\,))
((string-null? line) #f) ;; blank line
((string= line "fold along x=" 1 13 1 13)
`(x ,(last (string-split line #\=))))
((string= line "fold along y=" 1 13 1 13)
`(y ,(last (string-split line #\=))))
(else (error "bad input!"))))))))
(parse-input lst)))
I'd like to get rid of the repetition around conditions of the form below:
((string= line "fold along x=" 1 13 1 13)
`(x ,(last (string-split line #\=))))
This feels to me like a macro because this boilerplate can be generated at compile time using pattern matching - and I have naively tried something like this:
(define-syntax-rule (fold-parse str x-or-y)
`((string= ,str
,(string-append "fold along " (symbol->string x-or-y) "=")
1 13 1 13)
(x-or-y ,(string->number (last (string-split str #\=))))))
This does reproduce the (test expression) s-expression at the REPL:
scheme@(guile-user)> (fold-parse "fold along x=3" 'x)
$33 = ((string= "fold along x=3" "fold along x=" 1 13 1 13) ((quote x) 3))
scheme@(guile-user)>
But when I try to slot the macro into my cond I get the following error:
;;; WARNING: compilation of /home/foo/dev/aoc_2021/13/./13.scm failed:
;;; Syntax error:
;;; /home/foo/dev/aoc_2021/13/./13.scm:53:28: source expression failed to match any pattern in form fold-parse
ice-9/psyntax.scm:2794:12: In procedure syntax-violation:
Syntax error:
unknown location: source expression failed to match any pattern in form fold-parse
I'm naively adding it just like below - I've commented out the boilerplate in the cond around the "fold along x=" boilerplate its meant to replace:
(define (file->list filename)
"Read input and split into list of
coordinates and folds."
(let ((lst (call-with-input-file filename
(λ (p)
(list-ec (:port line p read-line)
(cond
((string-any (cut eqv? <> #\,) line)
(string-split line #\,))
((string-null? line) #f) ;; blank line
(fold-parse line 'x)
;;((string= line "fold along x=" 1 13 1 13)
;; `(x ,(last (string-split line #\=))))
((string= line "fold along y=" 1 13 1 13)
`(y ,(last (string-split line #\=))))
(else (error "bad input!"))))))))
(parse-input lst)))
Since this attempt, I've been down a bit of rabbit-hole of syntax-case, quasisyntax, and numerous other variations on the macro and cond to try to make it work.
However, I'm obviously not getting something fundamentally important about the way macros can "drop-in replace" a snippet or part of an expression in their place.
Can anyone help me see the error of my ways?
How do I write a macro that can generate a test and expression to be used inside a cond-clause? And - is it a reasonable/sensible thing to do?