;;; 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 "#"))) (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 "#\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)