Moved from PLaneT to pkg.racket-lang.org

This commit is contained in:
Jens Axel Søgaard 2015-04-25 23:15:49 +02:00
parent 41b78ccd74
commit 6624de6389
9 changed files with 808 additions and 0 deletions

240
control/begin-with-goto.rkt Normal file
View File

@ -0,0 +1,240 @@
#lang racket
(provide begin/goto)
;;; begin-with-goto.scm -- Jens Axel Søgaard -- 7th July 2007
; This file implements a simple begin with gotos.
; For a more general version use tagged-begin.
; See the bottom of this file for examples.
#;(begin/goto
(label foo)
1
(label bar)
(goto foo))
; =>
#;(letrec ([foo (lambda ()
1
(goto bar))]
[bar (lambda ()
(goto foo))])
(foo))
#;(require-for-syntax
(only (lib "1.ss" "srfi") take-while)
(only (lib "1.ss" "srfi") drop-while)
(only (lib "1.ss" "srfi") filter)
(prefix srfi: (lib "1.ss" "srfi"))
(lib "stx.ss" "syntax"))
(require (for-syntax racket/list syntax/stx))
(define-for-syntax (label? stx)
(syntax-case stx (label)
[(label label-name) #t]
[_else #f]))
(define-for-syntax (goto? stx)
(syntax-case stx (goto)
[(goto label-name) #t]
[_else #f]))
(define-for-syntax (non-label? stx)
(not (label? stx)))
(define-for-syntax (first-label-and-block+more stx)
(syntax-case stx (label)
[((label label-name) label-or-expr ...)
(with-syntax ([(expr ...)
(let ([exprs
(takef (syntax->list #'(label-or-expr ...))
non-label?)])
(if (null? exprs) (list #'(void)) exprs))]
[more
(dropf (syntax->list #'(label-or-expr ...))
non-label?)])
(values #'(label-name (expr ...))
#'more))]))
(define-for-syntax (labels-and-exprs->blocks stx)
(syntax-case stx (label)
[() '()]
[_else (let-values ([(first more) (first-label-and-block+more stx)])
(cons first (labels-and-exprs->blocks more)))]))
(define-for-syntax (name-of-label stx)
(syntax-case stx (label)
[(label name) #'name]))
(define-for-syntax (error-check-begin/goto stx)
(syntax-case stx ()
[(_ label-or-expr ...)
(let* ([labels (filter label? (syntax->list #'(label-or-expr ...)))]
[names (map name-of-label labels)])
; Are all labels identifiers?
(for-each (lambda (name)
(unless (identifier? name)
(raise-syntax-error 'begin/goto
"labels must be identifiers" name)))
names)
; Are the duplicate labels?
(cond
[(check-duplicate-identifier names)
=> (lambda (name)
(raise-syntax-error 'begin/goto
"duplicate label found: "
name))]))]))
(define-for-syntax (introduce-labels-after-goto stx)
(syntax-case stx (goto label)
[((goto label-name) (label label-name1) label-or-expr ...)
(with-syntax ([((goto label-name) (label label-name1) label-or-expr ...)
stx])
(with-syntax ([(label-or-expr ...)
(introduce-labels-after-goto
#'(label-or-expr ...))])
(syntax/loc stx
((goto label-name) (label label-name1) label-or-expr ...))))]
[((goto label-name) expr label-or-expr ...)
(with-syntax ([((goto label-name) expr label-or-expr ...) stx])
(with-syntax ([(lab) (generate-temporaries (list #'lab))])
(introduce-labels-after-goto
(syntax/loc stx
((goto label-name) (label lab) expr label-or-expr ...)))))]
[(label-or-expr1 label-or-expr ...)
(with-syntax ([(label-or-expr ...)
(introduce-labels-after-goto
#'(label-or-expr ...))])
(syntax/loc stx
(label-or-expr1 label-or-expr ...)))]
[_else
stx]))
(define-syntax (begin/goto stx)
(error-check-begin/goto stx)
(syntax-case stx (label)
[(_)
#'(void)]
[(_ (label start) label-or-expr ...)
(with-syntax ([(label-or-expr ...)
(introduce-labels-after-goto #'(label-or-expr ...))])
(with-syntax ([((label-name (expr ... last-expr)) ... (end-label-name (end-expr ...)))
(labels-and-exprs->blocks #'((label start) label-or-expr ...))])
(with-syntax ([(next-label ...)
(cdr (syntax->list #'(label-name ... end-label-name)))])
(with-syntax ([(continue ...)
(map (lambda (last-expr next-label)
(syntax-case last-expr (goto)
[(goto name) last-expr]
[_else #`(begin #,last-expr (#,next-label))]))
(syntax->list #'(last-expr ...))
(syntax->list #'(next-label ...)))])
(with-syntax ([(name1 ...)
(map name-of-label
(filter label? (syntax->list #'((label start) label-or-expr ...))))])
(with-syntax ([goto (syntax-local-introduce #'goto)])
(syntax/loc stx
(letrec-syntaxes+values
([(goto) (lambda (stx)
(syntax-case stx (goto)
[(_ name)
(begin
(unless (identifier? #'name)
(raise-syntax-error 'goto "identifier expected" #'name))
(cond
[(memf (λ(x)
; was module-identifier=?
(module-or-top-identifier=? x #'name))
(syntax->list #'(name1 ...)))
(syntax/loc stx
(name))]
[else
(raise-syntax-error 'goto "unknown label" stx)]))]
[_else
(raise-syntax-error 'goto "expected (goto <label>), got" stx)]))])
([(label-name) (lambda () expr ... continue)]
...
[(end-label-name) (lambda () end-expr ...)])
(start)))))))))]
[(_ expr label-or-expr ...)
(syntax/loc stx
(begin/goto (label start) expr label-or-expr ...))]))
;(require begin-with-goto)
;(require (planet "78.ss" ("soegaard" "srfi.plt")))
;
(module+ test (require rackunit)
(check-equal? (begin/goto) (void))
(check-equal? (begin/goto 1) 1)
(check-equal? (begin/goto 1 2) 2)
(check-equal? (begin/goto 1 2 3) 3)
(check-equal? (begin/goto (label l1)) (void))
(check-equal? (begin/goto (label l1) 1) 1)
(check-equal? (begin/goto (label l1) 1 2) 2)
(check-equal? (begin/goto (label l1) (label l2)) (void))
(check-equal? (begin/goto (label l1) (label l2) 1) 1)
(check-equal? (begin/goto (label l1) (label l2) 1 2) 2)
(check-equal? (begin/goto (goto l1) (label l1)) (void))
(check-equal? (begin/goto (goto l1) (label l1) 1) 1)
(check-equal? (begin/goto (goto l1) (label l1) 1 2) 2)
(check-equal? (begin/goto (goto l1) 3 (label l1)) (void))
(check-equal? (begin/goto (goto l1) 3 (label l1) 1) 1)
(check-equal? (begin/goto (goto l1) 3 (label l1) 1 2) 2)
(check-equal? (begin/goto (goto l1) (label l2) (label l1)) (void))
(check-equal? (begin/goto (goto l1) (label l2) (label l1) 1) 1)
(check-equal? (begin/goto (goto l1) (label l2) (label l1) 1 2) 2)
(check-equal? (begin/goto (label l2) (label l1)) (void))
(check-equal? (begin/goto (label l2) (label l1) 1) 1)
(check-equal? (begin/goto (label l2) (label l1) 1 2) 2)
(check-equal? (begin/goto (label l2) (goto l1) (label l1)) (void))
(check-equal? (begin/goto (label l2) (goto l1) (label l1) 1) 1)
(check-equal? (begin/goto (label l2) (goto l1) (label l1) 1 2) 2)
(check-equal? (let ([x 1])
(begin/goto (label l1)
(set! x (+ x 1))
(if (= x 10000)
(goto l2) ; sadly not tail-recursive (use tagged-begin instead)
(goto l1))
(label l2)
x))
10000)
(check-equal? (let ([x 1])
(let/ec return
(begin/goto
(label l1)
(set! x (+ x 1))
(when (= x 10000000)
(return x))
(goto l1)))) ; this is tail-recursive
10000000)
(check-equal? (let ([x 1])
(let/ec return
(begin/goto
(label l1)
(set! x (+ x 1))
(when (= x 10000000)
(return x))
(goto l1) ; unless this is tail-recursice, the stack will blow
2
)))
10000000)
;; The following must raise syntax errors
;(begin/goto (label dup) (label dup)) ; duplicate label
;(begin/goto (goto l1)) ; non-existing label
)

40
control/dotimes.rkt Normal file
View File

@ -0,0 +1,40 @@
#lang racket
(provide dotimes)
(require (for-syntax syntax/parse))
; macro: (dotimes (var expr [finally]) body ...)
; dotimes iterates over a series of integers.
; dotimes evaluates expr and signals an error if the result
; is not an integer. If expr is zero or negative, the
; body is not executed. Otherwiese dotimes executed the body
; for each integer from 0 up to but not including the value of expr.
; During the evaluation of body, var is bound to each integer.
; Then finally is evaluated if present, and the result is returned,
; otherwise #void is returned. At the time finally is evaluated,
; var is bound to the number of times body was excuted.
(define-syntax (dotimes stx)
(syntax-parse stx
[(_dotimes (var count-form finally) body ...)
#`(let ([count count-form])
(unless (integer? count)
(raise-syntax-error 'dotimes
(format "expected integer as result of expression, got ~s " count)
#'count-form))
(if (positive? count)
(let ([var 0])
(let loop ([i 0])
(set! var i)
(if (< i count)
(let ()
body ...
(loop (add1 i)))
finally)))
(let ([var 0])
finally)))]
[(_dotimes (var count-form) body ...)
#'(dotimes (var count-form (void)) body ...)]
[_
(raise-syntax-error
#f "bad syntax, (dotimes (var expr [finally-expr]) body ...) expected" stx)]))

11
control/info.rkt Normal file
View File

@ -0,0 +1,11 @@
#lang setup/infotab
(define name "Control")
(define blurb
(list "Control Structures: while, until, begin/goto, tagbody, dotimes"))
(define scribblings '(["scribblings/control-manual.scrbl"]))
(define categories '(devtools))
(define version "3.0")
(define primary-file "main.rkt")
(define compile-omit-paths '("tests" "scribblings"))
(define release-notes (list))
;(define repositories '("4.x"))

12
control/main.rkt Normal file
View File

@ -0,0 +1,12 @@
#lang racket
(require "dotimes.rkt"
"tagged-begin.rkt"
"until.rkt"
"while.rkt"
"begin-with-goto.rkt")
(provide dotimes
tagged-begin
until
while
begin/goto)

View File

@ -0,0 +1,213 @@
#lang scribble/doc
@(require scribble/manual
scribble/eval
control
(for-label racket control))
@defmodule[control]
@title{Control}
@index{control}
@index{CL}
@index{tagbody}
@index{knuth}
@index{go}
@index{return}
@index{loop}
@index{iteration}
@index{goto}
@index{label}
@section{History}
Version 2.0
- added documentation in Scribble form
Version 1.1
- added @scheme[begin/goto]
Version 1.0
- initial version, available control structures:
@scheme[while], @scheme[until], @scheme[dotimes], and @scheme[tagged-begin]
@section{Control Structures}
@defform[(while test body)]{
Syntax: @scheme[_test] should be an expression and @scheme[_body] a
sequence of one or more expressions.
Semantics: @scheme[while] is an iteration construct. Each iteration begins by
evaluating the @scheme[_test] expression. If it evaluates to a
true value, the @scheme[_body] expressions are evaluated
sequentially from left to right, then the next iteration
begins. If the @scheme[_test] expression evaluates to false then
iteration stops and an unspecified value is returned
as the result of the while-expression.
Example: @interaction[
(require control)
(let ([n 3] [sum 0])
(while (> n 0)
(set! sum (+ sum n))
(set! n (- n 1)))
sum)]
}
@defform[(until test body)]{
Syntax: @scheme[_test] should be an expression and @scheme[_body] a
sequence of one or more expressions.
Semantics: @scheme[until] is an iteration construct. Each iteration
begins by evaluating the @scheme[_body] expressions
sequentially from left to right. The @scheme[_test]
expression is then evaluated. If the result is
a true value, then the next iteration begins.
Otherwise the iteration stops and unspecified
value is returned as the result of the
until-expression.
Example: @interaction[
(require control)
(let ([n 3] [sum 0])
(until (= n 1)
(set! sum (+ sum n))
(set! n (- n 1)))
sum)]
}
@defform[(dotimes (variable expression [finally]) body)]{
Syntax: @scheme[_variable] should be an identifier, @scheme[_expression]
and @scheme[_finally] (if present) should be expressions and
@scheme[_body] a sequence of one or more expressions.
Semantics: @scheme[dotimes] is an iteration contructs. Evalutations begins
by evaluating @scheme[_expression]. If the result is not an
integer an error is signaled. If the result is zero or
negative, the @scheme[_body] expressions are not evaluated.
Otherwise the @scheme[_body] expressions are evaluated for each
integer from 0 up to but not including the result of
@scheme[_expression].
During each evaluation of the @scheme[_body] expressions,
@scheme[_variable] is bound to each integer.
When the iteration stops @scheme[_finally] is evaluated if
present and the result returned, otherwise @schemeresult[void] is
returned. During evaluation of @scheme[_finally]
the @scheme[_variable] is bound to the number of times the
body were evaluated.
Examples: @interaction[
(require control)
(let ((xs '()))
(dotimes (x 5)
(set! xs (cons x xs)))
xs)
(let ((xs '()))
(dotimes (x 5 (list xs x))
(set! xs (cons x xs))))]
}
@defform[(tagged-begin (tag / expression)* )]{
Syntax: @scheme[_tag] should be a symbol, and all @scheme[_tag]s should be different.
Motivation: The macro @scheme[tagged-begin] is inspired by the Common Lisp
construct @schemeid[tagbody].
Semantics: The @scheme[tagged-begin] expression evaluates the expressions
in a lexical environment, where @scheme[go] and @scheme[return] are
are bound to functions of one argument, which will
transfer control when called.
As main rule the expressions will be evaluated sequentially
from left to right. When there are no more expressions to
be evaluated @schemeresult[void] is returned.
If an expression evaluates (go @scheme[_tag] then control is transfered
to the expression following the tag. The tags have lexical scope.
The dynamic extent of tag is indefinite. An @scheme[(go tag)] is allowed to
tranfer control to an outer tagged-begin. The call @scheme[(go tag)] has the
proper tail recursive property, even in situation where the call
syntactically is not in tail position.
If @scheme[(return _expression)] is evaluted, the value of @scheme[_expression] is
returned as the value of the entire @scheme[tagged-begin] form.
Examples: @interaction[
(require control)
(let ([i 0])
(tagged-begin
loop (set! i (+ i 1))
(when (< i 41) (go loop)))
i)
(let ([odd-numbers '()]
[a 0])
(tagged-begin
start (set! a 0)
on-odd (set! a (+ a 1))
(set! odd-numbers (cons a odd-numbers))
(cond
[(>= a 9) (go end)]
[(even? a) (go on-even)]
[else (go on-odd)])
on-even (set! a (+ a 1))
(go on-odd)
end)
odd-numbers)]
References: "Applications of Continuations" of Daniel P. Friedman.
}
@defform[(begin/goto label-or-goto-or-expression* )]{
Syntax: @scheme[_label-or-goto-or-expression] is
either @scheme[(label _identifier)]
or @scheme[(goto _identifier)]
or @scheme[_expression].
Motivation: Think of @scheme[begin/goto] as a normal @scheme[begin], where
@scheme[goto] can be used to jump to a control point
named by a label. An @scheme[(goto _identifier)] will
transfer control to the point named by the identifier.
If the @scheme[goto-form] is one of the @scheme[_label-or-goto-expression],
then a goto doesn't grow the control context.
Examples:
@interaction[
(require control)
(let ([x 1])
(let/ec return
(begin/goto
(label l1)
(set! x (+ x 1))
(when (= x 10000000)
(return x))
(goto l1)))) ; this is tail-recursive
(let ([x 1])
(let/ec return
(begin/goto
(label l1)
(set! x (+ x 1))
(when (= x 10000000)
(return x))
(goto l1) ; this is tail-recursive
2
)))]
}
@index-section{}

254
control/tagged-begin.rkt Normal file
View File

@ -0,0 +1,254 @@
#lang racket
(provide tagged-begin)
;;; INTRODUCTION
; This is a little macro that resembles the Common Lisp tagbody construct
; <http://www-2.cs.cmu.edu/Groups/AI/html/hyperspec/HyperSpec/Body/speope_tagbody.html#tagbody>
; See also "Applications of Continuations" of Daniel P. Friedman.
;;; MOTIVATION
; Many algorithms is specified in an imperative manner
; in the literature [See Example 5 from Knuth]. For a no-brain-
; conversion to Scheme tagged-begin is convenient.
;;; SYNTAX
; (tagged-begin
; (<tag> | <expression>)* )
; where <tag> is a symbol and duplicate tags are not allowed.
;;; SEMANTICS
; The form evaluates the expressions in a lexical environment
; that provides functions go and return both of one argument to
; transfer control.
; The expressions in tagged-begin are evaluated sequentially.
; If no expressions are left (void) is returned.
; If an expression evaluates (go tag) then control is transfered
; to the expression following tag. The tags have lexical scope.
; The dynamic extent of tag is indefinite. An (go tag) is allowed
; to tranfer control to an outer tagbody. The call (go tag) has the
; proper tail recursive property, even in situation where the call
; syntactically is not in tail position.
; If (return <expression>) is evaluted, the value of <expression> is
; the value of the entire tagged-begin form.
;;; EXAMPLES
; See below the implementation.
;;; IMPLEMENTATION
; Tagged begin is here implemented as a syntax-case macro.
; The rewrite rule is taken from Daniel P. Friedmans
; "Applications of Continuations".
; (tagged-begin
; tag_1 e1 ... ; If the body doesn't begin with a tag
; ... ; the macro inserts a fresh one
; tag_n-1 e_n-1 ...
; tag_n en ...)
; expands to
; ((let/cc go
; (let ([return (lambda (v) (go (lambda () v)))])
; (letrec
; ([tag_1 (lambda () e1 ... (tag2))]
; ...
; [tag_n-1 (lambda () e_n-1 ... (tag_n))]
; [tag_n (lambda () e_n ... (return (void)))]
; (tag_1))))))
; where (let/cc k expr ...) is short for (call/cc (lambda (k) expr ...)))]))
(require (for-syntax syntax/parse racket/list racket/match))
(define-syntax (tagged-begin stx)
(define tag? identifier?)
(define (non-tag? o) (not (tag? o)))
(define (generate-binding tag-exprs next-tag)
(match tag-exprs
[(list tag exprs) (quasisyntax/loc stx [#,tag (lambda () #,@exprs (#,next-tag))])]))
(define (generate-last-binding tag-exprs return)
(match tag-exprs
[(list tag exprs) (quasisyntax/loc stx [#,tag (lambda () #,@exprs (#,return (void)))])]))
(syntax-case stx ()
[(tagged-begin . tag/exprs-stx)
(let ([tes (syntax->list #'tag/exprs-stx)])
; introduce a dummy start-tag, if the tagged-begin starts with an expression
(when (not (tag? (car tes)))
(set! tes (cons #'start tes)))
(let* ([first-tag (car tes)]
[tag-exprs-list (let loop ([tes tes]
[rev-result '()])
(if (null? tes)
(reverse rev-result)
(let ([p tes])
(if (tag? (car p))
(loop (cdr tes)
(cons (list (car p) (takef (cdr p) non-tag?))
rev-result))
(loop (cdr tes)
rev-result)
))))
#;(list-ec (:pairs p tes)
(if (tag? (car p)))
(list (car p) (take-while non-tag? (cdr p))))
]
[tags (map car tag-exprs-list)])
; tag-exprs-list = ( (tag_1 (e1 ...)) (tag_2 (e2 ...)) ... )
(with-syntax ([go (syntax-local-introduce (syntax/loc stx go))]
[return (syntax-local-introduce (syntax/loc stx return))])
#`((let/cc go
(let ([return (lambda (v) (go (lambda () v)))])
(letrec
(#,@(map generate-binding
(drop-right tag-exprs-list 1)
(cdr tags))
#,(generate-last-binding (last tag-exprs-list) #'return))
(#,first-tag))))))))]))
(module+ test (require rackunit))
(module+ test
; Example 1 (tagged-begin returns (void))
(check-equal?
(let ([i 0])
(tagged-begin
loop (set! i (+ i 1))
(when (< i 41) (go loop)))
i)
41)
; Example 2 (tagged-begin returns 42)
(check-equal?
(let ([i 0])
(tagged-begin
loop (set! i (+ i 1))
(when (< i 42) (go loop))
(return i)))
42)
; Example 3 (tagged-begin returns 43)
(check-equal?
(let ([i 0])
(tagged-begin
loop (set! i (+ i 1))
(go b)
a (when (< i 43) (go loop))
(return i)
b (go a)))
43)
; Example 4 ( <http://www.emacswiki.org/cgi-bin/wiki.pl?StateMachine> )
(define (example4)
(let ((a 0))
(tagged-begin
start
(set! a 0)
part-1
(set! a (+ a 1))
(displayln a)
(cond
((>= a 9) (go end))
((even? a) (go part-1))
(else (go part-2)))
part-2
(set! a (+ a 1))
(go part-1)
end
(displayln "We're done printing the odd numbers between 0 and 10"))))
; Example 5 ( Knuth: "The Art of Computer Programming", vol1, p.176)
; Inplace inversion of a permutation represented as a vector.
(define permutation (vector 'dummy 6 2 1 5 4 3)) ; (Knuth counts from 1 not 0 :-) )
(define n (- (vector-length permutation) 1))
(define (X i) (vector-ref permutation i))
(define (X! i j) (vector-set! permutation i j))
(let ([m 0] [i 0] [j 0])
(tagged-begin
I1 ; Initialize
(set! m n)
(set! j -1)
I2 ; Next element
(set! i (X m))
(when (< i 0) (go I5))
I3 ; Invert one
(X! m j)
(set! j (- m))
(set! m i)
(set! i (X m))
I4 ; End of cycle?
(when (> i 0) (go I3))
(set! i j)
I5 ; Store final value
(X! m (- i))
I6 ; Loop on m
(set! m (- m 1))
(when (> m 0) (go I2))))
(check-equal? permutation #(dummy 3 2 6 5 4 1))
; Example 6 (The CommonLisp Hyper Spec examples of tagbody)
(define val 'foo)
(tagged-begin
(set! val 1)
(go a)
c (set! val (+ val 4))
(go b)
(set! val (+ val 32))
a (set! val (+ val 2))
(go c)
(set! val (+ val 64))
b (set! val (+ val 8)))
(check-equal? val 15)
(define (f1 flag)
(let ((n 1))
(tagged-begin
(set! n (f2 flag (lambda () (go out))))
out
(displayln n))))
(define (f2 flag escape)
(if flag (escape) 2))
; (f1 #f) ; displays 2
; (f1 #t) ; displays 1
; Example 7
; Demonstrates lexical scoping of tagged-begins,
; and that an inner tagged-begin can use an outer tag.
(check-equal?
(tagged-begin
a (tagged-begin
(go b))
b (return 'hello-world))
'hello-world)
; Demonstrates that tags are lexically shadowed.
(check-equal?
(tagged-begin
a (tagged-begin
(go b)
(return 'wrong)
b (go c))
b (return 'wrong)
c (return 'correct))
'correct)
)

13
control/until.rkt Normal file
View File

@ -0,0 +1,13 @@
#lang racket
(provide until)
(require (for-syntax syntax/parse))
(define-syntax (until stx)
(syntax-parse stx
[(_until expr body ...)
#'(let loop ()
body ...
(when (not expr)
(loop)))]))

19
control/while.rkt Normal file
View File

@ -0,0 +1,19 @@
#lang racket
(provide while)
; SYNTAX (while expr body ...)
; 1. Evaluate expr
; 2. If the result is true, then evaluate body ... and go to 1.
; 3. Return (void)
(require (for-syntax syntax/parse))
(define-syntax (while stx)
(syntax-parse stx
[(_while test-expr body ...)
#'(let loop ()
(when test
body ...
(loop)))]
[_
(raise-syntax-error
#f "(while test-expr body ...) expected, got:" stx)]))

6
info.rkt Normal file
View File

@ -0,0 +1,6 @@
#lang info
(define collection 'control)
(define version "3.0")
(define deps '("base"))
(define build-deps '("rackunit-lib" "scribble-lib" "racket-doc"))