Re: reg exp / quasiquote in 0.9.4 (the TinyScheme to s7 switch)

41 views
Skip to first unread message

Ben Swift

unread,
Jun 19, 2026, 3:45:26 AM (11 days ago) Jun 19
to extemp...@googlegroups.com
Hi Minoru,

Nice detective work --- you've found the cause in both cases. 0.9.4 switched
the Scheme interpreter from TinyScheme to s7, which is much stricter about
R7RS, so both of your reports are really the same thing underneath: s7 errors
on input that TinyScheme quietly tolerated.

Backslashes in regexes. The error is actually in the string reader, not the
regex engine. s7 only accepts known string escapes (\n, \t, \\, \", \x..; and
so on) and rejects the rest, so "\." dies before the regex ever sees it. The
rule is exactly the one you found: double any backslash that isn't a Scheme
escape, so \. becomes \\., \d becomes \\d, and so on. (Our own stdlib already
does this throughout, which is why it went unnoticed.)

unquote-splicing (,@). It now has to splice a proper list, and only inside a
list or vector template. So `(,@10) and a top-level `,@x are both errors; use
, for a single value: `(,*m1*) gives (10).

Your t5 macro only ever worked for exactly one trailing argument. The body
`(list ,a1 ',@b1) expands ',@b1 to (quote <spliced>), so an empty b1 gives
(quote) and two-or-more args give (quote a b) --- both malformed. If the intent
is to quote each rest argument, do the quoting inside the splice:

(define-macro (t5 a1 . b1)
`(list ,a1 ,@(map (lambda (x) `(quote ,x)) b1)))

(t5 'a) ; (a)
(t5 'a 'b) ; (a 'b)
(t5 'a 'b 'c) ; (a 'b 'c)

Cheers,
Ben

Minoru

unread,
Jun 20, 2026, 12:51:34 AM (10 days ago) Jun 20
to Extempore
Hi Ben,

Thanks so much for your explanations and ideas,
I didn't know 'define-macro*'.....

I thougt it's a reader macro error when one slash error occurs in read-eval-print loop, too.
Your nice explanations said so then I was convinced ... thanks again !!

The trouble with optional arg is just simple, evaluating the macro func has opt arg or not ....
My macro is ....

(define-macro (mklisgen2 n f . bsp)
  (let ((n1 (gensym))
        (opt (gensym))
        (ans (gensym))
        (ans-all (gensym)))
    `(let loop ((,n1 ,n)
                ;; org in ver 0.8.9, but error in ver 0.9.4 when no opt arg.
                ;; (,opt (if (not (null? ',@bsp)) ,@bsp '()))  ; <----

                ;; 2026-06-13, fixed, ok in ver 0.9.4.
                (,opt (if (not (null? ',bsp)) (eval (car ',bsp)))) ; <---

                (,ans ,f)
                (,ans-all '()))
       (if (not (null? ,opt))
           (if (number? ,ans) ;single note is a number, chord is a list.
               (set! ,ans (+ ,opt ,ans))
               (set! ,ans (map (lambda (p) (+ ,opt p))
                               ,ans))))
       (if (= ,n1 1)
           (reverse (cons ,ans ,ans-all))
           (loop (- ,n1 1)
                 (if (not (null? ,opt)) ,@bsp ,opt)
                 ,f
                 (cons ,ans ,ans-all))
           ))))


(mklisgen2 4 (random '(0 4 7)) )
;; -> (4 7 4 0), (7 0 0 7), (4 4 7 7), etc ...

(mklisgen2 4 (random '((0 4 7) (2 5 9) (5 9 4))) )
;; ->((5 9 4) (5 9 4) (2 5 9) (0 4 7)), etc ...


;; if opt arg, it is added to each
(define *bsp* 60)
(mklisgen2 4 (random '((0 4 7) (2 5 9) (5 9 4))) *bsp*)
;; -> ((60 64 67) (65 69 64) (62 65 69) (60 64 67)), etc


(sys:load "libs/core/pc_ivl.xtm")
(mklisgen2 4 (pc:make-chord 60 72 (random 1 4) (random '((0 4 7) (2 5 9) (5 9 4)))))
;; -> ((69) (60 64 67) (64) (65 69)), etc

(define *oct1* '(random '(0 12 -12)))
(mklisgen2 4 (pc:make-chord 60 72 (random 1 4) (random '((0 4 7) (2 5 9) (5 9 4)))) (eval *oct1*))
;; -> ((72) (48 55) (72 76 79) (53 57))

(define *kplus* '(random '(0)))
(define *bsp1* '(+ (eval *kplus*) (random '(60))))
(define *bsp2* '(+ (eval *kplus*) (random '(48 60 72))))

(mklisgen2 3 (random '((0 4 7) (2 5 9) (9 0 4))) (eval *bsp2*))
;; -> ((72 76 79) (50 53 57) (81 72 76)), etc

(mklisgen2 3 (random '((0 4 7) (2 5 9) (9 0 4))) (random '(48 60 72)))
;; same as above

(mklisgen2 3 (pc:diatonic 0 '^ (random '(i ii iv vi))) (eval *bsp2*))
;; ->((74 77 81) (53 57 48) (65 69 60)), etc ...

2026年6月19日金曜日 16:45:26 UTC+9 ben:

Ben Swift

unread,
Jun 20, 2026, 2:53:48 AM (10 days ago) Jun 20
to extemp...@googlegroups.com
Hi Minoru,

Glad that sorted it --- I ran your mklisgen2 through 0.9.4 here and every one of your examples behaves exactly as you've shown, so your fix is spot on.

One small thing worth knowing for later. Your (if (not (null? ',bsp)) (eval (car ',bsp))) has no else branch, so when there's no optional argument it evaluates (if #f ...), and extempore's s7 happens to hand that back as the empty list --- which is why null? catches it and the offset gets skipped. It works, but it's leaning on a quirk that a stricter Scheme wouldn't give you.

Since you liked the look of define-macro*, here's a version that does the same job without the quote/eval dance and makes the no-argument case explicit:

(define-macro* (mklisgen2 n f (bsp #f))
(let ((ans (gensym)) (off (gensym)))
`(map (lambda (_)
(let ((,ans ,f) (,off ,bsp))
(if ,off
(if (number? ,ans) (+ ,off ,ans) (map (lambda (p) (+ ,off p)) ,ans))
,ans)))
(make-list ,n #t))))

The (bsp #f) gives the optional argument a default, so you just test ,off directly instead of poking at a rest list. The (map (lambda (_) ...) (make-list n #t)) is simply "do this n times and collect the results" --- the #t is filler we ignore. And because ,off is bound fresh on each pass, a generator offset is re-rolled
once per element but stays constant within a chord, just like yours:

(mklisgen2 4 (random '(0 4 7))) ; (0 7 7 4)
(mklisgen2 4 (random '((0 4 7) (2 5 9))) 60) ; ((62 65 69) (60 64 67) ...)
(mklisgen2 6 (random '((0 4 7) (9 0 4))) (random '(48 60 72))) ;; ((60 64 67) (72 76 79) (69 60 64) (48 52 55) ...) -- different base per chord

Your original is perfectly fine as it stands, though --- this is just if you fancy the tidier form.

Cheers,
Ben
>--
>You received this message because you are subscribed to the Google Groups "Extempore" group.
>To unsubscribe from this group and stop receiving emails from it, send an email to extemporelan...@googlegroups.com.
>To view this discussion visit https://groups.google.com/d/msgid/extemporelang/bed9a7c3-162f-4148-b408-c80017eb5142n%40googlegroups.com.


--

Cheers,
Ben

Minoru

unread,
Jun 20, 2026, 11:31:34 PM (9 days ago) Jun 20
to Extempore
Hi Ben,

About the local variable of let and if expression's return value, on lisp and scheme, results are .....


;;;;;;;;;; lisp (elisp)

(let ((a1))
  a1)
; -> nil,  yes this is lisp !

(let ((a1 (if (> 10 0) 10)))
  a1)
; ->10

(let ((a1 (if (> 10 1000) 10)))
  a1)
; -> nil

(if (> 10 1000) 10)
; -> nil


;;;;;;;;; in ver 0.9.4 extempore/scheme(s7)

(let ((a1))
  a1)
; -> error, yes, this is scheme !
; -> BUT NIL without error in ver 0.8.9 like lisp !!

(let ((a1 (if (> 10 0) 10)))
  a1)
; ->10

(let ((a1 (if (> 10 1000) 10)))
  a1)
; -> NIL

(if (> 10 1000) 10)
; -> NIL

()
; -> NIL

(> 10 1000)
; -> #f

(> 10 0)
; #t

The one branch if statement returns NIL when the condition is false, as I think so ....
It looks like ok, no problem !!

On the other hand, I read some doccuments like this .....

;; Of course, you can also write a one-branch if, with no "else" clause.
;;
;; (if (some-test)
;;     (some-action))
;;
;; The return value of a one-branch if is unspecified in the case the condition is false,
;; so if you're interested in the return value, you should use a two-branch if,
;; and explicitly specify what should be returned in both cases.

Wah, unspecified return value !!
It means that the return balue of each scheme implementation may be different !
So I added the else-clause to my mklisgen2, now it has a two-branch if statement.

I appreciate your kind and appropriate advices.

2026年6月20日土曜日 15:53:48 UTC+9 ben:

Ben Swift

unread,
Jun 21, 2026, 12:32:10 AM (9 days ago) Jun 21
to extemp...@googlegroups.com
Hi Minoru,

Yes --- "unspecified" means each implementation can hand back whatever it likes, so the two-branch if is the robust move: your code no longer leans on any one interpreter's choice.

Same theme we started on --- s7 holds you to the standard where TinyScheme improvised. Your (let ((a1)) a1) is another instance: s7 insists every binding has a value, where lisp (and old TinyScheme) quietly filled in nil.

Nice detective work. Enjoy the music-making.

Cheers,
Ben
>To view this discussion visit https://groups.google.com/d/msgid/extemporelang/1942f02c-71c9-4a00-bda1-82f0fd9be4ben%40googlegroups.com.


--

Cheers,
Ben

Minoru

unread,
Jun 21, 2026, 10:38:45 PM (8 days ago) Jun 21
to Extempore
Hi Ben,

I'm very2 glad to keep on extempore-ing after the end of Rosseta2 support.
I appreciate Ben, Andrew and others, it's very nice works for those who love this extempore and lisp/scheme language.


p.s.  I found my some funcs didn't work then this time I could find it out very easily because some variables of 'let' have no initial value !

2026年6月21日日曜日 13:32:10 UTC+9 ben:

Ben Swift

unread,
Jun 21, 2026, 11:43:20 PM (8 days ago) Jun 21
to extemp...@googlegroups.com
You're very welcome mate :)

Ben
>To view this discussion visit https://groups.google.com/d/msgid/extemporelang/94dd6c3b-2177-4b6a-ae40-27efbc2fe08bn%40googlegroups.com.


--

Cheers,
Ben
Reply all
Reply to author
Forward
0 new messages