;; lib/kernel/parser.sx — Kernel s-expression reader.
;;
;; Reads R-1RK lexical syntax: numbers, strings, symbols, booleans (#t/#f),
;; the empty list (), nested lists, and ; line comments. Reader macros
;; (' ` , ,@) deferred to Phase 6 per the plan.
;;
;; Public AST shape:
;;   number      → SX number
;;   #t / #f     → SX true / false
;;   ()          → SX empty list (Kernel's nil — the empty list)
;;   "..."       → {:knl-string "..."}    wrapped to distinguish from symbols
;;   foo         → "foo"                  bare SX string is a Kernel symbol
;;   (a b c)     → SX list of forms
;;
;; Public API:
;;   (kernel-parse SRC)      — first form; errors on extra trailing input
;;   (kernel-parse-all SRC)  — all top-level forms, as SX list
;;   (kernel-string? V)      — recognise wrapped string literal
;;   (kernel-string-value V) — extract the underlying string
;;
;; Consumes: lib/guest/lex.sx (lex-digit?, lex-whitespace?)

(define kernel-string-make (fn (s) {:knl-string s}))
(define
  kernel-string?
  (fn (v) (and (dict? v) (string? (get v :knl-string)))))
(define kernel-string-value (fn (v) (get v :knl-string)))

;; Atom delimiters: characters that end a symbol or numeric token.
(define
  knl-delim?
  (fn
    (c)
    (or
      (nil? c)
      (lex-whitespace? c)
      (= c "(")
      (= c ")")
      (= c "\"")
      (= c ";")
      (= c "'")
      (= c "`")
      (= c ","))))

;; Numeric grammar:  [+-]? (digit+ ('.' digit+)? | '.' digit+) ([eE][+-]?digit+)?
(define
  knl-numeric?
  (fn
    (s)
    (let
      ((n (string-length s)))
      (cond
        ((= n 0) false)
        (:else
          (let
            ((c0 (substring s 0 1)))
            (let
              ((start (if (or (= c0 "+") (= c0 "-")) 1 0)))
              (knl-num-body? s start n))))))))

(define
  knl-num-body?
  (fn
    (s start n)
    (cond
      ((>= start n) false)
      ((= (substring s start (+ start 1)) ".")
        (knl-num-need-digits? s (+ start 1) n false))
      ((lex-digit? (substring s start (+ start 1)))
        (knl-num-int-tail? s (+ start 1) n))
      (:else false))))

(define
  knl-num-int-tail?
  (fn
    (s i n)
    (cond
      ((>= i n) true)
      ((lex-digit? (substring s i (+ i 1)))
        (knl-num-int-tail? s (+ i 1) n))
      ((= (substring s i (+ i 1)) ".")
        (knl-num-need-digits? s (+ i 1) n true))
      ((or (= (substring s i (+ i 1)) "e") (= (substring s i (+ i 1)) "E"))
        (knl-num-exp-sign? s (+ i 1) n))
      (:else false))))

(define
  knl-num-need-digits?
  (fn
    (s i n had-int)
    (cond
      ((>= i n) had-int)
      ((lex-digit? (substring s i (+ i 1)))
        (knl-num-frac-tail? s (+ i 1) n))
      (:else false))))

(define
  knl-num-frac-tail?
  (fn
    (s i n)
    (cond
      ((>= i n) true)
      ((lex-digit? (substring s i (+ i 1)))
        (knl-num-frac-tail? s (+ i 1) n))
      ((or (= (substring s i (+ i 1)) "e") (= (substring s i (+ i 1)) "E"))
        (knl-num-exp-sign? s (+ i 1) n))
      (:else false))))

(define
  knl-num-exp-sign?
  (fn
    (s i n)
    (cond
      ((>= i n) false)
      ((or (= (substring s i (+ i 1)) "+") (= (substring s i (+ i 1)) "-"))
        (knl-num-exp-digits? s (+ i 1) n false))
      (:else (knl-num-exp-digits? s i n false)))))

(define
  knl-num-exp-digits?
  (fn
    (s i n had)
    (cond
      ((>= i n) had)
      ((lex-digit? (substring s i (+ i 1)))
        (knl-num-exp-digits? s (+ i 1) n true))
      (:else false))))

;; Reader: a closure over (src, pos). Exposes :read-form and :read-all.
(define
  knl-make-reader
  (fn
    (src)
    (let
      ((pos 0) (n (string-length src)))
      (define
        at
        (fn () (if (< pos n) (substring src pos (+ pos 1)) nil)))
      (define adv (fn () (set! pos (+ pos 1))))
      (define
        skip-line
        (fn () (when (and (at) (not (= (at) "\n"))) (adv) (skip-line))))
      (define
        skip-ws
        (fn
          ()
          (cond
            ((nil? (at)) nil)
            ((lex-whitespace? (at)) (do (adv) (skip-ws)))
            ((= (at) ";") (do (adv) (skip-line) (skip-ws)))
            (:else nil))))
      (define
        read-string-body
        (fn
          (acc)
          (cond
            ((nil? (at)) (error "kernel-parse: unterminated string"))
            ((= (at) "\"") (do (adv) acc))
            ((= (at) "\\")
              (do
                (adv)
                (let
                  ((c (at)))
                  (when (nil? c) (error "kernel-parse: trailing backslash"))
                  (adv)
                  (read-string-body
                    (str
                      acc
                      (cond
                        ((= c "n") "\n")
                        ((= c "t") "\t")
                        ((= c "r") "\r")
                        ((= c "\"") "\"")
                        ((= c "\\") "\\")
                        (:else c)))))))
            (:else
              (let ((c (at))) (adv) (read-string-body (str acc c)))))))
      (define
        read-atom-body
        (fn
          (acc)
          (cond
            ((knl-delim? (at)) acc)
            (:else (let ((c (at))) (adv) (read-atom-body (str acc c)))))))
      (define
        classify-atom
        (fn
          (s)
          (cond
            ((= s "#t") true)
            ((= s "#f") false)
            ((knl-numeric? s) (string->number s))
            (:else s))))
      (define
        read-form
        (fn
          ()
          (skip-ws)
          (cond
            ((nil? (at)) :knl-eof)
            ((= (at) ")") (error "kernel-parse: unexpected ')'"))
            ((= (at) "(") (do (adv) (read-list (list))))
            ((= (at) "\"")
              (do (adv) (kernel-string-make (read-string-body ""))))
            ((= (at) "'")
              (do (adv) (list "$quote" (read-form))))
            ((= (at) "`")
              (do (adv) (list "$quasiquote" (read-form))))
            ((= (at) ",")
              (do (adv)
                  (cond
                    ((= (at) "@")
                      (do (adv) (list "$unquote-splicing" (read-form))))
                    (:else (list "$unquote" (read-form))))))
            (:else (classify-atom (read-atom-body ""))))))
      (define
        read-list
        (fn
          (acc)
          (skip-ws)
          (cond
            ((nil? (at)) (error "kernel-parse: unterminated list"))
            ((= (at) ")") (do (adv) acc))
            (:else (read-list (append acc (list (read-form))))))))
      (define
        read-all
        (fn
          (acc)
          (skip-ws)
          (if (nil? (at)) acc (read-all (append acc (list (read-form)))))))
      {:read-form read-form :read-all read-all})))

(define
  kernel-parse-all
  (fn (src) ((get (knl-make-reader src) :read-all) (list))))

(define
  kernel-parse
  (fn
    (src)
    (let
      ((r (knl-make-reader src)))
      (let
        ((form ((get r :read-form))))
        (cond
          ((= form :knl-eof) (error "kernel-parse: empty input"))
          (:else
            (let
              ((next ((get r :read-form))))
              (if
                (= next :knl-eof)
                form
                (error "kernel-parse: trailing input after first form")))))))))
