195 lines
5.8 KiB
Racket
195 lines
5.8 KiB
Racket
#lang racket/base
|
|
(require "minimatch.rkt"
|
|
"runtime-progress.rkt")
|
|
(provide (struct-out failure)
|
|
|
|
expect?
|
|
(struct-out expect:thing)
|
|
(struct-out expect:atom)
|
|
(struct-out expect:literal)
|
|
(struct-out expect:message)
|
|
(struct-out expect:disj)
|
|
|
|
normalize-expectstack
|
|
simplify-common-expectstacks
|
|
maximal-failures
|
|
partition/equal?)
|
|
|
|
;; A Failure is (make-failure PS ExpectStack)
|
|
;; A FailureSet is one of
|
|
;; - Failure
|
|
;; - (cons FailureSet FailureSet)
|
|
|
|
;; FailFunction = (FailureSet -> Answer)
|
|
|
|
(define-struct failure (progress expectstack) #:prefab)
|
|
|
|
;; == Expectations
|
|
|
|
;; FIXME: add phase to expect:literal
|
|
|
|
#|
|
|
An ExpectStack is (listof Expect)
|
|
|
|
An Expect is one of
|
|
- (make-expect:thing string boolean)
|
|
* (make-expect:message string)
|
|
* (make-expect:atom atom)
|
|
* (make-expect:literal identifier)
|
|
* (make-expect:disj (non-empty-listof Expect))
|
|
|
|
The *-marked variants can only occur at the top of the stack.
|
|
|#
|
|
(define-struct expect:thing (description transparent?) #:prefab)
|
|
(define-struct expect:message (message) #:prefab)
|
|
(define-struct expect:atom (atom) #:prefab)
|
|
(define-struct expect:literal (literal) #:prefab)
|
|
(define-struct expect:disj (expects) #:prefab)
|
|
|
|
(define (expect? x)
|
|
(or (expect:thing? x)
|
|
(expect:message? x)
|
|
(expect:atom? x)
|
|
(expect:literal? x)
|
|
(expect:disj? x)))
|
|
|
|
|
|
;; == Failure simplification ==
|
|
|
|
;; maximal-failures : FailureSet -> (listof (listof Failure))
|
|
(define (maximal-failures fs)
|
|
(define ann-failures
|
|
(for/list ([f (in-list (flatten fs null))])
|
|
(cons f (invert-ps (failure-progress f)))))
|
|
(maximal/progress ann-failures))
|
|
|
|
(define (flatten fs onto)
|
|
(cond [(pair? fs)
|
|
(flatten (car fs) (flatten (cdr fs) onto))]
|
|
[else
|
|
(cons fs onto)]))
|
|
|
|
;; == Expectation simplification ==
|
|
|
|
;; normalize-expectstack : ExpectStack -> ExpectStack
|
|
(define (normalize-expectstack es)
|
|
(filter-expectstack (truncate-opaque-expectstack es)))
|
|
|
|
;; truncate-opaque-expectstack : ExpectStack -> ExpectStack
|
|
;; Eliminates expectations on top of opaque (ie, transparent=#f) frames.
|
|
(define (truncate-opaque-expectstack es)
|
|
(let/ec return
|
|
(let loop ([es es])
|
|
(match es
|
|
['() '()]
|
|
[(cons (expect:thing description '#f) rest-es)
|
|
;; Tricky! If multiple opaque frames, multiple "returns",
|
|
;; but innermost one called first, so jumps past the rest.
|
|
(return (cons (car es) (loop rest-es)))]
|
|
[(cons expect rest-es)
|
|
(cons expect (loop rest-es))]))))
|
|
|
|
;; filter-expectstack : ExpectStack -> ExpectStack
|
|
;; Eliminates missing (ie, #f) messages and descriptions
|
|
(define (filter-expectstack es)
|
|
(filter (lambda (expect)
|
|
(match expect
|
|
[(expect:thing '#f _)
|
|
#f]
|
|
[(expect:message '#f)
|
|
#f]
|
|
[_ #t]))
|
|
es))
|
|
|
|
#|
|
|
Simplification dilemma
|
|
|
|
What if we have (e1 e2) and (e2)? How do we report that?
|
|
Options:
|
|
1) consider them separate
|
|
2) simplify to (e2), drop e1
|
|
|
|
Big problem with Option 1:
|
|
eg (x:id ...) matching #'1 yields
|
|
(union (failure #:progress () #:expectstack ())
|
|
(failure #:progress () #:expectstack (#s(expect:atom ()))))
|
|
but we don't want to see "expected ()"
|
|
|
|
So we go with option 2.
|
|
|#
|
|
|
|
;; simplify-common-expectstacks : (listof ExpectStack) -> (listof ExpectStack)
|
|
;; Should call remove-duplicates first.
|
|
(define (simplify-common-expectstacks ess)
|
|
;; simplify : (listof ReversedExpectStack) -> (listof ReversedExpectStack)
|
|
(define (simplify ress)
|
|
(let ([ress-partitions (partition/car ress)])
|
|
(if ress-partitions
|
|
(apply append
|
|
(for/list ([ress-partition (in-list ress-partitions)])
|
|
(let ([proto-frame (car (car ress-partition))]
|
|
[cdr-ress (map cdr ress-partition)])
|
|
(map (lambda (res) (cons proto-frame res))
|
|
(simplify/check-leafs cdr-ress)))))
|
|
(list null))))
|
|
;; simplify/check-leafs : (listof ReversedExpectStack) -> (listof ReversedExpectStack)
|
|
(define (simplify/check-leafs ress)
|
|
(let ([ress (simplify ress)])
|
|
(cond [(andmap singleton? ress)
|
|
(let* ([frames (map car ress)])
|
|
(list (list (if (singleton? frames)
|
|
(car frames)
|
|
(expect:disj frames)))))]
|
|
[else ress])))
|
|
;; singleton? : list -> boolean
|
|
(define (singleton? res)
|
|
(and (pair? res) (null? (cdr res))))
|
|
(map reverse (simplify/check-leafs (map reverse ess))))
|
|
|
|
;; partition/car : (listof list) -> (listof (listof list))/#f
|
|
;; Returns #f if any of lists is empty.
|
|
(define (partition/car lists)
|
|
(and (andmap pair? lists)
|
|
(partition/equal? lists car)))
|
|
|
|
(define (partition/equal? items key)
|
|
(let ([r-keys null] ;; mutated
|
|
[key-t (make-hash)])
|
|
(for ([item (in-list items)])
|
|
(let ([k (key item)])
|
|
(let ([entry (hash-ref key-t k null)])
|
|
(when (null? entry)
|
|
(set! r-keys (cons k r-keys)))
|
|
(hash-set! key-t k (cons item entry)))))
|
|
(let loop ([r-keys r-keys] [acc null])
|
|
(cond [(null? r-keys) acc]
|
|
[else
|
|
(loop (cdr r-keys)
|
|
(cons (reverse (hash-ref key-t (car r-keys)))
|
|
acc))]))))
|
|
|
|
;; ==== Debugging
|
|
|
|
(provide failureset->sexpr
|
|
failure->sexpr
|
|
expectstack->sexpr
|
|
expect->sexpr)
|
|
|
|
(define (failureset->sexpr fs)
|
|
(let ([fs (flatten fs null)])
|
|
(case (length fs)
|
|
((1) (failure->sexpr (car fs)))
|
|
(else `(union ,@(map failure->sexpr fs))))))
|
|
|
|
(define (failure->sexpr f)
|
|
(match f
|
|
[(failure progress expectstack)
|
|
`(failure ,(progress->sexpr progress)
|
|
#:expected ,(expectstack->sexpr expectstack))]))
|
|
|
|
(define (expectstack->sexpr es)
|
|
(map expect->sexpr es))
|
|
|
|
(define (expect->sexpr e)
|
|
e)
|