core.ss
#lang scheme
(require (for-syntax syntax/parse)
         scheme/generator)

(define-struct model (rules))
(define-struct rule (head body))
(define-struct state (env qs))
(define-struct query ())
(define-struct (sexpr-query query) (se))
(define-struct (fun-query query) (f args))

(define (variable? q)
  (and (symbol? q)
       (char-upper-case?
        (string-ref 
         (symbol->string q)
         0))))

(define (unbound-variable? env q1)
  (and (variable? q1)
       (not (hash-has-key? env q1))))

(define (bound-variable? env q1)
  (and (variable? q1)
       (hash-has-key? env q1)))

(define (unify env q1 q2)
  (cond
    [(equal? q1 q2)
     env]
    [(unbound-variable? env q1)
     (hash-set env q1 q2)]
    [(unbound-variable? env q2)
     (hash-set env q2 q1)]
    [(bound-variable? env q1)
     (unify env (hash-ref env q1) q2)]
    [(bound-variable? env q2)
     (unify env q1 (hash-ref env q2))]
    [(and (list? q1) (list? q2)
          (= (length q1) (length q2)))
     (for/fold ([env env])
       ([e1 (in-list q1)]
        [e2 (in-list q2)])
       (and env
            (unify env e1 e2)))]
    [else #f]))

(define alpha-vary
  (match-lambda
    [(struct rule (head body))
     (define mapping (make-hasheq))
     (define alpha-se
       (match-lambda
         [(? variable? v)
          (hash-ref! mapping v (lambda () (gensym v)))]
         [(list-rest e1 e2)
          (cons (alpha-se e1)
                (alpha-se e2))]
         [e
          e]))
     (define alpha
       (match-lambda
         [(struct sexpr-query (q))
          (make-sexpr-query (alpha-se q))]
         [(struct fun-query (f args))
          (make-fun-query f (alpha-se args))]))
     (make-rule (alpha-se head)
                (map alpha body))]))

(define (rule-matches? r env q)
  (match-define (struct rule (head body)) r)
  (define new-env (unify env head q))
  (and new-env
       (make-state new-env body)))

(define generator-done
  (local [(define-struct uniq ())]
    (make-uniq)))

(define (model-state-generator m s)
  (match-define (struct state (env (list-rest first-query other-queries))) s)
  (generator
   (match first-query
     [(struct sexpr-query (q-se))
      (for ([rule (in-list (model-rules m))])
        (match (rule-matches? (alpha-vary rule) env q-se)
          [#f (void)]
          [(struct state (new-env subqueries))
           (yield (make-state new-env (append subqueries other-queries)))]))]
     [(struct fun-query (f args))
      (when (apply f (map (curry env-deref env) args))
        (yield (make-state env other-queries)))])
   (yield generator-done)))

(define (model-final-state-generator m t)
  (generator
   (match t
     [(struct state (env (list)))
      (yield env)]
     [(? state?)
      (for* ([next-state (in-producer (model-state-generator m t) generator-done)]
             [ans (in-producer (model-final-state-generator m next-state) generator-done)])
        (yield ans))])
   (yield generator-done)))

(define (env-deref env v)
  (cond
    [(bound-variable? env v)
     (env-deref env (hash-ref env v))]
    [(list? v)
     (map (curry env-deref env) v)]
    [else
     v]))

(define (env-restrict env l)
  (for/hasheq ([(k v) (in-hash env)]
               #:when (member k l))
    (values k (env-deref env v))))

(define (variables-in q)
  (match q
    [(? variable? v) (list v)]
    [(struct fun-query (_ l))
     (append-map variables-in l)]
    [(struct sexpr-query (l))
     (append-map variables-in l)]
    [_ empty]))

(define (query-answer-generator m q)
  (define init-vars (variables-in q))
  (generator
   (for ([ans (in-producer (model-final-state-generator 
                            m 
                            (make-state (make-immutable-hasheq empty)
                                        (list q)))
                           generator-done)])
     (yield (env-restrict ans init-vars)))
   (yield generator-done)))

(define (query-model* m q #:limit [limit +inf.0])
  (for/list ([ans (in-producer (query-answer-generator m q) generator-done)]
             [i (in-range limit)])
    ans))

(define-syntax (compile-query stx)
  (syntax-parse
   stx #:literals (unquote)
   [(_ ((unquote f) arg ...))
    (syntax/loc stx
      (make-fun-query f (list 'arg ...)))]
   [(_ query)
    (syntax/loc stx
      (make-sexpr-query 'query))]))

(provide (all-defined-out))