[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: for with break and continue
From: |
Jean Abou Samra |
Subject: |
Re: for with break and continue |
Date: |
Sun, 4 Sep 2022 12:43:58 +0200 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.2.0 |
Le 04/09/2022 à 11:54, Damien Mattei a écrit :
i try to make a for with break and continue the way C language do it, i
works with break but if i add a continue feature i then loose the break
feature, here is my code:
(define-syntax for/bc
(lambda (stx)
(syntax-case stx ()
((kwd (init test incrmt) body ...)
(with-syntax
((BREAK (datum->syntax #'kwd 'break)))
#'(call/cc
(lambda (escape)
(let-syntax
((BREAK (identifier-syntax (escape))))
init
(let loop ()
(when test
(with-syntax
((CONTINUE (datum->syntax #'kwd 'continue)))
#'(call/cc
(lambda (next)
(let-syntax
((CONTINUE (identifier-syntax (next))))
body ...)))
incrmt
(loop))))))))))))
The problem is with the meta level vs. the expanded output level. You have
two nested levels of #' . This (with-syntax ((CONTINUE ...)) ...) is part of
the expanded output, it doesn't run when your macro is expanded. The body of
the (expanded) loop just returns a syntax object. That's not what you want.
Here's a definition that works:
(define-syntax for/bc
(lambda (stx)
(syntax-case stx ()
((kwd (init test incrmt) body ...)
(with-syntax ((BREAK (datum->syntax #'kwd 'break))
(CONTINUE (datum->syntax #'kwd 'continue)))
#'(call/cc
(lambda (escape)
(let-syntax ((BREAK (identifier-syntax (escape))))
init
(let loop ()
(when test
(call/cc
(lambda (next)
(let-syntax ((CONTINUE (identifier-syntax (next))))
body ...)))
incrmt
(loop)))))))))))
(let ((i #f))
(for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
(when (< i 5)
continue)
(when (> i 9)
break)
(display i)
(newline)))
You could also use quasisyntax (#` and #,) to get the same effect:
(define-syntax for/bc
(lambda (stx)
(syntax-case stx ()
((kwd (init test incrmt) body ...)
#`(call/cc
(lambda (escape)
(let-syntax ((#,(datum->syntax #'kwd 'break)
(identifier-syntax (escape))))
init
(let loop ()
(when test
(call/cc
(lambda (next)
(let-syntax ((#,(datum->syntax #'kwd 'continue)
(identifier-syntax (next))))
body ...)))
incrmt
(loop))))))))))
That said, I would recommend using syntax parameters for break and continue.
They're cleaner, since they can be rebound by the user. Also, you can
use let/ec from (ice-9 control) instead of call/cc. It's more efficient
because it doesn't need to actually reify the whole environment, since
an escape continuation is upwards-only (it can be used inside the expression
to escape it, but it can't be used outside to reinstate its context).
(use-modules (ice-9 control))
(define-syntax-parameter break
(lambda (sintax)
(syntax-violation 'break "break outside of for/bc" sintax)))
(define-syntax-parameter continue
(lambda (sintax)
(syntax-violation 'continue "continue outside of for/bc" sintax)))
(define-syntax-rule (for/bc (init test increment) body body* ...)
(begin
init
(let/ec escape
(syntax-parameterize ((break (identifier-syntax (escape))))
(let loop ()
(when test
(let/ec next
(syntax-parameterize ((continue (identifier-syntax (next))))
body body* ...))
increment
(loop)))))))
(let ((i #f))
(for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
(when (< i 5)
continue)
(when (> i 9)
break)
(display i)
(newline)))
And here's an example showing the benefits of syntax parameters.
Add at the beginning of the code above:
(define-module (for)
#:export (break continue for/bc))
In the same directory, put a file rename.scm containing:
(use-modules ((for)
#:select ((break . for-break) continue for/bc))
(srfi srfi-1) ; contains a break procedure
(ice-9 receive))
(let ((i #f))
(for/bc ((set! i 0) (< i 10) (set! i (1+ i)))
(receive (before after)
(break (lambda (x)
(> x 5))
(iota i))
(when (pair? after)
for-break)
(display i))))
And run as
guile -L . rename.scm
As you can see, syntax parameters enable the code to use 'break'
for something else.
A final note: are you aware of the existence of 'do' in Scheme?
Most cases of a C for loop can be written elegantly using do.
https://www.gnu.org/software/guile/manual/html_node/while-do.html
Regards,
Jean