2 High level mutator api
2.1 Defining mutators
The following forms define mutators (see Mutation concepts). In addition to the basic model of a mutator as a function, every mutator has a mutator type: a string that names the mutator for logging purposes (see Logging: recovering the mutation type that causes a mutation).
syntax
(define-simple-mutator (id syntax-id) maybe-type-spec #:pattern syntax-parse-pattern maybe-guard body ...)
maybe-type-spec =
| #:type type-name-expr maybe-guard =
| #:when guard-expr
Defines a simple mutator named id that mutates syntax bound to syntax-id, returning the mutated syntax. The resulting mutator satisfies the interface mutator/c.
If provided, type-name-expr must produce a string that is the mutator type of the mutator. If not provided, the type defaults to id as a string.
syntax-parse-pattern is a syntax/parse pattern captures what shape of syntax this mutator operates upon.
If provided, guard-expr guards the application of the mutator (evaluating to #f causes the mutation to be skipped). Pattern variables in syntax-parse-pattern are bound in the scope of guard-expr.
the mutated syntax, or
a stream of mutated syntax objects. This is useful for mutators that can change the same piece of syntax in multiple ways.
> (define-simple-mutator (if-swap stx) #:pattern ({~datum if} c t e) #'(if c e t)) > (if-swap #'(if (< x 0) 0 (f x)) 0) (mutated #<syntax:eval:1:0 (if (< x 0) (f x) 0)> 1)
> (if-swap #'(not-an-if 42 (+ 2 3)) 0) (mutated #<syntax:eval:3:0 (not-an-if 42 (+ 2 3))> 0)
>
> (define-simple-mutator (permute-args stx) #:pattern ({~datum ->} arg ... result) (for/stream ([args (in-permutations (attribute arg))]) #`(-> #,@args result))) > (permute-args #'(-> A B C D) 0) (mutated #<syntax:eval:4:0 (-> A B C D)> 1)
> (permute-args #'(-> A B C D) 1) (mutated #<syntax:eval:4:0 (-> B A C D)> 2)
> (permute-args #'(-> A B C D) 2) (mutated #<syntax:eval:4:0 (-> A C B D)> 3)
> (permute-args #'(-> A B C D) 3) (mutated #<syntax:eval:4:0 (-> C A B D)> 4)
; ...
syntax
(define-id-mutator id maybe-type-spec id-swap-spec ...)
maybe-type-spec =
| #:type type-name-expr id-swap-spec = [original-id #:-> swapped-id] | [left-id #:<-> right-id]
Each id-swap-spec specifies one way to swap identifiers. The first variant of id-swap-specs, using #:->, specifies a one-way swap: original-id will be swapped with swapped-id. In contrast, the second variant, using #:<->, specifies a two-way swap: left-id will be swapped with right-id, and vice-versa. The second form is equivalent to two copies of the first form with swapped sides.
> (define-id-mutator arithmetic-op-swap [+ #:<-> -] [* #:-> /]) > (arithmetic-op-swap #'+ 0) (mutated #<syntax:eval:2:0 -> 1)
> (arithmetic-op-swap #'- 0) (mutated #<syntax:eval:3:0 +> 1)
> (arithmetic-op-swap #'* 0) (mutated #<syntax:eval:4:0 /> 1)
> (arithmetic-op-swap #'/ 0) ; no mutation (mutated #<syntax:eval:5:0 /> 0)
syntax
(define-constant-mutator (id constant-value-id) maybe-type-spec value-swap-spec ...)
maybe-type-spec =
| #:type type-name-expr value-swap-spec = [original-value-pat #:-> replacement-expr]
Each value-swap-spec specifies one way to transform constants. original-value-pat is a match pattern that matches the value of a constant, and if the pattern matches replacement-expr is its replacement value. Each matching value-swap-spec is tried in turn, so they may overlap.
> (define-constant-mutator (number-constant-swap v) [(? number?) #:-> (- v)] [(? integer?) #:-> (exact->inexact v)] [(and (? number?) (? zero?)) #:-> 1] [(? real?) #:-> (* 1.0+0.0i v)]) > (number-constant-swap #'5 0) (mutated #<syntax:eval:2:0 -5> 1)
> (number-constant-swap #'5 1) (mutated #<syntax:eval:3:0 5.0> 2)
> (number-constant-swap #'5 2) (mutated #<syntax:eval:4:0 5.0+0.0i> 3)
> (number-constant-swap #'0 0) (mutated #<syntax:eval:5:0 0.0> 1)
> (number-constant-swap #'0 1) (mutated #<syntax:eval:6:0 1> 2)
2.2 Building a mutation engine from individual mutators
syntax
(build-mutation-engine #:mutators mutator-definition-or-name ... result-type-kw maybe-selector ... option ...)
result-type-kw = #:syntax-only | #:with-mutated-id maybe-selector =
| #:expression-selector expr-selector | #:top-level-selector top-level-selector option = #:module-mutator | #:streaming
The kind of results produced by the engine must be specified with either #:syntax-only or #:with-mutated-id. In the first case, the engine built returns only the syntax of the mutant program when applied. In the second case, the engine also returns an identifier indicating the top-level form that was mutated, in addition to the mutant syntax, wrapped up in a mutated-program struct.
Optionally, specify an expression selector to control how expressions are traversed, and a top-level selector to control which top level forms are considered for mutation and how.
(module name lang (#%module-begin top-level-form ...)) |
The #:streaming keyword selects the interface of the resulting engine: when supplied, the resulting engine produces a stream of all mutants, where each one’s mutation index corresponds to its position in the stream. It has the following interface (assuming for illustration that the choice above is fixed to #:syntax-only):
Otherwise (when #:streaming is not supplied), the resulting engine has the interface:
Where the second argument is the mutation index to select.