;; lib/common-lisp/loop.sx — The LOOP macro for CL-on-SX
;;
;; Supported clauses:
;;   for VAR in LIST             — iterate over list
;;   for VAR across VECTOR       — alias for 'in'
;;   for VAR from N              — numeric iteration (to/upto/below/downto/above/by)
;;   for VAR = EXPR [then EXPR]  — general iteration
;;   while COND                  — stop when false
;;   until COND                  — stop when true
;;   repeat N                    — repeat N times
;;   collect EXPR [into VAR]
;;   append EXPR [into VAR]
;;   nconc EXPR [into VAR]
;;   sum EXPR [into VAR]
;;   count EXPR [into VAR]
;;   maximize EXPR [into VAR]
;;   minimize EXPR [into VAR]
;;   do FORM...
;;   when/if COND clause...
;;   unless COND clause...
;;   finally FORM...
;;   always COND
;;   never COND
;;   thereis COND
;;   named BLOCK-NAME
;;
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/eval.sx already loaded.
;; Uses defmacro in the CL evaluator.

;; ── LOOP expansion driver ─────────────────────────────────────────────────

;; cl-loop-parse: analyse the flat LOOP clause list and build a Lisp form.
;; Returns a (block NAME (let (...) (tagbody ...))) form.
(define
  cl-loop-parse
  (fn
    (clauses)
    (define block-name nil)
    (define with-bindings (list))
    (define for-bindings (list))
    (define test-forms (list))
    (define repeat-var nil)
    (define repeat-count nil)
    (define body-forms (list))
    (define accum-vars (dict))
    (define accum-clauses (dict))
    (define result-var nil)
    (define finally-forms (list))
    (define return-expr nil)
    (define termination nil)
    (define idx 0)
    (define (lp-peek) (if (< idx (len clauses)) (nth clauses idx) nil))
    (define
      (next!)
      (let ((v (lp-peek))) (do (set! idx (+ idx 1)) v)))
    (define
      (skip-if pred)
      (if (and (not (nil? (lp-peek))) (pred (lp-peek))) (next!) nil))
    (define (upcase-str s) (if (string? s) (upcase s) s))
    (define (kw? s k) (= (upcase-str s) k))
    (define
      (make-accum-var!)
      (if
        (nil? result-var)
        (do (set! result-var "#LOOP-RESULT") result-var)
        result-var))
    (define
      (add-accum! type expr into-var)
      (let
        ((v (if (nil? into-var) (make-accum-var!) into-var)))
        (if
          (not (has-key? accum-vars v))
          (do
            (set!
              accum-vars
              (assoc
                accum-vars
                v
                (cond
                  ((= type ":sum") 0)
                  ((= type ":count") 0)
                  ((= type ":maximize") nil)
                  ((= type ":minimize") nil)
                  (:else (list)))))
            (set! accum-clauses (assoc accum-clauses v type))))
        (let
          ((update (cond ((= type ":collect") (list "SETQ" v (list "APPEND" v (list "LIST" expr)))) ((= type ":append") (list "SETQ" v (list "APPEND" v expr))) ((= type ":nconc") (list "SETQ" v (list "NCONC" v expr))) ((= type ":sum") (list "SETQ" v (list "+" v expr))) ((= type ":count") (list "SETQ" v (list "+" v (list "IF" expr 1 0)))) ((= type ":maximize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list ">" expr v)) expr v))) ((= type ":minimize") (list "SETQ" v (list "IF" (list "OR" (list "NULL" v) (list "<" expr v)) expr v))) (:else (list "SETQ" v (list "APPEND" v (list "LIST" expr)))))))
          (set! body-forms (append body-forms (list update))))))
    (define
      (parse-clause!)
      (let
        ((tok (lp-peek)))
        (if
          (nil? tok)
          nil
          (do
            (let
              ((u (upcase-str tok)))
              (cond
                ((= u "NAMED")
                  (do (next!) (set! block-name (next!)) (parse-clause!)))
                ((= u "WITH")
                  (do
                    (next!)
                    (let
                      ((var (next!)))
                      (skip-if (fn (s) (kw? s "=")))
                      (let
                        ((init (next!)))
                        (set!
                          with-bindings
                          (append with-bindings (list (list var init))))
                        (parse-clause!)))))
                ((= u "FOR")
                  (do
                    (next!)
                    (let
                      ((var (next!)))
                      (let
                        ((kw2 (upcase-str (lp-peek))))
                        (cond
                          ((or (= kw2 "IN") (= kw2 "ACROSS"))
                            (do
                              (next!)
                              (let
                                ((lst-expr (next!))
                                  (tail-var (str "#TAIL-" var)))
                                (set!
                                  for-bindings
                                  (append for-bindings (list {:list lst-expr :tail tail-var :type ":list" :var var})))
                                (parse-clause!))))
                          ((= kw2 "=")
                            (do
                              (next!)
                              (let
                                ((init-expr (next!)))
                                (let
                                  ((then-expr (if (kw? (lp-peek) "THEN") (do (next!) (next!)) init-expr)))
                                  (set!
                                    for-bindings
                                    (append for-bindings (list {:type ":general" :then then-expr :init init-expr :var var})))
                                  (parse-clause!)))))
                          ((or (= kw2 "FROM") (= kw2 "DOWNFROM") (= kw2 "UPFROM"))
                            (do
                              (next!)
                              (let
                                ((from-expr (next!))
                                  (dir (if (= kw2 "DOWNFROM") ":down" ":up"))
                                  (limit-expr nil)
                                  (limit-type nil)
                                  (step-expr 1))
                                (let
                                  ((lkw (upcase-str (lp-peek))))
                                  (when
                                    (or
                                      (= lkw "TO")
                                      (= lkw "UPTO")
                                      (= lkw "BELOW")
                                      (= lkw "DOWNTO")
                                      (= lkw "ABOVE"))
                                    (do
                                      (next!)
                                      (set! limit-type lkw)
                                      (set! limit-expr (next!)))))
                                (when
                                  (kw? (lp-peek) "BY")
                                  (do (next!) (set! step-expr (next!))))
                                (set!
                                  for-bindings
                                  (append for-bindings (list {:dir dir :step step-expr :from from-expr :type ":numeric" :limit-type limit-type :var var :limit limit-expr})))
                                (parse-clause!))))
                          ((or (= kw2 "TO") (= kw2 "UPTO") (= kw2 "BELOW"))
                            (do
                              (next!)
                              (let
                                ((limit-expr (next!))
                                  (step-expr 1))
                                (when
                                  (kw? (lp-peek) "BY")
                                  (do (next!) (set! step-expr (next!))))
                                (set!
                                  for-bindings
                                  (append for-bindings (list {:dir ":up" :step step-expr :from 0 :type ":numeric" :limit-type kw2 :var var :limit limit-expr})))
                                (parse-clause!))))
                          (:else (do (parse-clause!))))))))
                ((= u "WHILE")
                  (do
                    (next!)
                    (set! test-forms (append test-forms (list {:expr (next!) :type ":while"})))
                    (parse-clause!)))
                ((= u "UNTIL")
                  (do
                    (next!)
                    (set! test-forms (append test-forms (list {:expr (next!) :type ":until"})))
                    (parse-clause!)))
                ((= u "REPEAT")
                  (do
                    (next!)
                    (set! repeat-count (next!))
                    (set! repeat-var "#REPEAT-COUNT")
                    (parse-clause!)))
                ((or (= u "COLLECT") (= u "COLLECTING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":collect" expr into-var)
                      (parse-clause!))))
                ((or (= u "APPEND") (= u "APPENDING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":append" expr into-var)
                      (parse-clause!))))
                ((or (= u "NCONC") (= u "NCONCING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":nconc" expr into-var)
                      (parse-clause!))))
                ((or (= u "SUM") (= u "SUMMING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":sum" expr into-var)
                      (parse-clause!))))
                ((or (= u "COUNT") (= u "COUNTING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":count" expr into-var)
                      (parse-clause!))))
                ((or (= u "MAXIMIZE") (= u "MAXIMIZING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":maximize" expr into-var)
                      (parse-clause!))))
                ((or (= u "MINIMIZE") (= u "MINIMIZING"))
                  (do
                    (next!)
                    (let
                      ((expr (next!)) (into-var nil))
                      (when
                        (kw? (lp-peek) "INTO")
                        (do (next!) (set! into-var (next!))))
                      (add-accum! ":minimize" expr into-var)
                      (parse-clause!))))
                ((= u "DO")
                  (do
                    (next!)
                    (define
                      (loop-kw? s)
                      (let
                        ((us (upcase-str s)))
                        (some
                          (fn (k) (= us k))
                          (list
                            "FOR"
                            "WITH"
                            "WHILE"
                            "UNTIL"
                            "REPEAT"
                            "COLLECT"
                            "COLLECTING"
                            "APPEND"
                            "APPENDING"
                            "NCONC"
                            "NCONCING"
                            "SUM"
                            "SUMMING"
                            "COUNT"
                            "COUNTING"
                            "MAXIMIZE"
                            "MAXIMIZING"
                            "MINIMIZE"
                            "MINIMIZING"
                            "DO"
                            "WHEN"
                            "IF"
                            "UNLESS"
                            "FINALLY"
                            "ALWAYS"
                            "NEVER"
                            "THEREIS"
                            "RETURN"
                            "NAMED"))))
                    (define
                      (collect-do-forms!)
                      (if
                        (or (nil? (lp-peek)) (loop-kw? (lp-peek)))
                        nil
                        (do
                          (set!
                            body-forms
                            (append body-forms (list (next!))))
                          (collect-do-forms!))))
                    (collect-do-forms!)
                    (parse-clause!)))
                ((or (= u "WHEN") (= u "IF"))
                  (do
                    (next!)
                    (let
                      ((cond-expr (next!))
                       (body-start (len body-forms)))
                      (parse-clause!)
                      ;; wrap forms added since body-start in (WHEN cond ...)
                      (when (> (len body-forms) body-start)
                        (let ((added (list (nth body-forms body-start))))
                          (set! body-forms
                            (append
                              (if (> body-start 0)
                                (list (nth body-forms (- body-start 1)))
                                (list))
                              (list (list "WHEN" cond-expr (first added)))))
                          nil)))))
                ((= u "UNLESS")
                  (do
                    (next!)
                    (let
                      ((cond-expr (next!))
                       (body-start (len body-forms)))
                      (parse-clause!)
                      (when (> (len body-forms) body-start)
                        (let ((added (list (nth body-forms body-start))))
                          (set! body-forms
                            (append
                              (if (> body-start 0)
                                (list (nth body-forms (- body-start 1)))
                                (list))
                              (list (list "UNLESS" cond-expr (first added)))))
                          nil)))))
                ((= u "ALWAYS")
                  (do (next!) (set! termination {:expr (next!) :type ":always"}) (parse-clause!)))
                ((= u "NEVER")
                  (do (next!) (set! termination {:expr (next!) :type ":never"}) (parse-clause!)))
                ((= u "THEREIS")
                  (do (next!) (set! termination {:expr (next!) :type ":thereis"}) (parse-clause!)))
                ((= u "RETURN")
                  (do (next!) (set! return-expr (next!)) (parse-clause!)))
                ((= u "FINALLY")
                  (do
                    (next!)
                    (define
                      (collect-finally!)
                      (if
                        (nil? (lp-peek))
                        nil
                        (do
                          (set!
                            finally-forms
                            (append finally-forms (list (next!))))
                          (collect-finally!))))
                    (collect-finally!)
                    (parse-clause!)))
                (:else
                  (do
                    (set! body-forms (append body-forms (list (next!))))
                    (parse-clause!)))))))))
    (parse-clause!)
    (define let-bindings (list))
    (for-each
      (fn (wb) (set! let-bindings (append let-bindings (list wb))))
      with-bindings)
    (for-each
      (fn
        (v)
        (set!
          let-bindings
          (append let-bindings (list (list v (get accum-vars v))))))
      (keys accum-vars))
    (when
      (not (nil? repeat-var))
      (set!
        let-bindings
        (append let-bindings (list (list repeat-var repeat-count)))))
    (for-each
      (fn
        (fb)
        (let
          ((type (get fb "type")))
          (cond
            ((= type ":list")
              (do
                (set!
                  let-bindings
                  (append
                    let-bindings
                    (list (list (get fb "tail") (get fb "list")))
                    (list
                      (list
                        (get fb "var")
                        (list
                          "IF"
                          (list "CONSP" (get fb "tail"))
                          (list "CAR" (get fb "tail"))
                          nil)))))
                nil))
            ((= type ":numeric")
              (set!
                let-bindings
                (append
                  let-bindings
                  (list (list (get fb "var") (get fb "from"))))))
            ((= type ":general")
              (set!
                let-bindings
                (append
                  let-bindings
                  (list (list (get fb "var") (get fb "init"))))))
            (:else nil))))
      for-bindings)
    (define all-tests (list))
    (when
      (not (nil? repeat-var))
      (set!
        all-tests
        (append
          all-tests
          (list
            (list
              "WHEN"
              (list "<=" repeat-var 0)
              (list "RETURN-FROM" block-name (if (nil? result-var) nil result-var))))))
      (set!
        body-forms
        (append
          (list (list "SETQ" repeat-var (list "-" repeat-var 1)))
          body-forms)))
    (for-each
      (fn
        (fb)
        (when
          (= (get fb "type") ":list")
          (let
            ((tvar (get fb "tail")) (var (get fb "var")))
            (set!
              all-tests
              (append
                all-tests
                (list
                  (list
                    "WHEN"
                    (list "NULL" tvar)
                    (list
                      "RETURN-FROM"
                      block-name
                      (if (nil? result-var) nil result-var))))))
            (set!
              body-forms
              (append
                body-forms
                (list
                  (list "SETQ" tvar (list "CDR" tvar))
                  (list
                    "SETQ"
                    var
                    (list "IF" (list "CONSP" tvar) (list "CAR" tvar) nil))))))))
      for-bindings)
    (for-each
      (fn
        (fb)
        (when
          (= (get fb "type") ":numeric")
          (let
            ((var (get fb "var"))
              (dir (get fb "dir"))
              (lim (get fb "limit"))
              (ltype (get fb "limit-type"))
              (step (get fb "step")))
            (when
              (not (nil? lim))
              (let
                ((test-op (cond ((or (= ltype "BELOW") (= ltype "ABOVE")) (if (= dir ":up") ">=" "<=")) ((or (= ltype "TO") (= ltype "UPTO")) ">") ((= ltype "DOWNTO") "<") (:else (if (= dir ":up") ">" "<")))))
                (set!
                  all-tests
                  (append
                    all-tests
                    (list
                      (list
                        "WHEN"
                        (list test-op var lim)
                        (list
                          "RETURN-FROM"
                          block-name
                          (if (nil? result-var) nil result-var))))))))
            (let
              ((step-op (if (or (= dir ":down") (= ltype "DOWNTO") (= ltype "ABOVE")) "-" "+")))
              (set!
                body-forms
                (append
                  body-forms
                  (list (list "SETQ" var (list step-op var step)))))))))
      for-bindings)
    (for-each
      (fn
        (fb)
        (when
          (= (get fb "type") ":general")
          (set!
            body-forms
            (append
              body-forms
              (list (list "SETQ" (get fb "var") (get fb "then")))))))
      for-bindings)
    (for-each
      (fn
        (t)
        (let
          ((type (get t "type")) (expr (get t "expr")))
          (if
            (= type ":while")
            (set!
              all-tests
              (append
                all-tests
                (list
                  (list
                    "WHEN"
                    (list "NOT" expr)
                    (list
                      "RETURN-FROM"
                      block-name
                      (if (nil? result-var) nil result-var))))))
            (set!
              all-tests
              (append
                all-tests
                (list
                  (list
                    "WHEN"
                    expr
                    (list
                      "RETURN-FROM"
                      block-name
                      (if (nil? result-var) nil result-var)))))))))
      test-forms)
    (when
      (not (nil? termination))
      (let
        ((type (get termination "type")) (expr (get termination "expr")))
        (cond
          ((= type ":always")
            (set!
              body-forms
              (append
                body-forms
                (list
                  (list "UNLESS" expr (list "RETURN-FROM" block-name false)))))
            (set! return-expr true))
          ((= type ":never")
            (set!
              body-forms
              (append
                body-forms
                (list
                  (list "WHEN" expr (list "RETURN-FROM" block-name false)))))
            (set! return-expr true))
          ((= type ":thereis")
            (set!
              body-forms
              (append
                body-forms
                (list
                  (list "WHEN" expr (list "RETURN-FROM" block-name expr)))))))))
    (define tag "#LOOP-START")
    (define
      inner-body
      (append (list tag) all-tests body-forms (list (list "GO" tag))))
    (define
      result-form
      (cond
        ((not (nil? return-expr)) return-expr)
        ((not (nil? result-var)) result-var)
        (:else nil)))
    (define
      full-body
      (if
        (= (len let-bindings) 0)
        (append
          (list "PROGN")
          (list (append (list "TAGBODY") inner-body))
          finally-forms
          (list result-form))
        (list
          "LET*"
          let-bindings
          (append (list "TAGBODY") inner-body)
          (append (list "PROGN") finally-forms (list result-form)))))
    (list "BLOCK" block-name full-body)))

;; ── Install LOOP as a CL macro ────────────────────────────────────────────
;;
;; (loop ...) — the form arrives with head "LOOP" and rest = clauses.
;; The macro fn receives the full form.

(dict-set!
  cl-macro-registry
  "LOOP"
  (fn (form env) (cl-loop-parse (rest form))))
