On this page:
2.1 Defining mutators
define-simple-mutator
define-id-mutator
define-constant-mutator
2.2 Building a mutation engine from individual mutators
build-mutation-engine

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
The simplest generic mutator definition form.

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 body forms must produce either
  • 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.

Example: (The 0 is the mutation index; see Mutation concepts.)
> (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]
Defines a mutator that only swaps identifiers.

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.

Examples:
> (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]
Defines a mutator that transforms constants. Unlike most other mutators, this mutator is not defined in terms of syntax. Constant mutators automatically extract the value of constants to allow easier transformation, and re-pack the transformed values back into syntax objects.

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.

Examples:
> (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
Builds and returns a mutation engine (a function to mutate program syntax) from the mutators defined by the enclosed mutator-definitions (which are either definitions, uses of the forms in the preceding section Defining mutators, or identifiers naming mutators).

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.

The #:module-mutator keyword makes the resulting engine mutate module forms with the shape

(module name lang (#%module-begin top-level-form ...))

without it, the engine treats its input program as a sequence of top level forms.

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):

(syntax? . -> . (stream/c syntax?))

Otherwise (when #:streaming is not supplied), the resulting engine has the interface:

(syntax? natural? . -> . (or/c syntax? #f))

Where the second argument is the mutation index to select.