machine/choice.ss
#lang scheme/base

;; Backtracking (relational programming) with the following features:

;;  - composable (using partial continuations)
;;  - allows for dynamic parameters to store (functional) threaded state
;;  - enumerator interface for choices and solutions

(provide query/parameterize
         query
         solutions  ;; produces an enumerator
         choice choice/mark choice/enum
         choice/fail
         cut)

(require "enum.ss"
         "fail.ss"
         srfi/41
         scheme/control
         scheme/match)

;; SHIFT + PARAMETER SAVE / RESTORE

(define choice-tag (new-prompt))

(define-syntax shift/parameters
  (syntax-rules ()
    ((_ () k expr)
     (shift-at choice-tag k expr))
    ((_ parameters k expr)
     (let ((ps (list . parameters)))
       (apply (lambda (x . pvs)
                (for ((p ps) (pv pvs)) (p pv))
                x)
              (let ((pvs (for/list ((p ps)) (p))))
                (shift-at choice-tag _k
                          (let ((k (lambda (x) (_k (cons x pvs)))))
                            expr))))))))

;; CONTROL OPS

;; The driver uses 2 instructions to sequence search, which either
;; instruct contexts to be saved or restored.

;; A `choicepoint' abstracts a fork in the search tree as a
;; combination of the current path, represented as a partial
;; contiuation with saved dynamic state, and its branch points,
;; abstracted by an enumerator.

(define-struct choicepoint (k enum mark?))

;; A `result' abstracts the end of a search path, combined with
;; instructions to retain a value as a solution, and whether to cut
;; the search tree to the currently marked point.

(define-struct result (cut? solution? value))

;; Enumerate solutions to nondeterminisitc program as a left fold.
;; Similarly, choices are specified using an enumerator.  This allows
;; straightforward composition of multiple searches.

;; ( Note that inside this routine, we explicitly loop, turning all
;; choice superfolds into streams.  It's probably possible to simply
;; compose the enumerators as function instead of appending streams.
;; I find streams in this instance easier to understand though.. )


(define (no-driver . _) (error 'choice-no-driver))
(define current-choice (make-parameter no-driver))

(define (choice . vals) ((current-choice) (list->enum vals) #f))
(define (choice/mark . vals) ((current-choice) (list->enum vals) #t))
(define (choice/enum enum [mark? #f]) ((current-choice) enum mark?))
(define (choice/fail . _) (abort-current-continuation
                           choice-tag (lambda () (make-result #f #f #f))))

(define (cut . lst)
  (abort-current-continuation
   choice-tag (lambda ()
                (cond
                 ((null? lst) (make-result #t #f #f))
                 ((null? (cdr lst)) (make-result #t #t (car lst)))
                 (else 'cut-nargs "~a" lst)))))


;; DRIVER LOOP

(define-struct choice-prog (thunk))
(define (choice-foldl/list fn init program [breadth-first? #f])
  (define _cut_ (stream (lambda () (make-result #t #f #f))))
  (let next ((paths (stream (choice-prog-thunk program)))
             (marks '())
             (state init))
    (if (stream-null? paths)
        state ;; done
        (let ((resume (stream-car paths))
              (paths- (stream-cdr paths)))
          (match (resume)

                 ;; Push choice points to paths, possibly marking
                 ;; current paths for `cut'.
                 ((struct choicepoint (k enum mark?))
                  (let ((thunks (stream-map
                                 (lambda (val)
                                   (lambda () (k val)))
                                 (enum->stream enum))))
                    (if breadth-first?
                        (if (not mark?)
                            (next (stream-append thunks paths-) marks state)
                            (error 'breadth-first-with-mark))
                        (if (not mark?)
                            (next (stream-append paths- thunks) marks state)
                            (next
                             (stream-append thunks _cut_) ;; pop marks when done
                             (cons paths- marks)  ;; push current paths on mark stack
                             state)))))

                 ;; Update fold state if there's a result, handle `fn'
                 ;; termination requests, otherwise continue search,
                 ;; possibly pruning tree.
                 ((struct result (cut? solution? value))
                  (when (and cut? (null? marks))
                    (error 'cut-without-marks))
                  (let-values
                      (((cont? state)
                        (if solution?
                            (call-with-values
                                (lambda () (apply fn value state))
                              (lambda (cont? . state+)
                                (values cont? state+)))
                            (values #t state)))
                       ((paths marks)
                        (if cut?
                            (values (car marks) (cdr marks))
                            (values paths- marks))))
                    (if cont?
                        (next paths marks state)
                        state))) ;; done
                            
                 (ins (error 'choice-foldl "invalid instruction: ~a" ins)))))))

;; Create the solutions enumerator.
(define (solutions prompt-thunk [breadth-first #f])
  (lambda (fn . init)
    (apply values (choice-foldl/list fn init prompt-thunk breadth-first))))


;; Create a non-deterministic program context.
(define-syntax-rule (query/parameterize ((p pv) ...) . body)
  (make-choice-prog
   (lambda ()
     (prompt-at
      choice-tag
      (parameterize
          ((current-fail choice/fail)
           (current-choice
            (lambda (enum mark?)
              (shift/parameters
               (p ...) k
               (make-choicepoint k enum mark?))))
           (p pv) ...)
        (make-result #f #t  ;; don't cut, use result
                     (begin . body)))))))

(define-syntax-rule (query . body)
  (query/parameterize () . body))