#lang racket/base
;; Continuation-mark-based explicitly-scoped stores

(provide (struct-out store)
         make-store
         with-store
         store-box
         store-ref
         store-set!)

(struct store (mark-key default-box)
  #:property prop:procedure
  (case-lambda
    [(s) (store-ref s)]
    [(s v) (store-set! s v)]))

(define (make-store #:default-box [default-box #f])
  (store (make-continuation-mark-key (gensym 'store)) default-box))

(define (store-box s)
  (or (continuation-mark-set-first #f (store-mark-key s))
      (store-default-box s)
      (error 'store-box
             "Attempt to access store that is not currently in scope")))

(define (store-ref s)
  (unbox (store-box s)))

(define (store-set! s v)
  (set-box! (store-box s) v))

(define-syntax with-store
  (syntax-rules ()
    [(_ [] body ...)
     (let () body ...)]
    [(_ [(st initial-val) more ...] body ...)
     (let ((s st))
       (with-continuation-mark (store-mark-key s) (box initial-val)
         (with-store [more ...] body ...)))]))

(module+ test
  (require rackunit)

  (define p (make-parameter 123))

  (define t (make-continuation-prompt-tag 'store-test))

  (define s1 (make-store))
  (define s2 (make-store))

  (with-store [(s1 's1)]
    (check-equal? (s1) 's1)
    (s1 'b)
    (check-equal? (s1) 'b))

  (with-store [(s1 's1)
               (s2 's2)]
    (check-equal? (s1) 's1)
    (check-equal? (s2) 's2)
    (s1 'b)
    (s2 'b)
    (with-store [(s1 'c)]
      (check-equal? (s1) 'c)
      (check-equal? (s2) 'b)
      (s1 'd)
      (s2 'd)
      (check-equal? (s1) 'd)
      (check-equal? (s2) 'd))
    (check-equal? (s1) 'b)
    (check-equal? (s2) 'd))

  (define (s1-push! x)
    (s1 (cons x (s1))))

  (with-store [(s1 '())]
    (define k
      (call-with-continuation-prompt
       (lambda ()
         (s1-push! 'a)
         (s1-push! (p))
         (parameterize ((p 234))
           (s1-push! (p))
           (s1-push! (call-with-composable-continuation
                      (lambda (k)
                        (abort-current-continuation
                         t
                         (lambda ()
                           (s1-push! 'x)
                           k)))
                      t))
           (s1-push! 'b)
           (s1-push! (p)))
         (s1-push! (p))
         (s1-push! 'c))
       t))
    (s1-push! 'y)
    (k 99)
    (s1-push! 'd)
    (check-equal? (reverse (s1)) '(a 123 234 x y 99 b 234 123 c d))))
