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
