racket/collects/redex/private/gen-trace.rkt
Burke Fetscher 44dd4acb44 Additional random test generation capability for Redex.
This adds the option to generate random terms that satisfy
judgment-forms and metafunctions.

Currently functionality does not include:
- patterns/terms using: ellipses, in-hole/hole and relatives, side-conditions, unquotes
- define-relation
- redex-check integration
2012-10-17 16:30:51 -05:00

750 lines
26 KiB
Racket

#lang racket/base
(require racket/contract
racket/set
racket/function
racket/match
racket/vector
racket/list
racket/class
racket/pretty
racket/gui/base
racket/string
"jdg-gen.rkt"
"judgment-form.rkt"
"search.rkt"
"trace-layout.rkt"
(only-in "pat-unify.rkt"
env-eqs
env-dqs))
(provide make-tree
show-trace
raw-locs
format-trace)
;; tree browser prototype
;;
;; uses a mapping to a geometric series to get an "axis at infinity" effect
;;
;; TODO:
;; * the layout algorithm is pretty dumb right now, could definitely be improved
;; * zooming/panning starts getting slow for trees with around 1,000 nodes
;;
(define loc? (listof exact-nonnegative-integer?))
(define trace-element? (cons/c loc? list?))
(define trace? (listof trace-element?))
;; well-formed trace: nodes with no siblings have a parent
;; and nodes that aren't the oldest sibling (index 0) have a next-youngest
;; this is a little slow right now - mabe overkill, but probably will have a similar
;; complexity to a layout algorithm that scans the entire trace
(define (trace-is-well-formed? trace)
(define locs (set))
(define (add-check-for-replaces loc)
(set! locs (set-remove-all locs (curry loc-prefix-same? loc)))
(set! locs (set-add locs loc)))
(for/and ([t-e (in-list trace)])
(add-check-for-replaces (car t-e))
(match t-e
[`(() ,rest ...)
#t]
[`((,locs-parent ... 0) ,rest ...)
(set-member? locs locs-parent)]
[`((,locs-parent ... ,n) ,rest ...)
(set-member? locs `(,@locs-parent ,(- n 1)))])))
(define well-formed-trace? (and/c trace? trace-is-well-formed?))
(define (show-trace)
(define tr (get-most-recent-trace))
(when tr
(define trace (format-trace tr))
(when trace
(show-trace-frame trace))))
(define (format-trace tr)
(map (match-lambda
[(vector 'info clause-name
(gen-trace tr-loc clause input state bound env))
(list (reverse tr-loc) clause-name state input (clause-head-pat clause) bound 0 env)])
tr))
(define/contract (show-trace-frame trace)
(-> well-formed-trace? any)
(define tf (new tree-frame% [t trace] [w 600] [h 600]))
(send tf display))
(define (set-remove-all old-set pred?)
(for/fold ([new-s (set)])
([s old-set])
(if (pred? s)
new-s
(set-add new-s s))))
(define (loc-prefix-same? parent child)
(and (<= (length parent) (length child))
(for/and ([e1 parent] [e2 child])
(equal? e1 e2))))
(struct gen-tree
(loc attributes [children #:mutable])
#:transparent)
(struct attributes
(label id in-bound term body coords [focus #:mutable] env)
#:transparent)
(struct coords
(x y)
#:transparent
#:mutable)
(struct trace-step (path b-factor attributes)
#:transparent)
(struct lvar (id) #:prefab)
(define SCROLL-RANGE 10000)
;; doesn't currently use all the information in the trace
(define (make-tree trace)
(let loop ([tree (vector 0)]
[t trace])
(match t
[`((,loc ,name ,state ,term ,body ,bound ,depth ,env) ,remaining-traces ...)
(define atts (attributes name (gensym) (positive? bound) term body (coords #f #f) #f env))
(loop (insert-tree-atts loc atts tree) remaining-traces)]
[else
tree])))
(define (trace-step-loc t-step)
(match t-step
[`(,loc ,name ,state ,term ,body ,bound ,depth ,env)
loc]))
(define (trace-step->atts t-step)
(match t-step
[`(,loc ,name ,state ,term ,body ,bound ,depth ,env)
(attributes name (gensym) (positive? bound) term body (coords #f #f) #f env)]))
(define (insert-tree-atts loc attributes tree-root)
(insert-tree-node loc (gen-tree loc attributes (make-vector 0)) tree-root))
(define (insert-tree-node full-loc node tree-root)
(define (insert-node loc node tree)
(match loc
[`(,i) ;; append or replace
(cond [(or (= 0 (vector-length (gen-tree-children tree)))
(not (vector-member full-loc
(vector-map gen-tree-loc (gen-tree-children tree)))))
(define new-children (vector-append (gen-tree-children tree)
(vector node)))
(set-gen-tree-children! tree new-children)
#f]
[else
(define old-node
(vector-ref (gen-tree-children tree)
(vector-member full-loc
(vector-map gen-tree-loc (gen-tree-children tree)))))
(vector-set! (gen-tree-children tree) i
node)
old-node])]
[`(,i ,is ...)
(insert-node is node (vector-ref (gen-tree-children tree) i))]
['() ;; initial tree (or replacement)
(set! tree-root node)]
[else (error "tree didn't have expected generation pattern" loc tree)]))
(insert-node full-loc node tree-root)
tree-root)
(define (get-node-at-loc loc tree-root)
(let recur ([t tree-root]
[l loc])
(match l
[`(,i)
(if (and (0 . < . (vector-length (gen-tree-children t)))
(i . < . (vector-length (gen-tree-children t))))
(vector-ref (gen-tree-children t) i)
#f)]
['() t]
[`(,i ,is ...)
(recur (vector-ref (gen-tree-children t) i) is)]
[else #f])))
(define (remove-tree-node loc tree-root)
(let recur ([t tree-root]
[l loc])
(match l
[`(,i)
(if (= (vector-length (gen-tree-children t)) (+ i 1))
(set-gen-tree-children! t (vector-take (gen-tree-children t) i))
(error "can only remove the last child of a node"))]
[`(,i ,is ...)
(recur (vector-ref (gen-tree-children t) i) is)]
[`()
(error "can't remove tree root")]))
tree-root)
(define (set-focus-to-true node)
(match node
[(gen-tree loc as cs)
(set-attributes-focus! as #t)]
[else
(void)]))
(define (all-trees trace)
(for/list ([i (length trace)]) (make-tree (take trace i))))
(define no-pen (new pen% [style 'transparent]))
(define no-brush (new brush% [style 'transparent]))
(define green-brush (new brush% [color "Medium Sea Green"]))
(define focus-brush (new brush% [color "Red"]))
(define blue-pen (new pen% [color "Steel Blue"] [width 2]))
(define grey-pen (new pen% [color "Light Slate Grey"] [width 1]))
(define grey-brush (new pen% [color "Slate Grey"]))
(define (add-layout-info tree locs->x-coords)
(let loop ([t tree]
[depth 1])
(match t
[(gen-tree loc (attributes p b-f b l l2 cs f e) children)
(for ([c children])
(loop c (add1 depth)))
(set-coords-x! cs (hash-ref locs->x-coords loc))
(set-coords-y! cs depth)]))
tree)
(define (max-depth trace)
(define max-d 0)
(let loop ([t trace])
(match t
[`((,loc ,other-stuff ...) ,remaining-traces ...)
(when (> (add1 (length loc)) max-d)
(set! max-d (add1 (length loc))))
(loop remaining-traces)]
[else max-d])))
(define (raw-locs trace)
(map (match-lambda
[`(,loc ,name ,state ,term ,body ,bound ,depth ,env)
loc])
trace))
;; handles keeping track of the trace
;; and mutating the tree
;; tree-frame does the drawing
(define trace-state%
(class object%
(init trace-init)
(define step 0)
(define tree #f)
(define trace trace-init)
(define tr-locs (raw-locs trace))
(define-values (locs->widths locs->coords)
(trace->widths/coords tr-locs))
(define final-tree (make-tree trace))
(define width (hash-ref locs->widths '()))
(define depth (apply max (map length tr-locs)))
(define last-atts #f)
(define lvars->names (make-hash))
(define names-inc 0)
(define subtree-stack '())
(super-new)
(update-tree-one-step)
(define/public (take-step)
(unless (equal? step (sub1 (length trace)))
(set! step (add1 step))
(update-tree-one-step)))
(define/public (step-back)
(unless (= step 0)
(match (first subtree-stack)
[`(,loc ,subtree)
(if subtree
(set! tree (insert-tree-node loc subtree tree))
(set! tree (remove-tree-node loc tree)))])
(set! subtree-stack (rest subtree-stack))
(set! step (sub1 step))
(defocus)
(set-focus-to-true (get-node-at-loc (trace-step-loc (list-ref trace step)) tree))
(set! last-atts (trace-step->atts (list-ref trace step)))
(add-layout-info tree locs->coords)))
(define/public (rewind)
(set! step 0)
(set! tree #f)
(update-tree-one-step))
(define/public (fast-forward)
(for ([i (in-range (add1 step) (length trace))])
(take-step)))
(define/public (get-tree) tree)
(define/public (get-width) width)
(define/public (get-depth) depth)
(define/public (current-atts)
last-atts)
(define/public (prettify-pat term)
(match term
[`(name ,id ,bound)
id]
[`(list ,subterms ...)
(for/list ([s subterms]) (prettify-pat s))]
[`(cstr (,nts ...) ,term)
`(cstr (,@nts) ,(prettify-pat term))]
[else term]))
;; TODO: "garbage collect" vars,
;; treating rule and input as roots
(define/public (format-pattern p eqs)
(match p
[`(name ,id ,bound)
(format-pattern (hash-ref eqs (lvar id)) eqs)]
[(lvar id)
(format-pattern (hash-ref eqs (lvar id)) eqs)]
[`(list ,subterms ...)
(for/list ([s subterms]) (format-pattern s eqs))]
[`(cstr (,nts ...) ,p)
`(cstr (,@nts) ,(format-pattern p eqs))]
[else p]))
(define/public (update-focus x y-index)
(defocus)
(define as (get-coord-atts x y-index))
(when as
(set-attributes-focus! as #t))
as)
(define/public (get-coord-atts x y-index)
(define (get-x t-node)
(match t-node
[(gen-tree loc (attributes p b-f b l l2 (coords x y) f e) cs)
x]
[else +inf.0]))
(define closest-node
(let recur ([t tree]
[d (sub1 y-index)])
(cond
[(= d 0)
t]
[else
(match t
[(gen-tree loc as '#())
#f]
[(gen-tree loc as children)
(define c-closest-ns (for/list ([c children])
(recur c (sub1 d))))
(first (sort c-closest-ns <
#:key (λ (c) (if c
(abs (- x (get-x c)))
+inf.0))))])])))
(match closest-node
[(gen-tree loc as children)
(set-attributes-focus! as #t)
as]
[else #f]))
(define/public (focus-coords)
(let recur ([t tree])
(match t
[(gen-tree loc (attributes p b-f b l l2 cs #t e) children)
cs]
[(gen-tree loc as '#())
#f]
[(gen-tree loc as children)
(define s-t-l (memf (λ (e) e)
(for/list ([c children]) (recur c))))
(if (or (not s-t-l) (empty? s-t-l))
#f
(first s-t-l))])))
(define/public (format-info as)
(match as
[(attributes name id bound term body coords f e)
(define eqs (env-eqs e))
(define sorted-vars (sort (hash-keys eqs)
(λ (l r) (string<? (symbol->string (lvar-id l))
(symbol->string (lvar-id r))))))
(string-append
(string-join
(for/list ([k sorted-vars])
(string-append (symbol->string (lvar-id k)) "\t = "
(match (hash-ref eqs k)
[(lvar next) (symbol->string next)]
[`(name ,next ,_) (symbol->string next)]
[else " - "]) "\t = "
(string-replace (pretty-format (format-pattern (hash-ref eqs k) eqs)) "\n" "\n\t \t ")))
"\n")
"\n\n"
(string-join
(for/list ([dq (in-list (env-dqs e))])
(string-append (format "~s" (first dq)) "\n\t"
(format "~s" (second dq))))
"\n"))]))
(define/private (defocus)
(let recur ([t tree])
(match t
[(gen-tree loc as '#())
(set-attributes-focus! as #f)]
[(gen-tree loc as children)
(set-attributes-focus! as #f)
(for ([c children]) (recur c))])))
(define/private (update-tree-one-step)
(define trace-step (list-ref trace step))
(when tree (defocus))
(match trace-step
[`(,loc ,name ,state ,term ,body ,bound ,depth ,env)
(set! subtree-stack (cons (list loc (get-node-at-loc loc tree)) subtree-stack))
(define atts (attributes name (gensym) (positive? bound) term body (coords #f #f) #t env))
(set! last-atts atts)
(set! tree (insert-tree-atts loc atts tree))]
[else (error "Trace had incorrect format, failed to update tree")])
(add-layout-info tree locs->coords))
))
;; end trace-state%
(define yscale-base .632)
(define (set-y-base f) (set! yscale-base f))
(define (ybase-sum) (/ yscale-base (- 1 yscale-base)))
(define (find-ybase-center)
(define mid (/ (ybase-sum) 2))
(define sums (for/hash ([i 10]) (values (abs (- mid
(apply + (for/list ([k i])
(expt yscale-base i)))))
i)))
(hash-ref sums (apply min (hash-keys sums))))
(define trans-steps 15)
(define (set-t-s x) (set! trans-steps x))
(define Y-SHIFT 0)
(define (set-y-shift s) (set! Y-SHIFT s))
(define tree-canvas%
(class canvas%
(super-new)
(define scroll-handler (λ (e) #f))
(define/public (set-scroll-handler f)
(set! scroll-handler f))
(define/override (on-scroll event)
(scroll-handler event))
(define key-handler (λ (e) #f))
(define/public (set-key-handler f)
(set! key-handler f))
(define/override (on-char event)
(key-handler event))))
(define tree-frame%
(class frame%
(init t w h)
(define trace (new trace-state% [trace-init t]))
(define width w)
(define height h)
(define shift (/ w 2))
(super-new [label "Generation Trace"])
(define (t-width)
(send trace get-width))
(define (depth)
(send trace get-depth))
(define y-index 1)
(define x-coord 0)
(define scale 1)
(define/private (rescale factor)
(set! scale (* scale factor)))
(define/private (trans-x x)
(set! x-coord (+ x-coord x))
(update-scroll-x x-coord))
(define/private (shift-y steps)
(set! y-index (+ y-index steps))
(update-scroll-y y-index))
(define rescale-factor (/ w (t-width)))
(define scale-factor (expt rescale-factor (/ 1 (depth))))
(rescale rescale-factor)
(define canvas (new tree-canvas% [parent this]
[min-width w]
[min-height h]
[style (list 'hscroll 'vscroll)]
[paint-callback (lambda (canvas dc)
(draw-t))]))
(send canvas set-scroll-range 'horizontal SCROLL-RANGE)
(send canvas set-scroll-range 'vertical SCROLL-RANGE)
(send canvas set-scroll-pos 'vertical 0)
(send canvas set-scroll-pos 'horizontal (/ SCROLL-RANGE 2))
(send canvas set-scroll-handler
(λ (event)
(define dir (send event get-direction))
(define pos (update-scroll-pos event))
(define x-pos x-coord)
(define y-pos y-index)
(match dir
['horizontal
(set! x-pos (* (t-width) (- .5 (/ pos SCROLL-RANGE))))]
['vertical
(set! y-pos (- (* (depth) (/ pos SCROLL-RANGE))))])
(animate-transition (- x-pos x-coord) (- y-pos y-index))))
(send canvas set-key-handler
(λ (event)
(define-values (d-x d-y)
(match (send event get-key-code)
['wheel-down
(values 0 (- (/ (depth) 40)))]
['wheel-up
(values 0 (/ (depth) 40))]
['wheel-right
(values (- (/ (t-width) 40)) 0)]
['wheel-left
(values (/ (t-width) 40) 0)]
['left
(send trace step-back)
(update/all-steps)
(values #f #f)]
['right
(send trace take-step)
(update/all-steps)
(values #f #f)]
[else
(values #f #f)]))
(when (and d-x d-y)
(animate-transition d-x d-y))))
(define/private (update-scroll-pos event)
(define pos (send event get-position))
(match (send event get-event-type)
['line-up
(- pos (/ SCROLL-RANGE 20))]
['line-down
(+ pos (/ SCROLL-RANGE 20))]
['page-up
(- pos (/ SCROLL-RANGE 20))]
['page-down
(+ pos (/ SCROLL-RANGE 20))]
[else pos]))
(define/private (update-scroll-x x)
(send canvas set-scroll-pos 'horizontal (+ (* (- x) (/ SCROLL-RANGE (t-width)))
(/ SCROLL-RANGE 2))))
(define/private (update-scroll-y y-index)
(send canvas set-scroll-pos 'vertical (max 0 (* (/ y-index (- (depth))) SCROLL-RANGE))))
(define dc (send canvas get-dc))
(define/public (display)
(send this show #t))
(define panel1 (new vertical-panel% [parent this]
[min-height 80]
[stretchable-height 80]))
(define panel2 (new horizontal-panel% [parent panel1]
[min-height 40]
[stretchable-height 40]))
(define rule-message (new text-field% [parent panel2]
[label "Rule:"]))
(define rewind-button (new button% [parent panel2]
[label "<<"]
[callback (λ (b e)
(send trace rewind)
(update/all-steps))]))
(define back-button (new button% [parent panel2]
[label "Back"]
[callback (λ (b e)
(send trace step-back)
(update/all-steps))]))
(define step-button (new button% [parent panel2]
[label "Step"]
[callback (λ (b e)
(send trace take-step)
(update/all-steps))]))
(define ff-button (new button% [parent panel2]
[label ">>"]
[callback (λ (b e)
(send trace fast-forward)
(update/all-steps))]))
(define term-message (new text-field% [parent panel1]
[label "Term:"]))
(define body-message (new text-field% [parent panel1]
[label "Body:"]))
(update-messages)
(define/private (update/all-steps)
(define f-coords (send trace focus-coords))
(cond
[f-coords
(define center-x (x-inv-map shift))
(define ∆x (- center-x (coords-x f-coords)))
(define ∆y (- 5 (+ y-index (coords-y f-coords))))
(animate-transition ∆x ∆y)]
[else
(send canvas refresh-now
(λ (dc) (draw-t)))])
(update-messages))
(define/private (update-messages [atts #f])
(define as (if atts atts (send trace current-atts)))
(match as
[(attributes name id bound term body coords f e)
(send rule-message set-value (format "~s" name))
(send term-message set-value (format "~s" (send trace prettify-pat term)))
(send body-message set-value (format "~s" (send trace prettify-pat body)))])
(send panel1 refresh))
(define/private (pop-info-window as)
(define info-w (new frame% [label "Environment"]
[width 300]
[height 300]))
(define output-string (send trace format-info as))
(define env-text (new text-field% [label #f]
[parent info-w]
[style '(multiple)]
[init-value output-string]))
(send info-w show #t))
(define y-scale (/ h (ybase-sum)))
(define y-cs (for/list ([y 10])
(* y-scale
(apply + (for/list ([i (in-range 1 y)]) (expt yscale-base i))))))
(define/private (i-for-y y-raw)
(define y (- y-raw Y-SHIFT))
(define diffs (for/hash ([i 10] [y-c y-cs])
(values (+ 0.0 (abs (- y y-c))) i)))
(hash-ref diffs (+ 0.0 (apply min (hash-keys diffs)))))
(define/private (delta-x x)
(/ (- (/ width 2) x) scale))
(define/private (x-inv-map x)
(- (/ (- x shift) scale) x-coord))
(define/override (on-subwindow-event receiver event)
(cond
[(and (eq? receiver canvas)
(send event button-down?))
(define x (send event get-x))
(define y (send event get-y))
(define ∆x (delta-x x))
(define actual-y (- (i-for-y y) y-index))
(define actual-x (x-inv-map x))
(cond
[(send event button-down? 'left)
(define focus-as (send trace update-focus actual-x (round actual-y)))
(update-messages focus-as)
(send canvas refresh-now
(λ (dc) (draw-t)))]
[(send event button-down? 'right)
(pop-info-window (send trace get-coord-atts actual-x (round actual-y)))])]
[else
#f]))
(define/private (animate-transition ∆x ∆y)
(define scaling (expt scale-factor ∆y))
(define trans-steps (inexact->exact (ceiling (max (abs (/ (* ∆x 40) (t-width)))
(abs (/ (* ∆y 40) (depth)))
1))))
(define dx (/ ∆x trans-steps))
(define dy (/ ∆y trans-steps))
(define ds (expt scaling (/ 1 trans-steps)))
(for ([i trans-steps])
(trans-x dx)
(shift-y dy)
(rescale ds)
(send canvas refresh-now
(λ (dc) (draw-t)))))
(define map-y-memo (make-hash))
(define map-y-int-memo (make-hash))
(define/private (map-y-int y)
(hash-ref map-y-int-memo y
(λ ()
(define res (if (< 0 y)
(+ Y-SHIFT (* (apply + (for/list ([i (in-range 1 y)])
(expt yscale-base i)))
y-scale))
(- Y-SHIFT (* (+ (abs y) 1) y-scale))))
(hash-set! map-y-int-memo y res)
res)))
(define/private (map-y y)
(hash-ref map-y-memo y
(λ ()
(define y-t (truncate y))
(define res (if (= y y-t)
(map-y-int y)
(let ([frac (abs (- y y-t))]
[next (if (<= 0 y-t) (+ y-t 1) (- y-t 1))])
(+ (* frac (map-y-int next))
(* (- 1 frac) (map-y-int y-t))))))
(hash-set! map-y-memo y res)
res)))
(define/private (adjust-x x)
(* scale (+ x-coord x)))
(define/private (adjust-y y)
(+ y y-index))
(define/private (line x1-raw y1-raw x2-raw y2-raw)
(define x1 (adjust-x x1-raw))
(define x2 (adjust-x x2-raw))
(define y1 (adjust-y y1-raw))
(define y2 (adjust-y y2-raw))
(when (or (and (x1 . > . (- shift)) (x1 . < . shift)
(y1 . > . -1) (y1 . < . 10))
(and (x2 . > . (- shift)) (x2 . < . shift)
(y2 . > . -1) (y2 . < . 10)))
(send dc set-brush no-brush)
(send dc set-pen blue-pen)
(send dc draw-line (+ shift x1) (map-y y1) (+ shift x2) (map-y y2))))
(define/private (node x-raw y-raw focus?)
(define x (adjust-x x-raw))
(define y (adjust-y y-raw))
(when (and (x . > . (- shift)) (x . < . shift)
(y . < . 8) (y . > . -1))
(send dc set-pen no-pen)
(cond
[focus?
(send dc set-brush focus-brush)
(send dc draw-ellipse (+ shift (- x 7)) (- (map-y y) 7) 14 14)]
[else
(send dc set-brush green-brush)
(send dc draw-ellipse (+ shift (- x 5)) (- (map-y y) 5) 10 10)])))
(define/private (draw-t)
(define (draw-subtree t d)
(match t
[(gen-tree loc (attributes p b-f b l l2 (coords x y) f e) '#())
(node x y f)]
[(gen-tree loc (attributes p b-f b l l2 (coords x y) f e) children)
(for ([c children])
(match c
[(gen-tree loc (attributes p b-f b l l2 (coords c-x c-y) f e) children)
(line x y c-x c-y)
(draw-subtree c (add1 d))]))
(node x y f)]))
(send dc set-smoothing 'aligned)
(draw-subtree (send trace get-tree) 0))))
;;; end treeframe%