From 9ef439d31fef5bfc68de8e1fd5d08486c515ce8d Mon Sep 17 00:00:00 2001 From: Robby Findler Date: Thu, 7 Mar 2013 09:26:46 -0600 Subject: [PATCH] add in documentation for plai/gc2 --- collects/plai/scribblings/collector.scrbl | 229 ++++++++++ collects/plai/scribblings/collector2.scrbl | 256 +++++++++++ collects/plai/scribblings/fake-collector2.rkt | 17 + collects/plai/scribblings/fake-mutator2.rkt | 3 + collects/plai/scribblings/lang-names.rkt | 6 + collects/plai/scribblings/mutator.scrbl | 260 +++++++++++ collects/plai/scribblings/mutator2.scrbl | 282 ++++++++++++ collects/plai/scribblings/plai.scrbl | 419 +----------------- 8 files changed, 1058 insertions(+), 414 deletions(-) create mode 100644 collects/plai/scribblings/collector.scrbl create mode 100644 collects/plai/scribblings/collector2.scrbl create mode 100644 collects/plai/scribblings/fake-collector2.rkt create mode 100644 collects/plai/scribblings/fake-mutator2.rkt create mode 100644 collects/plai/scribblings/lang-names.rkt create mode 100644 collects/plai/scribblings/mutator.scrbl create mode 100644 collects/plai/scribblings/mutator2.scrbl diff --git a/collects/plai/scribblings/collector.scrbl b/collects/plai/scribblings/collector.scrbl new file mode 100644 index 0000000000..51b88d007d --- /dev/null +++ b/collects/plai/scribblings/collector.scrbl @@ -0,0 +1,229 @@ +#lang scribble/doc +@(require scribble/manual + "rkt-exports.rkt" + "plai-exports.rkt" + "lang-names.rkt" + (for-syntax scheme) + (for-label (except-in scheme + error printf) + (prefix-in scheme: + scheme) + (only-in plai/main + type-case define-type error + test test/pred test/exn test/regexp + abridged-test-output + plai-catch-test-exn + halt-on-errors print-only-errors + test-inexact-epsilon plai-ignore-exn-strings + plai-all-test-results) + (only-in plai/collector + root? + heap-size + location? + heap-value? + heap-set! heap-ref with-heap + get-root-set read-root set-root! + procedure-roots) + plai/scribblings/fake-collector + plai/scribblings/fake-mutator + plai/scribblings/fake-web + plai/random-mutator + (only-in plai/web + no-web-browser + static-files-path) + (only-in plai/mutator + set-first! + set-rest! + import-primitives + test/location=? + test/value=? + printf))) + +@title[#:tag "collector"]{@COLLECT-LANG} + +@defmodulelang[plai/collector] + +@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI Scheme}. It provides +additional procedures and syntax for writing garbage collectors. + +@section{Garbage Collector Interface} + +The @COLLECT-LANG language provides the following functions that provide access +to the heap and root set: + +@defproc[(heap-size) exact-nonnegative-integer?]{ + +Returns the size of the heap. The size of the heap is specified by the mutator +that uses the garbage collector. See @racket[allocator-setup] for more +information. +} + +@defproc[(location? [v any/c]) + boolean?]{ + Determines if @racket[v] is an integer between @racket[0] and + @racket[(- (heap-size) 1)] inclusive. +} + +@defproc[(root? [v any/c]) + boolean?]{ +Determines if @racket[v] is a root. +} + +@defproc[(heap-value? [v any/c]) boolean?]{ + A value that may be stored on the heap. Roughly corresponds to the contract + @racket[(or/c boolean? number? procedure? symbol? empty?)]. +} + + +@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{ + Sets the value at @racket[_loc] to @racket[_val]. +} + +@defproc[(heap-ref (loc location?)) heap-value?]{ + Returns the value at @racket[_loc]. +} + +@defform/subs[(get-root-set id ...)()]{ + Returns the current roots as a list. Local roots are created for the + identifiers @racket[_id] as well. +} + +@defproc[(read-root (root root?)) location?]{ + Returns the location of @racket[_root]. +} + +@defproc[(set-root! (root root?) (loc location?)) void?]{ + Updates the root to reference the given location. +} + +@defproc[(procedure-roots (proc procedure?)) (listof root?)]{ + Given a closure stored on the heap, returns a list of the roots reachable + from the closure's environment. If @racket[_proc] is not reachable, the + empty list is returned. +} + +@defform[(with-heap heap-expr body-expr ...) + #:contracts ([heap-expr (vectorof heap-value?)])]{ + Evaluates each of the @racket[body-expr]s in a context where + the value of @racket[heap-expr] is used as the heap. Useful in + tests: + @racketblock[ + (test (with-heap (make-vector 20) + (init-allocator) + (gc:deref (gc:alloc-flat 2))) + 2) + ]} + +@defform[(with-roots roots-expr expr1 expr2 ...) + #:contracts ([roots-expr (listof location?)])]{ + Evaluates each of @racket[expr1] and the @racket[expr2]s in + in a context with the result of @racket[roots-expr] + as additional roots. + + This function is intended to be used in test suites + for collectors. Since your test suites are not running + in the @racketmod[plai/mutator] language, @racket[get-root-set] + returns a list consisting only of the roots it created, + not all of the other roots it normally would return. + Use this function to note specific locations as roots + and set up better tests for your GC. + + @racketblock[ + (test (with-heap (make-vector 4) + (define f1 (gc:alloc-flat 1)) + (define c1 (gc:cons f1 f1)) + (with-roots (list c1) + (gc:deref + (gc:first + (gc:cons f1 f1))))) + 1)] + +} + +@section{Garbage Collector Exports} + +@declare-exporting[#:use-sources (plai/scribblings/fake-collector)] + +A garbage collector must define the following functions: + +@defproc[(init-allocator) void?]{ + +@racket[init-allocator] is called before all other procedures by a +mutator. Place any requisite initialization code here. + +} + +@defproc[(gc:deref (loc location?)) heap-value?]{ + +Given the location of a flat Scheme value, this procedure should return that +value. If the location does not hold a flat value, this function should signal +an error. + +} + +@defproc[(gc:alloc-flat (val heap-value?)) location?]{ + +This procedure should allocate a flat Scheme value (number, symbol, boolean, +closure or empty list) on the heap, returning its location (a number). The +value should occupy a single heap cell, though you may use additional space to +store a tag, etc. You are also welcome to pre-allocate common constants (e.g., +the empty list). This procedure may need to perform a garbage-collection. If +there is still insufficient space, it should signal an error. + +Note that closures are flat values. The environment of a closure is internally +managed, but contains references to values on the heap. Therefore, during +garbage collection, the environment of reachable closures must be updated. The +language exposes the environment via the @racket[procedure-roots] function. + +} + +@defproc[(gc:cons (first location?) (rest location?)) location?]{ + +Given the location of the @racket[_first] and @racket[_rest] values, this +procedure must allocate a cons cell on the heap. If there is insufficient +space to allocate the cons cell, it should signal an error. + +} + +@defproc[(gc:first (cons-cell location?)) location?]{ + +If the given location refers to a cons cell, this should return the first +field. Otherwise, it should signal an error. + +} + +@defproc[(gc:rest (cons-cell location?)) location?]{ + +If the given location refers to a cons cell, this should return the rest +field. Otherwise, it should signal an error. + +} + +@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{ + +If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to +@racket[_first-value]. Otherwise, signal an error. + +} + +@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{ + +If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to +@racket[_rest-value]. Otherwise, signal an error. + +} + +@defproc[(gc:cons? (loc location?)) boolean?]{ + + +Returns @racket[true] if @racket[_loc] refers to a cons cell. This function +should never signal an error. + +} + +@defproc[(gc:flat? (loc location?)) boolean?]{ + +Returns @racket[true] if @racket[_loc] refers to a flat value. This function +should never signal an error. + +} diff --git a/collects/plai/scribblings/collector2.scrbl b/collects/plai/scribblings/collector2.scrbl new file mode 100644 index 0000000000..6c6c3c7f2e --- /dev/null +++ b/collects/plai/scribblings/collector2.scrbl @@ -0,0 +1,256 @@ +#lang scribble/doc +@(require scribble/manual + "rkt-exports.rkt" + "plai-exports.rkt" + "lang-names.rkt" + (for-syntax scheme) + (for-label (except-in scheme + error printf) + (prefix-in scheme: + scheme) + (only-in plai/main + type-case define-type error + test test/pred test/exn test/regexp + abridged-test-output + plai-catch-test-exn + halt-on-errors print-only-errors + test-inexact-epsilon plai-ignore-exn-strings + plai-all-test-results) + (only-in plai/collector + root? + heap-size + location? + heap-value? + heap-set! heap-ref with-heap + get-root-set read-root set-root! + procedure-roots) + plai/scribblings/fake-collector + plai/scribblings/fake-mutator + plai/scribblings/fake-web + plai/random-mutator + (only-in plai/web + no-web-browser + static-files-path) + (only-in plai/mutator + set-first! + set-rest! + import-primitives + test/location=? + test/value=? + printf))) + + +@title[#:tag "gc2-collector"]{GC Collector, 2} + +@defmodulelang[plai/gc2/collector] + +@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI}. It provides +additional procedures and syntax for writing garbage collectors. + +@section{Garbage Collector Interface for GC2} + +The @COLLECT-LANG language provides the following functions that provide access +to the heap and root set: + +@defproc[(heap-size) exact-nonnegative-integer?]{ + +Returns the size of the heap. The size of the heap is specified by the mutator +that uses the garbage collector. See @racket[allocator-setup] for more +information. +} + +@defproc[(location? [v any/c]) + boolean?]{ + Determines if @racket[v] is an integer between @racket[0] and + @racket[(- (heap-size) 1)] inclusive. +} + +@defproc[(root? [v any/c]) + boolean?]{ +Determines if @racket[v] is a root. +} + +@defproc[(heap-value? [v any/c]) boolean?]{ + A value that may be stored on the heap. Roughly corresponds to the contract + @racket[(or/c boolean? number? procedure? symbol? empty?)]. +} + + +@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{ + Sets the value at @racket[_loc] to @racket[_val]. +} + +@defproc[(heap-ref (loc location?)) heap-value?]{ + Returns the value at @racket[_loc]. +} + +@defform/subs[(get-root-set id ...)()]{ + Returns the current roots as a list. Local roots are created for the + identifiers @racket[_id] as well. +} + +@defproc[(read-root (root root?)) location?]{ + Returns the location of @racket[_root]. +} + +@defproc[(set-root! (root root?) (loc location?)) void?]{ + Updates the root to reference the given location. +} + +@defproc[(procedure-roots (proc procedure?)) (listof root?)]{ + Given a closure stored on the heap, returns a list of the roots reachable + from the closure's environment. If @racket[_proc] is not reachable, the + empty list is returned. +} + +@defform[(with-heap heap-expr body-expr ...) + #:contracts ([heap-expr (vectorof heap-value?)])]{ + Evaluates each of the @racket[body-expr]s in a context where + the value of @racket[heap-expr] is used as the heap. Useful in + tests: + @racketblock[ + (test (with-heap (make-vector 20) + (init-allocator) + (gc:deref (gc:alloc-flat 2))) + 2) + ]} + +@defform[(with-roots roots-expr expr1 expr2 ...) + #:contracts ([roots-expr (listof location?)])]{ + Evaluates each of @racket[expr1] and the @racket[expr2]s in + in a context with the result of @racket[roots-expr] + as additional roots. + + This function is intended to be used in test suites + for collectors. Since your test suites are not running + in the @racketmod[plai/gc2/mutator] language, @racket[get-root-set] + returns a list consisting only of the roots it created, + not all of the other roots it normally would return. + Use this function to note specific locations as roots + and set up better tests for your GC. + + @racketblock[ + (test (with-heap (make-vector 4) + (define f1 (gc:alloc-flat 1)) + (define c1 (gc:cons f1 f1)) + (with-roots (list c1) + (gc:deref + (gc:first + (gc:cons f1 f1))))) + 1)] + +} + +@section{Garbage Collector Exports for GC2} + +@declare-exporting[#:use-sources (plai/scribblings/fake-collector2)] + +A garbage collector must define the following functions: + +@defproc[(init-allocator) void?]{ + +@racket[init-allocator] is called before all other procedures by a +mutator. Place any requisite initialization code here. + +} + +@defproc[(gc:deref (loc location?)) heap-value?]{ + +Given the location of a flat value, this procedure should return that +value. If the location does not hold a flat value, this function should signal +an error. + +} + +@defproc[(gc:alloc-flat (val heap-value?)) location?]{ + +This procedure should allocate a flat value (number, symbol, boolean, +closure or empty list) on the heap, returning its location (a number). The +value should occupy a single heap cell, though you may use additional space to +store a tag, etc. You are also welcome to pre-allocate common constants (e.g., +the empty list). This procedure may need to perform a garbage-collection. If +there is still insufficient space, it should signal an error. + +Note that closures are flat values. The environment of a closure is internally +managed, but contains references to values on the heap. Therefore, during +garbage collection, the environment of reachable closures must be updated. The +language exposes the environment via the @racket[procedure-roots] function. + +} + +@defproc[(gc:cons (first location?) (rest location?)) location?]{ + +Given the location of the @racket[_first] and @racket[_rest] values, this +procedure must allocate a cons cell on the heap. If there is insufficient +space to allocate the cons cell, it should signal an error. + +} + +@defproc[(gc:first (cons-cell location?)) location?]{ + +If the given location refers to a cons cell, this should return the first +field. Otherwise, it should signal an error. + +} + +@defproc[(gc:rest (cons-cell location?)) location?]{ + +If the given location refers to a cons cell, this should return the rest +field. Otherwise, it should signal an error. + +} + +@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{ + +If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to +@racket[_first-value]. Otherwise, signal an error. + +} + +@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{ + +If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to +@racket[_rest-value]. Otherwise, signal an error. + +} + +@defproc[(gc:cons? (loc location?)) boolean?]{ + + +Returns @racket[true] if @racket[_loc] refers to a cons cell. This function +should never signal an error. + +} + +@defproc[(gc:flat? (loc location?)) boolean?]{ + +Returns @racket[true] if @racket[_loc] refers to a flat value. This function +should never signal an error. + +} + +@defproc[(gc:closure [code-ptr heap-value?] [free-vars (vectorof location?)]) + location?]{ + Allocates a closure with 'code-ptr' and the free variables + in the vector 'free-vars'. +} +@defproc[(gc:closure-code-ptr [loc location?]) heap-value?]{ + Given a location returned from an earlier allocation + check to see if it is a closure; if not signal an + error. if so, return the @racket[_code-ptr] for that closure. +} + +@defproc[(gc:closure-env-ref [loc location?] [i exact-nonnegative-integer?]) + location?]{ + Given a location returned from an earlier allocation, check + to see if it is a closure; if not signal an error. Uf so, + return the @racket[i]th variable in the closure (counting from 0). +} + +@defproc[(gc:closure? [loc location?]) boolean?]{ + Determine if a previously allocated location + holds a closure. This function will be called only + with locations previous returned from an allocating + function or passed to @racket[set-root!]. It should + never signal an error. +} diff --git a/collects/plai/scribblings/fake-collector2.rkt b/collects/plai/scribblings/fake-collector2.rkt new file mode 100644 index 0000000000..49618658d1 --- /dev/null +++ b/collects/plai/scribblings/fake-collector2.rkt @@ -0,0 +1,17 @@ +#lang scheme +(provide (all-defined-out)) + +(define init-allocator #f) +(define gc:deref #f) +(define gc:alloc-flat #f) +(define gc:cons #f) +(define gc:first #f) +(define gc:rest #f) +(define gc:set-first! #f) +(define gc:set-rest! #f) +(define gc:cons? #f) +(define gc:flat? #f) +(define gc:closure #f) +(define gc:closure-code-ptr #f) +(define gc:closure-env-ref #f) +(define gc:closure? #f) diff --git a/collects/plai/scribblings/fake-mutator2.rkt b/collects/plai/scribblings/fake-mutator2.rkt new file mode 100644 index 0000000000..5692205145 --- /dev/null +++ b/collects/plai/scribblings/fake-mutator2.rkt @@ -0,0 +1,3 @@ +#lang scheme +(provide (all-defined-out)) +(define-syntax allocator-setup #f) diff --git a/collects/plai/scribblings/lang-names.rkt b/collects/plai/scribblings/lang-names.rkt new file mode 100644 index 0000000000..5145a5b29e --- /dev/null +++ b/collects/plai/scribblings/lang-names.rkt @@ -0,0 +1,6 @@ +#lang racket/base +(provide PLAI-LANG COLLECT-LANG MUTATE-LANG WEB-LANG) +(define PLAI-LANG "PLAI Scheme") +(define COLLECT-LANG "GC Collector Scheme") +(define MUTATE-LANG "GC Mutator Scheme") +(define WEB-LANG "Web Application Scheme") diff --git a/collects/plai/scribblings/mutator.scrbl b/collects/plai/scribblings/mutator.scrbl new file mode 100644 index 0000000000..6d5698ded5 --- /dev/null +++ b/collects/plai/scribblings/mutator.scrbl @@ -0,0 +1,260 @@ +#lang scribble/doc +@(require scribble/manual + "rkt-exports.rkt" + "plai-exports.rkt" + "lang-names.rkt" + (for-syntax scheme) + (for-label (except-in scheme + error printf) + (prefix-in scheme: + scheme) + (only-in plai/main + type-case define-type error + test test/pred test/exn test/regexp + abridged-test-output + plai-catch-test-exn + halt-on-errors print-only-errors + test-inexact-epsilon plai-ignore-exn-strings + plai-all-test-results) + (only-in plai/collector + root? + heap-size + location? + heap-value? + heap-set! heap-ref with-heap + get-root-set read-root set-root! + procedure-roots) + plai/scribblings/fake-collector + plai/scribblings/fake-mutator + plai/scribblings/fake-web + plai/random-mutator + (only-in plai/web + no-web-browser + static-files-path) + (only-in plai/mutator + set-first! + set-rest! + import-primitives + test/location=? + test/value=? + printf))) + +@title[#:tag "mutator"]{@MUTATE-LANG} + +@defmodulelang[plai/mutator] + +The @MUTATE-LANG language is used to test garbage collectors written with the +@secref["collector"] language. Since collectors support a subset of Scheme's +values, the @MUTATE-LANG language supports a subset of procedures and syntax. +In addition, many procedures that can be written in the mutator are omitted as +they make good test cases. Therefore, the mutator language provides only +primitive procedures, such as @racket[+], @racket[cons], etc. + +@section{Building Mutators} + +@declare-exporting[#:use-sources (plai/scribblings/fake-mutator)] + +The first expression of a mutator must be: + +@defform/subs[ +(allocator-setup collector-module + heap-size) +([heap-size exact-nonnegative-integer?])]{ + +@racket[_collector-module] specifies the path to the garbage collector that the +mutator should use. The collector must be written in the @COLLECT-LANG +language. +} + +The rest of a mutator module is a sequence of definitions, expressions and test +cases. The @MUTATE-LANG language transforms these definitions and statements to +use the collector specified in @racket[allocator-setup]. In particular, many +of the primitive forms, such as @racket[cons] map directly to procedures such +as @racket[gc:cons], written in the collector. + +@section{Mutator API} + +The @MUTATE-LANG language supports the following procedures and syntactic +forms: + +@(define-syntax (document/lift stx) + (syntax-case stx () + [(_ a ...) + (with-syntax ([(doc ...) + (map (λ (a) + (with-syntax ([a a] + [rkt:a (string->symbol (format "rkt:~a" (syntax-e a)))]) + #'@defidform[a]{Just like Racket's @|rkt:a|.})) + (syntax->list #'(a ...)))]) + + #'(begin + doc ...))])) + +@document/lift[if and or cond case define-values let let-values let* set! quote error begin] + +@defform[(define (id arg-id ...) body-expression ...+)]{ + Just like Racket's @racket[define], except restricted to the simpler form + above. +} +@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{} + @defform[(λ (arg-id ...) body-expression ...+)]{})]{ + Just like Racket's @racket[lambda] and @racket[λ], except restricted to the + simpler form above. +} + +@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >= + symbol? symbol=? number? boolean? empty? eq?] + +@defproc[(cons [hd any/c] [tl any/c]) cons?]{ + Constructs a (mutable) pair. +} +@defproc[(cons? [v any/c]) boolean?]{ + Returns @racket[#t] when given a value created by @racket[cons], + @racket[#f] otherwise. +} +@defproc[(first [c cons?]) any/c]{ + Extracts the first component of @racket[c]. +} +@defproc[(rest [c cons?]) any/c]{ + Extracts the rest component of @racket[c]. +} + +@defproc[(set-first! [c cons?] [v any/c]) + void]{ + Sets the @racket[first] of the cons cell @racket[c]. +} + +@defproc[(set-rest! [c cons?] [v any/c]) + void]{ + Sets the @racket[rest] of the cons cell @racket[c]. +} + +@defidform[empty]{ + The identifier @racket[empty] is defined to invoke + @racket[(gc:alloc-flat empty)] wherever it is used. +} + +@defidform[print-only-errors]{ + Behaves like PLAI's @|plai:print-only-errors|. +} + +@defidform[halt-on-errors]{ + Behaves like PLAI's @|plai:halt-on-errors|. +} + +Other common procedures are left undefined as they can be defined in +terms of the primitives and may be used to test collectors. + +Additional procedures from @racketmodname[scheme] may be imported with: + +@defform/subs[(import-primitives id ...)()]{ + +Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each +procedure is transformed to correctly interface with the mutator. That is, its +arguments are dereferenced from the mutator's heap and the result is allocated +on the mutator's heap. The arguments and result must be @racket[heap-value?]s, +even if the imported procedure accepts or produces structured data. + +For example, the @MUTATE-LANG language does not define @racket[modulo]: + +@racketblock[ + +(import-primitives modulo) + +(test/value=? (modulo 5 3) 2) +] + +} + +@section{Testing Mutators} + +@MUTATE-LANG provides two forms for testing mutators: + +@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{ + +@racket[test/location=?] succeeds if @racket[_mutator-expr1] and +@racket[_mutator-expr2] reference the same location on the heap. + +} + +@defform/subs[(test/value=? mutator-expr scheme-datum/quoted)()]{ + +@racket[test/value=?] succeeds if @racket[_mutator-expr] and +@racket[_scheme-datum/expr] are structurally equal. +@racket[_scheme-datum/quoted] is not allocated on the mutator's +heap. Futhermore, it must either be a quoted value or a literal value. + +} + +@defform/subs[ +(printf format mutator-expr ...) +([format literal-string])]{ + +In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The +format string, @racket[_format] is not allocated on the mutator's heap. + +} + +@section{Generating Random Mutators} + +@defmodule[plai/random-mutator] + +This PLAI library provides a facility for generating random mutators, +in order to test your garbage collection implementation. + +@defproc[(save-random-mutator + [file path-string?] + [collector-name string?] + [#:heap-values heap-values (cons heap-value? (listof heap-value?)) + (list 0 1 -1 'x 'y #f #t '())] + [#:iterations iterations exact-positive-integer? 200] + [#:program-size program-size exact-positive-integer? 10] + [#:heap-size heap-size exact-positive-integer? 100]) + void?]{ +Creates a random mutator that uses the collector @racket[collector-name] and +saves it in @racket[file]. + +The mutator is created by first making a random graph whose nodes either have +no outgoing edges, two outgoing edges, or some random number of outgoing edges +and then picking a random path in the graph that ends at one of the nodes with +no edges. + +This graph and path are then turned into a PLAI program by creating a +@racket[let] expression that binds one variable per node in the graph. If the +node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node +has two outgoing edges, it is bound to a pair and the two edges are put into +the first and rest fields. Otherwise, the node is represented as a procedure +that accepts an integer index and returns the destination node of the +corresponding edge. + +Once the @racket[let] expression has been created, the program creates a bunch +of garbage and then traverses the graph, according to the randomly created +path. If the result of the path is the expected heap value, the program does +this again, up to @racket[iterations] times. If the result of the path is not +the expected heap value, the program terminates with an error. + +The keyword arguments control some aspects of the generation +of random mutators: +@itemize[ +@item{Elements from the @racket[heap-values] argument are used as the base + values when creating nodes with no outgoing edges. See also + @racket[find-heap-values].} +@item{The @racket[iterations] argument controls how many times the graph is + created (and traversed).} +@item{The @racket[program-size] argument is a bound on how big the program it + is; it limits the number of nodes, the maximum number of edges, and the + length of the path in the graph.} +@item{The @racket[heap-size] argument controls the size of the heap in the + generated mutator.}] + +} + +@defproc[(find-heap-values [input (or/c path-string? input-port?)]) + (listof heap-value?)]{ + Processes @racket[input] looking for occurrences of @racket[heap-value?]s in + the source of the program and returns them. This makes a good start for the + @racket[heap-values] argument to @racket[save-random-mutator]. + + If @racket[input] is a port, its contents are assumed to be a well-formed + PLAI program. If @racket[input] is a file, the contents of the file are used. +} diff --git a/collects/plai/scribblings/mutator2.scrbl b/collects/plai/scribblings/mutator2.scrbl new file mode 100644 index 0000000000..65257f2a2e --- /dev/null +++ b/collects/plai/scribblings/mutator2.scrbl @@ -0,0 +1,282 @@ +#lang scribble/doc +@(require scribble/manual + "rkt-exports.rkt" + "plai-exports.rkt" + "lang-names.rkt" + scribble/decode + (for-syntax scheme) + (for-label (except-in scheme + error printf) + (prefix-in scheme: + scheme) + (only-in plai/main + type-case define-type error + test test/pred test/exn test/regexp + abridged-test-output + plai-catch-test-exn + halt-on-errors print-only-errors + test-inexact-epsilon plai-ignore-exn-strings + plai-all-test-results) + (only-in plai/collector + root? + heap-size + location? + heap-value? + heap-set! heap-ref with-heap + get-root-set read-root set-root! + procedure-roots) + plai/random-mutator + (only-in plai/web + no-web-browser + static-files-path) + (only-in plai/mutator + set-first! + set-rest! + import-primitives + test/location=? + test/value=? + printf))) + +@title[#:tag "gc2-mutator"]{GC Mutator Language, 2} + +@defmodulelang[plai/gc2/mutator] + +The @MUTATE-LANG language is used to test garbage collectors written with the +@secref["collector"] language. Since collectors support a subset of Racket's +values, the @MUTATE-LANG language supports a subset of procedures and syntax. +In addition, many procedures that can be written in the mutator are omitted as +they make good test cases. Therefore, the mutator language provides only +primitive procedures, such as @racket[+], @racket[cons], etc. + +@section{Building Mutators for GC2} + +@declare-exporting[#:use-sources (plai/scribblings/fake-mutator2)] + +The first expression of a mutator must be: + +@defform/subs[ +(allocator-setup collector-module + heap-size) +([heap-size exact-nonnegative-integer?])]{ + +@racket[_collector-module] specifies the path to the garbage collector that the +mutator should use. The collector must be written in the @COLLECT-LANG +language. +} + +The rest of a mutator module is a sequence of definitions, expressions and test +cases. The @MUTATE-LANG language transforms these definitions and statements to +use the collector specified in @racket[allocator-setup]. In particular, many +of the primitive forms, such as @racket[cons] map directly to procedures such +as @racket[gc:cons], written in the collector. + +@section{Mutator API for GC2} + +The @MUTATE-LANG language supports the following procedures and syntactic +forms: + +@(define-syntax (document/lift stx) + (syntax-case stx () + [(_ a ...) + (with-syntax ([(doc ...) + (for/list ([a (in-list (syntax->list #'(a ...)))]) + (syntax-case a () + [a + (identifier? #'a) + (with-syntax ([rkt:a (string->symbol (format "rkt:~a" (syntax-e #'a)))]) + #'@defidform[a]{Just like Racket's @|rkt:a|.})] + [(a stuff) + (identifier? #'a) + (with-syntax ([rkt:a (string->symbol (format "rkt:~a" (syntax-e #'a)))]) + #'@defidform[a]{Similar to Racket's @|rkt:a|. @stuff})]))]) + #'(begin + doc ...))])) + +@document/lift[if and or cond case define-values let let-values let* + (set! @splice[@list{Unlike Racket's @|rkt:set!|, this @racket[set!] is syntactically allowed only + in positions that discard its result, e.g., at the top-level + or in a @racket[begin] expression (although not as the last expression + in a @racket[begin]).}]) + quote error begin] + +@defform[(define (id arg-id ...) body-expression ...+)]{ + Just like Racket's @racket[define], except restricted to the simpler form + above. +} +@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{} + @defform[(λ (arg-id ...) body-expression ...+)]{})]{ + Just like Racket's @racket[lambda] and @racket[λ], except restricted to the + simpler form above. +} + +@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >= + symbol? symbol=? number? boolean? empty? eq?] + +@defproc[(cons [hd any/c] [tl any/c]) cons?]{ + Constructs a (mutable) pair. +} +@defproc[(cons? [v any/c]) boolean?]{ + Returns @racket[#t] when given a value created by @racket[cons], + @racket[#f] otherwise. +} +@defproc[(first [c cons?]) any/c]{ + Extracts the first component of @racket[c]. +} +@defproc[(rest [c cons?]) any/c]{ + Extracts the rest component of @racket[c]. +} + +@defproc[(set-first! [c cons?] [v any/c]) + void]{ + Sets the @racket[first] of the cons cell @racket[c]. + + Calls to this function are allowed only in syntactic + positions that would discard its result, e.g., at the + top-level or inside a @racket[begin] expression (but + not in the last expression in a @racket[begin]). + Also, this function appear only in the function + position of an application expression. + + So, in order to pass around a version of this function, + you must write something like this + @racket[(λ (c v) (begin (set-first! c v) 42))], + perhaps picking a different value than @racket[42] + as the result. +} + +@defproc[(set-rest! [c cons?] [v any/c]) + void]{ + Sets the @racket[rest] of the cons cell @racket[c], + with the same syntactic restrictions as + @racket[set-first!]. +} + +@defidform[empty]{ + The identifier @racket[empty] is defined to invoke + @racket[(gc:alloc-flat empty)] wherever it is used. +} + +@defidform[print-only-errors]{ + Behaves like PLAI's @|plai:print-only-errors|. +} + +@defidform[halt-on-errors]{ + Behaves like PLAI's @|plai:halt-on-errors|. +} + +Other common procedures are left undefined as they can be defined in +terms of the primitives and may be used to test collectors. + +Additional procedures from @racketmodname[scheme] may be imported with: + +@defform/subs[(import-primitives id ...)()]{ + +Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each +procedure is transformed to correctly interface with the mutator. That is, its +arguments are dereferenced from the mutator's heap and the result is allocated +on the mutator's heap. The arguments and result must be @racket[heap-value?]s, +even if the imported procedure accepts or produces structured data. + +For example, the @MUTATE-LANG language does not define @racket[modulo]: + +@racketblock[ + +(import-primitives modulo) + +(test/value=? (modulo 5 3) 2) +] + +} + +@section{Testing Mutators with GC2} + +@MUTATE-LANG provides two forms for testing mutators: + +@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{ + +@racket[test/location=?] succeeds if @racket[_mutator-expr1] and +@racket[_mutator-expr2] reference the same location on the heap. + +} + +@defform/subs[(test/value=? mutator-expr datum/quoted)()]{ + +@racket[test/value=?] succeeds if @racket[_mutator-expr] and +@racket[_datum/expr] are structurally equal. +@racket[_datum/quoted] is not allocated on the mutator's +heap. Futhermore, it must either be a quoted value or a literal value. + +} + +@defform/subs[ +(printf format mutator-expr ...) +([format literal-string])]{ + +In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The +format string, @racket[_format] is not allocated on the mutator's heap. + +} + +@section{Generating Random Mutators for GC2} + +@defmodule[plai/gc2/random-mutator] + +This PLAI library provides a facility for generating random mutators, +in order to test your garbage collection implementation. + +@defproc[(save-random-mutator + [file path-string?] + [collector-name string?] + [#:heap-values heap-values (cons heap-value? (listof heap-value?)) + (list 0 1 -1 'x 'y #f #t '())] + [#:iterations iterations exact-positive-integer? 200] + [#:program-size program-size exact-positive-integer? 10] + [#:heap-size heap-size exact-positive-integer? 100]) + void?]{ +Creates a random mutator that uses the collector @racket[collector-name] and +saves it in @racket[file]. + +The mutator is created by first making a random graph whose nodes either have +no outgoing edges, two outgoing edges, or some random number of outgoing edges +and then picking a random path in the graph that ends at one of the nodes with +no edges. + +This graph and path are then turned into a PLAI program by creating a +@racket[let] expression that binds one variable per node in the graph. If the +node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node +has two outgoing edges, it is bound to a pair and the two edges are put into +the first and rest fields. Otherwise, the node is represented as a procedure +that accepts an integer index and returns the destination node of the +corresponding edge. + +Once the @racket[let] expression has been created, the program creates a bunch +of garbage and then traverses the graph, according to the randomly created +path. If the result of the path is the expected heap value, the program does +this again, up to @racket[iterations] times. If the result of the path is not +the expected heap value, the program terminates with an error. + +The keyword arguments control some aspects of the generation +of random mutators: +@itemize[ +@item{Elements from the @racket[heap-values] argument are used as the base + values when creating nodes with no outgoing edges. See also + @racket[find-heap-values].} +@item{The @racket[iterations] argument controls how many times the graph is + created (and traversed).} +@item{The @racket[program-size] argument is a bound on how big the program it + is; it limits the number of nodes, the maximum number of edges, and the + length of the path in the graph.} +@item{The @racket[heap-size] argument controls the size of the heap in the + generated mutator.}] + +} + +@defproc[(find-heap-values [input (or/c path-string? input-port?)]) + (listof heap-value?)]{ + Processes @racket[input] looking for occurrences of @racket[heap-value?]s in + the source of the program and returns them. This makes a good start for the + @racket[heap-values] argument to @racket[save-random-mutator]. + + If @racket[input] is a port, its contents are assumed to be a well-formed + PLAI program. If @racket[input] is a file, the contents of the file are used. +} diff --git a/collects/plai/scribblings/plai.scrbl b/collects/plai/scribblings/plai.scrbl index 5da58e274a..0633b037a0 100644 --- a/collects/plai/scribblings/plai.scrbl +++ b/collects/plai/scribblings/plai.scrbl @@ -2,6 +2,7 @@ @(require scribble/manual "rkt-exports.rkt" "plai-exports.rkt" + "lang-names.rkt" (for-syntax scheme) (for-label (except-in scheme error printf) @@ -38,11 +39,6 @@ test/value=? printf))) -@(define PLAI-LANG "PLAI Scheme") -@(define COLLECT-LANG "GC Collector Scheme") -@(define MUTATE-LANG "GC Mutator Scheme") -@(define WEB-LANG "Web Application Scheme") - @title{@italic{Programming Languages: Application and Interpretation}} This is the documentation for the software accompanying the textbook @bold{Programming Languages: Application and @@ -249,415 +245,10 @@ This variable is the list of all tests that have been run so far, with the most recent test at the head. } - -@section[#:tag "collector"]{@COLLECT-LANG} - -@defmodulelang[plai/collector] - -@COLLECT-LANG is based on @seclink["plai-scheme"]{PLAI Scheme}. It provides -additional procedures and syntax for writing garbage collectors. - -@subsection{Garbage Collector Interface} - -The @COLLECT-LANG language provides the following functions that provide access -to the heap and root set: - -@defproc[(heap-size) exact-nonnegative-integer?]{ - -Returns the size of the heap. The size of the heap is specified by the mutator -that uses the garbage collector. See @racket[allocator-setup] for more -information. -} - -@defproc[(location? [v any/c]) - boolean?]{ - Determines if @racket[v] is an integer between @racket[0] and - @racket[(- (heap-size) 1)] inclusive. -} - -@defproc[(root? [v any/c]) - boolean?]{ -Determines if @racket[v] is a root. -} - -@defproc[(heap-value? [v any/c]) boolean?]{ - A value that may be stored on the heap. Roughly corresponds to the contract - @racket[(or/c boolean? number? procedure? symbol? empty?)]. -} - - -@defproc[(heap-set! (loc location?) (val heap-value?)) void?]{ - Sets the value at @racket[_loc] to @racket[_val]. -} - -@defproc[(heap-ref (loc location?)) heap-value?]{ - Returns the value at @racket[_loc]. -} - -@defform/subs[(get-root-set id ...)()]{ - Returns the current roots as a list. Local roots are created for the - identifiers @racket[_id] as well. -} - -@defproc[(read-root (root root?)) location?]{ - Returns the location of @racket[_root]. -} - -@defproc[(set-root! (root root?) (loc location?)) void?]{ - Updates the root to reference the given location. -} - -@defproc[(procedure-roots (proc procedure?)) (listof root?)]{ - Given a closure stored on the heap, returns a list of the roots reachable - from the closure's environment. If @racket[_proc] is not reachable, the - empty list is returned. -} - -@defform[(with-heap heap-expr body-expr ...) - #:contracts ([heap-expr (vectorof heap-value?)])]{ - Evaluates each of the @racket[body-expr]s in a context where - the value of @racket[heap-expr] is used as the heap. Useful in - tests: - @racketblock[ - (test (with-heap (make-vector 20) - (init-allocator) - (gc:deref (gc:alloc-flat 2))) - 2) - ]} - -@defform[(with-roots roots-expr expr1 expr2 ...) - #:contracts ([roots-expr (listof location?)])]{ - Evaluates each of @racket[expr1] and the @racket[expr2]s in - in a context with the result of @racket[roots-expr] - as additional roots. - - This function is intended to be used in test suites - for collectors. Since your test suites are not running - in the @racketmod[plai/mutator] language, @racket[get-root-set] - returns a list consisting only of the roots it created, - not all of the other roots it normally would return. - Use this function to note specific locations as roots - and set up better tests for your GC. - - @racketblock[ - (test (with-heap (make-vector 4) - (define f1 (gc:alloc-flat 1)) - (define c1 (gc:cons f1 f1)) - (with-roots (list c1) - (gc:deref - (gc:first - (gc:cons f1 f1))))) - 1)] - -} - -@subsection{Garbage Collector Exports} - -@declare-exporting[#:use-sources (plai/scribblings/fake-collector)] - -A garbage collector must define the following functions: - -@defproc[(init-allocator) void?]{ - -@racket[init-allocator] is called before all other procedures by a -mutator. Place any requisite initialization code here. - -} - -@defproc[(gc:deref (loc location?)) heap-value?]{ - -Given the location of a flat Scheme value, this procedure should return that -value. If the location does not hold a flat value, this function should signal -an error. - -} - -@defproc[(gc:alloc-flat (val heap-value?)) location?]{ - -This procedure should allocate a flat Scheme value (number, symbol, boolean, -closure or empty list) on the heap, returning its location (a number). The -value should occupy a single heap cell, though you may use additional space to -store a tag, etc. You are also welcome to pre-allocate common constants (e.g., -the empty list). This procedure may need to perform a garbage-collection. If -there is still insufficient space, it should signal an error. - -Note that closures are flat values. The environment of a closure is internally -managed, but contains references to values on the heap. Therefore, during -garbage collection, the environment of reachable closures must be updated. The -language exposes the environment via the @racket[procedure-roots] function. - -} - -@defproc[(gc:cons (first location?) (rest location?)) location?]{ - -Given the location of the @racket[_first] and @racket[_rest] values, this -procedure must allocate a cons cell on the heap. If there is insufficient -space to allocate the cons cell, it should signal an error. - -} - -@defproc[(gc:first (cons-cell location?)) location?]{ - -If the given location refers to a cons cell, this should return the first -field. Otherwise, it should signal an error. - -} - -@defproc[(gc:rest (cons-cell location?)) location?]{ - -If the given location refers to a cons cell, this should return the rest -field. Otherwise, it should signal an error. - -} - -@defproc[(gc:set-first! (cons-cell location?) (first-value location?)) void?]{ - -If @racket[_cons-cell] refers to a cons cell, set the head of the cons cell to -@racket[_first-value]. Otherwise, signal an error. - -} - -@defproc[(gc:set-rest! (cons-cell location?) (rest-value location?)) void?]{ - -If @racket[_cons-cell] refers to a cons cell, set the tail of the cons cell to -@racket[_rest-value]. Otherwise, signal an error. - -} - -@defproc[(gc:cons? (loc location?)) boolean?]{ - - -Returns @racket[true] if @racket[_loc] refers to a cons cell. This function -should never signal an error. - -} - -@defproc[(gc:flat? (loc location?)) boolean?]{ - -Returns @racket[true] if @racket[_loc] refers to a flat value. This function -should never signal an error. - -} - -@section[#:tag "mutator"]{@MUTATE-LANG} - -@defmodulelang[plai/mutator] - -The @MUTATE-LANG language is used to test garbage collectors written with the -@secref["collector"] language. Since collectors support a subset of Scheme's -values, the @MUTATE-LANG language supports a subset of procedures and syntax. -In addition, many procedures that can be written in the mutator are omitted as -they make good test cases. Therefore, the mutator language provides only -primitive procedures, such as @racket[+], @racket[cons], etc. - -@subsection{Building Mutators} - -@declare-exporting[#:use-sources (plai/scribblings/fake-mutator)] - -The first expression of a mutator must be: - -@defform/subs[ -(allocator-setup collector-module - heap-size) -([heap-size exact-nonnegative-integer?])]{ - -@racket[_collector-module] specifies the path to the garbage collector that the -mutator should use. The collector must be written in the @COLLECT-LANG -language. -} - -The rest of a mutator module is a sequence of definitions, expressions and test -cases. The @MUTATE-LANG language transforms these definitions and statements to -use the collector specified in @racket[allocator-setup]. In particular, many -of the primitive forms, such as @racket[cons] map directly to procedures such -as @racket[gc:cons], written in the collector. - -@subsection{Mutator API} - -The @MUTATE-LANG language supports the following procedures and syntactic -forms: - -@(define-syntax (document/lift stx) - (syntax-case stx () - [(_ a ...) - (with-syntax ([(doc ...) - (map (λ (a) - (with-syntax ([a a] - [rkt:a (string->symbol (format "rkt:~a" (syntax-e a)))]) - #'@defidform[a]{Just like Racket's @|rkt:a|.})) - (syntax->list #'(a ...)))]) - - #'(begin - doc ...))])) - -@document/lift[if and or cond case define-values let let-values let* set! quote error begin] - -@defform[(define (id arg-id ...) body-expression ...+)]{ - Just like Racket's @racket[define], except restricted to the simpler form - above. -} -@deftogether[(@defform[(lambda (arg-id ...) body-expression ...+)]{} - @defform[(λ (arg-id ...) body-expression ...+)]{})]{ - Just like Racket's @racket[lambda] and @racket[λ], except restricted to the - simpler form above. -} - -@document/lift[add1 sub1 zero? + - * / even? odd? = < > <= >= - symbol? symbol=? number? boolean? empty? eq?] - -@defproc[(cons [hd any/c] [tl any/c]) cons?]{ - Constructs a (mutable) pair. -} -@defproc[(cons? [v any/c]) boolean?]{ - Returns @racket[#t] when given a value created by @racket[cons], - @racket[#f] otherwise. -} -@defproc[(first [c cons?]) any/c]{ - Extracts the first component of @racket[c]. -} -@defproc[(rest [c cons?]) any/c]{ - Extracts the rest component of @racket[c]. -} - -@defproc[(set-first! [c cons?] [v any/c]) - void]{ - Sets the @racket[first] of the cons cell @racket[c]. -} - -@defproc[(set-rest! [c cons?] [v any/c]) - void]{ - Sets the @racket[rest] of the cons cell @racket[c]. -} - -@defidform[empty]{ - The identifier @racket[empty] is defined to invoke - @racket[(gc:alloc-flat empty)] wherever it is used. -} - -@defidform[print-only-errors]{ - Behaves like PLAI's @|plai:print-only-errors|. -} - -@defidform[halt-on-errors]{ - Behaves like PLAI's @|plai:halt-on-errors|. -} - -Other common procedures are left undefined as they can be defined in -terms of the primitives and may be used to test collectors. - -Additional procedures from @racketmodname[scheme] may be imported with: - -@defform/subs[(import-primitives id ...)()]{ - -Imports the procedures @racket[_id ...] from @racketmodname[scheme]. Each -procedure is transformed to correctly interface with the mutator. That is, its -arguments are dereferenced from the mutator's heap and the result is allocated -on the mutator's heap. The arguments and result must be @racket[heap-value?]s, -even if the imported procedure accepts or produces structured data. - -For example, the @MUTATE-LANG language does not define @racket[modulo]: - -@racketblock[ - -(import-primitives modulo) - -(test/value=? (modulo 5 3) 2) -] - -} - -@subsection{Testing Mutators} - -@MUTATE-LANG provides two forms for testing mutators: - -@defform/subs[(test/location=? mutator-expr1 mutator-expr2)()]{ - -@racket[test/location=?] succeeds if @racket[_mutator-expr1] and -@racket[_mutator-expr2] reference the same location on the heap. - -} - -@defform/subs[(test/value=? mutator-expr scheme-datum/quoted)()]{ - -@racket[test/value=?] succeeds if @racket[_mutator-expr] and -@racket[_scheme-datum/expr] are structurally equal. -@racket[_scheme-datum/quoted] is not allocated on the mutator's -heap. Futhermore, it must either be a quoted value or a literal value. - -} - -@defform/subs[ -(printf format mutator-expr ...) -([format literal-string])]{ - -In @|MUTATE-LANG|, @racket[printf] is a syntactic form and not a procedure. The -format string, @racket[_format] is not allocated on the mutator's heap. - -} - -@subsection{Generating Random Mutators} - -@defmodule[plai/random-mutator] - -This PLAI library provides a facility for generating random mutators, -in order to test your garbage collection implementation. - -@defproc[(save-random-mutator - [file path-string?] - [collector-name string?] - [#:heap-values heap-values (cons heap-value? (listof heap-value?)) - (list 0 1 -1 'x 'y #f #t '())] - [#:iterations iterations exact-positive-integer? 200] - [#:program-size program-size exact-positive-integer? 10] - [#:heap-size heap-size exact-positive-integer? 100]) - void?]{ -Creates a random mutator that uses the collector @racket[collector-name] and -saves it in @racket[file]. - -The mutator is created by first making a random graph whose nodes either have -no outgoing edges, two outgoing edges, or some random number of outgoing edges -and then picking a random path in the graph that ends at one of the nodes with -no edges. - -This graph and path are then turned into a PLAI program by creating a -@racket[let] expression that binds one variable per node in the graph. If the -node has no outgoing edges, it is bound to a @racket[heap-value?]. If the node -has two outgoing edges, it is bound to a pair and the two edges are put into -the first and rest fields. Otherwise, the node is represented as a procedure -that accepts an integer index and returns the destination node of the -corresponding edge. - -Once the @racket[let] expression has been created, the program creates a bunch -of garbage and then traverses the graph, according to the randomly created -path. If the result of the path is the expected heap value, the program does -this again, up to @racket[iterations] times. If the result of the path is not -the expected heap value, the program terminates with an error. - -The keyword arguments control some aspects of the generation -of random mutators: -@itemize[ -@item{Elements from the @racket[heap-values] argument are used as the base - values when creating nodes with no outgoing edges. See also - @racket[find-heap-values].} -@item{The @racket[iterations] argument controls how many times the graph is - created (and traversed).} -@item{The @racket[program-size] argument is a bound on how big the program it - is; it limits the number of nodes, the maximum number of edges, and the - length of the path in the graph.} -@item{The @racket[heap-size] argument controls the size of the heap in the - generated mutator.}] - -} - -@defproc[(find-heap-values [input (or/c path-string? input-port?)]) - (listof heap-value?)]{ - Processes @racket[input] looking for occurrences of @racket[heap-value?]s in - the source of the program and returns them. This makes a good start for the - @racket[heap-values] argument to @racket[save-random-mutator]. - - If @racket[input] is a port, its contents are assumed to be a well-formed - PLAI program. If @racket[input] is a file, the contents of the file are used. -} +@include-section["collector.scrbl"] +@include-section["mutator.scrbl"] +@include-section["collector2.scrbl"] +@include-section["mutator2.scrbl"] @section[#:tag "web"]{@WEB-LANG}