diff --git a/index.scrbl b/index.scrbl index 3e82f39..2e777ce 100644 --- a/index.scrbl +++ b/index.scrbl @@ -20,6 +20,7 @@ @include-example{optional-assert} @include-example{cross-macro-communication} @include-example{let-star} +@include-example{while-break} @include-example{def} @include-example{conditional-require} @include-example{multi-check-true} diff --git a/while-break/while-break-test.rkt b/while-break/while-break-test.rkt new file mode 100644 index 0000000..8f2665f --- /dev/null +++ b/while-break/while-break-test.rkt @@ -0,0 +1,30 @@ +#lang racket/base +(module+ test + (require rackunit racket/port syntax-parse-example/while-break/while-break) + + (test-begin + (define x 5) + + (test-case "ex1" + (check-equal? + (with-output-to-string + (lambda () + (while (> x 0) + (displayln x) + (set! x (sub1 x))))) + "5\n4\n3\n2\n1\n")) + + (set! x 5) + + (test-case "ex2" + (check-equal? + (with-output-to-string + (lambda () + (while #t + (displayln x) + (set! x (sub1 x)) + (unless (> x 0) + (break))))) + "5\n4\n3\n2\n1\n"))) + +) diff --git a/while-break/while-break.rkt b/while-break/while-break.rkt new file mode 100644 index 0000000..b150cf6 --- /dev/null +++ b/while-break/while-break.rkt @@ -0,0 +1,17 @@ +#lang racket/base +(provide while break) +(require syntax/parse/define racket/stxparam (for-syntax racket/base)) + +(define-syntax-parameter break + (lambda (stx) + (raise-syntax-error (syntax-e stx) "can only be used inside `while`"))) + +(define-syntax-parse-rule (while condition body ...) + (call/ec + (λ (return) + (syntax-parameterize ([break (make-rename-transformer #'return)]) + (let loop () + (when condition + (begin body ... + (loop)))))))) + diff --git a/while-break/while-break.scrbl b/while-break/while-break.scrbl new file mode 100644 index 0000000..2a616af --- /dev/null +++ b/while-break/while-break.scrbl @@ -0,0 +1,46 @@ +#lang syntax-parse-example +@require[ + (for-label racket/base syntax/parse syntax-parse-example/while-break/while-break)] + +@(define while-break-eval + (make-base-eval '(require syntax-parse-example/while-break/while-break))) + +@title{Basic while loop equipped with break} +@stxbee2021["countvajhula" 20] + +@; ============================================================================= + +@defmodule[syntax-parse-example/while-break/while-break]{} + +@defform[(while test body ...+)]{ + + @examples[#:eval while-break-eval + (define x 5) + + (while (> x 0) + (displayln x) + (set! x (sub1 x))) + + (set! x 5) + + (while #t + (displayln x) + (set! x (sub1 x)) + (unless (> x 0) + (break))) + ] + + + @; TODO add description here +} + +@defidform[break]{ + May only appear within an @racket[while] form. +} + +The macro uses an escape continuation to provide the semantics of @racket[break], and +leverages it using a syntax parameter so that the continuation is accessible +in the lexical scope of the @racket[while] body, and also so that @racket[break] is a syntax +error outside the @racket[while] loop. + +@racketfile{while-break.rkt}