;;; repl-server.lsp — REMOTE SCHEME REPL OVER TCP.
;;;
;;; DANGER: this accepts ANY S-expression from the network and evaluates
;;; it in the global environment. Anyone who can reach the TCP port can
;;; run arbitrary Scheme code in this process — read files, open sockets,
;;; shell out via system calls if any are exposed, leak the env, etc.
;;;
;;; Run on localhost only. Do not expose publicly. This exists to show
;;; what the "language IS the interchange" thesis gets you when taken
;;; to its logical end: a single socket carries a full-powered REPL
;;; because both sides already have a reader and an evaluator.
;;;
;;; Usage:
;;;   python3 lumbda.py --fast examples/repl-server.lsp
;;;   ./c/lumbda examples/repl-server.lsp
;;;   ./asm/lumbda < examples/repl-server.lsp
;;;
;;; Then from a client:
;;;   (define x 42)      ; server mutates its global env
;;;   (* x 10)           ; => 420
;;;   (map car '((1 a) (2 b))) ; => (1 2)
;;;
;;; Each connection = one request + one response. Persistent sessions
;;; across connections because all defines land in the shared global env.

(define *port* 9081)
(define *max-requests* 10000)

;; Portable serializer — same as rpc-server.lsp pattern.
;; Avoids open-output-string and `guard` (neither exists in asm).

(define (atom->string v)
  (cond
    ((number? v) (number->string v))
    ((symbol? v) (symbol->string v))
    ((null? v)   "()")
    ((pair? v)   (string-append "(" (list->string v) ")"))
    ((string? v) (string-append "\"" v "\""))
    (else        "#<unknown>")))

(define (list->string lst)
  (cond
    ((null? lst) "")
    ((null? (cdr lst)) (atom->string (car lst)))
    (else (string-append (atom->string (car lst)) " " (list->string (cdr lst))))))

(define (response->string v)
  (cond
    ((number? v)  (string-append (number->string v) "\n"))
    ((symbol? v)  (string-append (symbol->string v) "\n"))
    ((null? v)    "()\n")
    ((pair? v)    (string-append "(" (list->string v) ")\n"))
    ((string? v)  (string-append "\"" v "\"\n"))
    (else         "#<unknown>\n")))

(define (handle-request raw)
  (let ((form (read-from-string raw)))
    (if (eqv? form #f)
        "(error \"empty or malformed input\")\n"
        (response->string (eval form)))))

(define server (tcp-listen *port*))

;; Intentionally does NOT use heap-snapshot. A remote `(define x ...)`
;; adds a new binding to the global env chain — heap cells allocated
;; AFTER any snapshot point. Rewinding would invalidate those bindings.
;; The asm heap grows with each new top-level define; the ulimit -v
;; safety net kills the process if it escapes. Each request still
;; produces garbage (tcp-recv buffer, intermediate strings) that stays
;; forever — acceptable for a demo, at ~100 bytes per request plus
;; whatever `define` binds.

(define (server-loop n)
  (if (>= n *max-requests*)
      (begin (display "request cap reached, exiting\n") (tcp-close server))
      (begin
        (let ((client (tcp-accept server)))
          (let ((req (tcp-recv client 8192)))
            (if (and req (> (string-length req) 0))
                (tcp-send client (handle-request req))
                #f))
          (tcp-close client))
        (server-loop (+ n 1)))))

(display "repl-server on :") (display *port*)
(display " — DANGER: full remote eval") (newline)
(server-loop 0)
