;   Copyright (c) Christophe Grand. All rights reserved.
;   The use and distribution terms for this software are covered by the
;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;   which can be found in the file epl-v10.html at the root of this distribution.
;   By using this software in any fashion, you are agreeing to be bound by
;   the terms of this license.
;   You must not remove this notice, or any other, from this software.

(ns net.cgrand.parsley
  "A total truly incremental parser generator. Grammars are expressed 
   in a value-based DSL."
  (:require [net.cgrand.parsley.lrplus :as core]
            [net.cgrand.parsley.fold :as f]
            [net.cgrand.parsley.tree :as t]
            [net.cgrand.parsley.util :as u]
            [net.cgrand.parsley.grammar :as g]))


(defrecord Node [tag content]) ; for memory efficiency

(defn- stepper [table options-map]
  (let [options-map (merge
              {:make-node #(Node. %1 %2) 
               :make-leaf nil} ; nil for identity
              options-map)
        options-map (if-not (:make-unexpected options-map)
                      (let [make-node (:make-node options-map)
                            make-leaf (or (:make-leaf options-map) identity)] 
                        (assoc options-map :make-unexpected 
                               #(make-node ::unexpected [(make-leaf %)])))
                      options-map)
        ops (select-keys options-map [:make-node :make-leaf :make-unexpected])]
    ^{::options options-map} ; feeling dirty, metadata make me uneasy
    (fn self
      ([s]
        (let [a (self core/zero s) b (self a nil)]
          (-> (f/stitch a b) (nth 2) f/finish)))
      ([state s]
        (core/step table ops state s)))))

(defn- flatten-rules [rules]
  (if (map? rules)
    (apply concat rules)
    rules))

(defn make-parser [options-map rules]
  (-> (apply g/grammar options-map (flatten-rules rules)) core/lr-table core/totalize 
    core/number-states (stepper options-map)))

(defn parser [options-map & rules]
  (let [[options-map rules] (if-not (map? options-map)
                              [{} (cons options-map rules)]
                              [options-map rules])]
    (make-parser options-map rules)))

(defn unspaced 
 "Creates an unspaced sequence." 
 [& specs]
  (apply g/unspaced specs))


(defn- memoize-parser [f]
  (let [cache (atom nil)]
    (fn [input]
      (u/cond
        [last-result @cache
         new-result (f/rebase last-result input)]
          (if (identical? last-result new-result)
            last-result
            (reset! cache new-result))
        (reset! cache (f input))))))

(defn- memoize1 [parser s]
  (memoize-parser #(parser % s)))

(defn- memoize2 [mpa mpb]
  (memoize-parser #(let [a (mpa %)
                         b (mpb a)]
                     (f/stitch a b))))

(defn- memoize-1shot [f]
  (let [cache (atom [(Object.) nil])]
    (fn [& args]
      (let [[cargs cr] @cache]
        (if (= args cargs)
          cr
          (let [r (apply f args)]
            (reset! cache [args r])
            r))))))

(defn- memoize-eof [parser]
  (let [mp (memoize1 parser nil)]
    (memoize-1shot #(-> (f/stitch % (mp %)) (nth 2) f/finish))))

(defn incremental-buffer
  "Creates an empty incremental buffer for the specified parser." 
  [parser]
	  {:buffer (t/buffer {:unit #(memoize1 parser %) 
                       :plus memoize2 
                       :chunk #(.split ^String % "(?<=\n)")
                       :left #(subs %1 0 %2) 
                       :right subs 
                       :cat str})
    :eof-parser (memoize-eof parser)
    :options (::options (meta parser))})

(defn edit 
  "Returns a new buffer reflecting the specified edit."
  [incremental-buffer offset length s]
  (update-in incremental-buffer [:buffer] t/edit offset length s))

(defn parse-tree 
  "Returns the parse-tree."
  [incremental-buffer]
  (let [f (t/value (:buffer incremental-buffer))
        a (f core/zero)]
    ((:eof-parser incremental-buffer) a))) 

