;;; portal-formats.lsp — Benchmark three portal formats
;;;
;;; 1. S-expression (portable — any implementation can read)
;;; 2. JSON (Python + C)
;;; 3. Binary heap dump (assembly only, fastest)
;;;
;;; Run in Python: python3 lumbda.py --fast tests/portal-formats.lsp
;;; Run in C:      ./c/lumbda tests/portal-formats.lsp
;;; Asm:           asm/lumbda < tests/portal-cross-load.lsp  (no `load` builtin)
;;;
;;; ── Observed results (2026-04-16, same laptop) ─────────────────
;;;
;;;                  | save time | load time | file size
;;;   Python S-exp   |  ~0.55 ms |  ~0.38 ms |   310 B
;;;   Python JSON    |  ~3.07 ms |  (n/a)    | 25,855 B
;;;   C      S-exp   |  ~0.17 ms |  ~0.03 ms |   310 B
;;;   Asm    binary  |  ~0.08 ms |  ~0.08 ms |  ~10 KB
;;;
;;; Insight: S-expression is ~80x smaller than JSON for the same
;;; state, and every implementation already has a parser for it —
;;; because the language IS the interchange format.
;;;
;;; Cross-impl verified: Python→C, C→Python, Python→asm, C→asm
;;; (see tests/portal-cross-save.lsp + portal-cross-load.lsp)

;;; ═══════════════════════════════════════════════════════════════
;;; Setup: create some state to serialize
;;; ═══════════════════════════════════════════════════════════════

(define my-int 42)
(define my-list (list 1 2 3 4 5 6 7 8 9 10))
(define my-string "hello world")
(define my-nested (list (list 1 2) (list 3 4) (list 5 6)))
(define my-alist (list (cons 'a 1) (cons 'b 2) (cons 'c 3)))

(define (my-fib n)
  (let loop ((a 0) (b 1) (i 0))
    (if (= i n) a (loop b (+ a b) (+ i 1)))))

(define my-result (my-fib 30))

;;; ═══════════════════════════════════════════════════════════════
;;; Format 1: S-expression (portable)
;;; Write values as Scheme (define ...) forms. Any impl can (load) it.
;;; ═══════════════════════════════════════════════════════════════

(define (write-sexp-portal filename)
  (let ((port (open-output-file filename)))
    ;; Header
    (display ";; lumbda portable state (S-expression format)" port)
    (newline port)
    (display ";; resume: (load \"" port)
    (display filename port)
    (display "\")" port)
    (newline port)
    (newline port)
    ;; Values
    (display "(define my-int " port) (write my-int port) (display ")" port) (newline port)
    (display "(define my-list '" port) (write my-list port) (display ")" port) (newline port)
    (display "(define my-string " port) (write my-string port) (display ")" port) (newline port)
    (display "(define my-nested '" port) (write my-nested port) (display ")" port) (newline port)
    (display "(define my-alist (list" port)
    (for-each (lambda (p)
      (display " (cons '" port) (write (car p) port)
      (display " " port) (write (cdr p) port) (display ")" port))
      my-alist)
    (display "))" port) (newline port)
    (display "(define my-result " port) (write my-result port) (display ")" port) (newline port)
    (close-port port)))

(define t0 (current-time))
(write-sexp-portal "/tmp/state.sexp")
(define t1 (current-time))

(display "S-expression save: ")
(display (* (- t1 t0) 1000))
(display "ms") (newline)

;;; ═══════════════════════════════════════════════════════════════
;;; Verify: load it back
;;; ═══════════════════════════════════════════════════════════════

;; Clear the values
(set! my-int 0)
(set! my-list '())
(set! my-string "")
(set! my-result 0)

(define t2 (current-time))
(load "/tmp/state.sexp")
(define t3 (current-time))

(display "S-expression load: ")
(display (* (- t3 t2) 1000))
(display "ms") (newline)

;; Verify
(display "Verify: my-int=") (display my-int)
(display " my-result=") (display my-result)
(display " my-list=") (display my-list)
(newline)

(if (and (= my-int 42) (= my-result 832040) (= (length my-list) 10))
    (display "PASS: S-expression portal round-trip")
    (display "FAIL: S-expression portal round-trip"))
(newline)

;;; ═══════════════════════════════════════════════════════════════
;;; Format 2: JSON (via portal-save builtin if available)
;;; Python binds `portal-save` directly. C binds `portal-save!` as a
;;; VM-level checkpoint (deferred save). We probe by procedure? so this
;;; stays portable and never invokes an unbound callable.
;;; ═══════════════════════════════════════════════════════════════

(define json-saver
  (guard (e (#t #f))
    (if (procedure? portal-save) portal-save #f)))

(if json-saver
    (let ((t4 (current-time)))
      (json-saver "/tmp/state.json")
      (let ((t5 (current-time)))
        (display "JSON save: ")
        (display (* (- t5 t4) 1000))
        (display "ms") (newline)))
    (display "JSON save: skipped (no direct portal-save in this impl)\n"))

;;; ═══════════════════════════════════════════════════════════════
;;; Summary
;;; ═══════════════════════════════════════════════════════════════

(newline)
(display "Portal format comparison:") (newline)
(display "  S-expression: portable — every impl parses it") (newline)
(display "  JSON:         Python + C, graph-aware, preserves sharing") (newline)
(display "  Binary:       asm only — raw heap dump, same-arch restore") (newline)
(newline)
(display "Files left at /tmp/state.sexp and /tmp/state.json for size comparison.") (newline)
(display "Run `wc -c /tmp/state.*` in shell to compare sizes.") (newline)
