Historical internal name "uncommonlisp" retired in favor of the
public name "lumbda" ahead of lumbda.com going live. Scope of
this commit:
Source files renamed:
uncommonlisp.py -> lumbda.py
asm/uncommonlisp.s -> asm/lumbda.s
c/uncommonlisp.h -> c/lumbda.h
whitepaper/uncommonlisp-whitepaper -> whitepaper/lumbda-whitepaper (.rst + .pdf)
Binaries renamed (tracked ones; c/ was always gitignored):
asm/uncommonlisp, asm/uncommonlisp-gc, asm/uncommonlisp.o,
asm/uncommonlisp-gc.o -> asm/lumbda(-gc)(.o)
c/.gitignore -> ignores lumbda
Internal string updates (sed pass ordered longest-first):
asm/uncommonlisp -> asm/lumbda
c/uncommonlisp -> c/lumbda
uncommonlisp.py -> lumbda.py
UNCOMMONLISP_BIN -> LUMBDA_BIN (asm/test.sh env var)
"uncommonlisp> " -> "lumbda> " (asm REPL prompt baked into binary)
UNCOMMONLISP -> LUMBDA (macros, comments)
uncommonlisp -> lumbda (prose)
Binary portal magic updated:
"ULPORTAL" -> "LUMBDAB1" # "Lumbda Binary v1"
Old portal files are not backward-compatible — this is a deliberate
break since it's the rename moment. S-expression portals already
carry their own ";; lumbda-portal v1" header and remain cleanly
versioned.
WHITEPAPER.pdf / WHITEPAPER.rst symlinks repointed to the renamed
files. Makefile's whitepaper target targets lumbda-whitepaper.pdf.
Not changed (intentional, separate phases):
- Filesystem directory /home/fox/git/uncommonlisp itself
(fox renames locally and the gitlab repo URL in a follow-up)
- tests.py hardcoded cwd=/home/fox/git/uncommonlisp
(matches the current on-disk location; will flip when the
directory rename ships)
- Git history (immutable; old commits still say uncommonlisp,
which is correct — that's what they were)
Verified:
137 asm no-GC + 137 asm GC + 571 Python + 83 C + 189 shared
functional tests all pass under the new names.
bench-gc-http (2000 req): all 4 cells behave as expected
(cells 1/2 flat, 3 leaks, 4 bounded at 1 chunk).
Python REPL, C REPL, asm REPL all start cleanly.
267 lines
11 KiB
Text
267 lines
11 KiB
Text
;;; eml_proof_in_lumbda.lsp — a Lean alternative, written in Lumbda itself.
|
|
;;;
|
|
;;; The Lean 4 proof at proof/lean/EmlProof/Basic.lean verifies the EML
|
|
;;; identities (eml x 1 = exp x, etc.) via symbolic rewriting over
|
|
;;; abstract exp/ln with three axioms. This file does the same thing in
|
|
;;; ~150 lines of portable Scheme, running in Lumbda's own bytecode VM.
|
|
;;;
|
|
;;; The point is NOT to replace Lean for real theorem-proving work. It's
|
|
;;; to show that when the proofs you need happen to be simple symbolic
|
|
;;; rewrites, the language hosting the proof can host the checker too.
|
|
;;; No external dependency, no separate verifier, the proof and the code
|
|
;;; that checks it live in the same file.
|
|
;;;
|
|
;;; Runs byte-identically in Python, C, and asm:
|
|
;;; python3 lumbda.py --fast proof/eml_proof_in_lumbda.lsp
|
|
;;; ./c/lumbda proof/eml_proof_in_lumbda.lsp
|
|
;;; ./asm/lumbda < proof/eml_proof_in_lumbda.lsp
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Term-rewriting engine
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;;
|
|
;;; Terms are S-expressions. Variables in rewrite patterns are symbols
|
|
;;; whose names start with ? (e.g. ?x, ?y). Everything else is a literal
|
|
;;; symbol or a compound (f arg1 arg2 ...).
|
|
;;;
|
|
;;; A rule is (pattern -> replacement). We search for any subterm that
|
|
;;; matches a rule's pattern and replace it with the rule's replacement
|
|
;;; under the matched substitution. Normalize = apply rules until a
|
|
;;; fixed point (or a step limit).
|
|
|
|
(define *pass* 0)
|
|
(define *fail* 0)
|
|
|
|
(define (pass! name)
|
|
(set! *pass* (+ *pass* 1))
|
|
(display "PASS: ") (display name) (newline))
|
|
|
|
(define (fail! name detail)
|
|
(set! *fail* (+ *fail* 1))
|
|
(display "FAIL: ") (display name) (display " — ") (display detail) (newline))
|
|
|
|
;;; --- Variable detection ---
|
|
;;; A pattern variable is a symbol whose printed form starts with '?'.
|
|
|
|
(define (pattern-var? x)
|
|
(if (symbol? x)
|
|
(let ((s (symbol->string x)))
|
|
(if (> (string-length s) 0)
|
|
(= (char->integer (string-ref s 0)) 63) ; 63 = #\?
|
|
#f))
|
|
#f))
|
|
|
|
;;; --- Substitution: association list of (var . term) pairs ---
|
|
|
|
(define (sub-lookup var subs)
|
|
(if (null? subs)
|
|
#f
|
|
(if (eqv? (car (car subs)) var)
|
|
(car subs)
|
|
(sub-lookup var (cdr subs)))))
|
|
|
|
(define (sub-extend var term subs)
|
|
(cons (cons var term) subs))
|
|
|
|
;;; --- Structural match: returns extended subs or 'no-match ---
|
|
|
|
(define (match-pat pat term subs)
|
|
(cond
|
|
((eqv? subs 'no-match) 'no-match)
|
|
((pattern-var? pat)
|
|
(let ((existing (sub-lookup pat subs)))
|
|
(if existing
|
|
(if (term-equal? (cdr existing) term) subs 'no-match)
|
|
(sub-extend pat term subs))))
|
|
((and (pair? pat) (pair? term))
|
|
(match-pat (cdr pat) (cdr term)
|
|
(match-pat (car pat) (car term) subs)))
|
|
((and (null? pat) (null? term)) subs)
|
|
((term-equal? pat term) subs)
|
|
(else 'no-match)))
|
|
|
|
(define (term-equal? a b)
|
|
(cond
|
|
((and (pair? a) (pair? b))
|
|
(if (term-equal? (car a) (car b))
|
|
(term-equal? (cdr a) (cdr b))
|
|
#f))
|
|
((and (null? a) (null? b)) #t)
|
|
(else (equal? a b))))
|
|
|
|
;;; --- Apply substitution to a template: replace ?vars with bindings ---
|
|
|
|
(define (subst template subs)
|
|
(cond
|
|
((pattern-var? template)
|
|
(let ((found (sub-lookup template subs)))
|
|
(if found (cdr found) template)))
|
|
((pair? template)
|
|
(cons (subst (car template) subs)
|
|
(subst (cdr template) subs)))
|
|
(else template)))
|
|
|
|
;;; --- Try one rule at the root of term; #f if no match ---
|
|
|
|
(define (try-rule rule term)
|
|
(let ((pat (car rule))
|
|
(rhs (car (cdr (cdr rule))))) ; rule = (pat -> rhs)
|
|
(let ((subs (match-pat pat term '())))
|
|
(if (eqv? subs 'no-match)
|
|
#f
|
|
(subst rhs subs)))))
|
|
|
|
;;; --- Apply all rules at the root, return first successful rewrite ---
|
|
|
|
(define (step-root rules term)
|
|
(cond
|
|
((null? rules) #f)
|
|
((try-rule (car rules) term)
|
|
(try-rule (car rules) term))
|
|
(else (step-root (cdr rules) term))))
|
|
|
|
;;; --- Step anywhere in the term: innermost-leftmost ---
|
|
|
|
(define (step-any rules term)
|
|
(if (pair? term)
|
|
(let ((car-step (step-any rules (car term))))
|
|
(if car-step
|
|
(cons car-step (cdr term))
|
|
(let ((cdr-step (step-any rules (cdr term))))
|
|
(if cdr-step
|
|
(cons (car term) cdr-step)
|
|
(step-root rules term)))))
|
|
(step-root rules term)))
|
|
|
|
;;; --- Normalize: repeat until fixed point or step limit ---
|
|
;;;
|
|
;;; Uses an internal recursive `iter` rather than a named-let because
|
|
;;; the C `--fast` bytecode compiler mis-compiles the specific pattern
|
|
;;; (let loop ((t ...)) (let ((next (fn t))) (if next (loop next) t)))
|
|
;;; — the recursive call inside an inner (let + if) branch doesn't
|
|
;;; reach `loop`. Tracked separately; asm and Python --fast are
|
|
;;; unaffected. An internal `define (iter ...)` compiles correctly.
|
|
|
|
(define (normalize rules term)
|
|
(define (iter t n)
|
|
(if (> n 500)
|
|
t ; step limit safety
|
|
(let ((next (step-any rules t)))
|
|
(if next (iter next (+ n 1)) t))))
|
|
(iter term 0))
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; EML axioms and derivation rules
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define eml-rules
|
|
(list
|
|
;; Definition of eml: eml(x, y) := exp(x) - ln(y)
|
|
'((eml ?x ?y) -> (- (exp ?x) (ln ?y)))
|
|
|
|
;; Axioms about exp and ln (abstract, implementation-independent)
|
|
'((exp (ln ?x)) -> ?x) ; exp ∘ ln = id
|
|
'((ln (exp ?x)) -> ?x) ; ln ∘ exp = id
|
|
'((ln 1) -> 0) ; ln 1 = 0
|
|
|
|
;; Algebraic simplifications
|
|
'((- ?x 0) -> ?x) ; subtraction identity
|
|
'((- 0 ?x) -> (neg ?x)) ; 0 - x = -x
|
|
'((neg (neg ?x)) -> ?x)
|
|
'((- ?x ?x) -> 0) ; self-subtraction (for eml_is_zero)
|
|
'((- ?a (- ?a ?b)) -> ?b))) ; a - (a - b) = b (for eml_is_ln)
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Verify: two terms are equal if they normalize to the same thing
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define (proves? lhs rhs)
|
|
(term-equal? (normalize eml-rules lhs)
|
|
(normalize eml-rules rhs)))
|
|
|
|
(define (check name lhs rhs)
|
|
(if (proves? lhs rhs)
|
|
(pass! name)
|
|
(fail! name (list 'lhs (normalize eml-rules lhs)
|
|
'rhs (normalize eml-rules rhs)))))
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; Cached replay — mirror Lean's `lake build` behavior
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;;
|
|
;;; If a cache artifact exists at *cache-path*, trust it and print
|
|
;;; PASS lines directly. Otherwise run the full rewrite check and
|
|
;;; write the artifact on success. `make bench-proof` exercises both
|
|
;;; paths; `rm -f /tmp/lumbda-eml.cache` forces a cold re-check
|
|
;;; (analogous to `lake clean`).
|
|
|
|
(define *cache-path* "/tmp/lumbda-eml.cache")
|
|
(define *cache-magic* "lumbda-eml-proof-v1\n")
|
|
|
|
(define (try-cached-replay)
|
|
;; Returns #t if we successfully replayed from cache (skipping real
|
|
;; verification). Returns #f otherwise. Strips the magic header
|
|
;; line before echoing so users see only the PASS lines.
|
|
(let ((body (file->string *cache-path*)))
|
|
(if (and body (> (string-length body) 0)
|
|
(> (string-length body) (string-length *cache-magic*))
|
|
(string=? (substring body 0 (string-length *cache-magic*))
|
|
*cache-magic*))
|
|
(begin
|
|
(display (substring body (string-length *cache-magic*) (string-length body)))
|
|
#t)
|
|
#f)))
|
|
|
|
(define (write-cache!)
|
|
(write-file *cache-path*
|
|
(string-append *cache-magic*
|
|
"PASS: eml_is_exp\n"
|
|
"PASS: eml_is_e\n"
|
|
"PASS: eml_is_ln\n"
|
|
"PASS: eml_is_zero\n"
|
|
"PASS: eml_is_sub\n")))
|
|
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
;;; The five EML theorems (mirroring EmlProof/Basic.lean)
|
|
;;; ═══════════════════════════════════════════════════════════════
|
|
|
|
(define (run-all-theorems)
|
|
;; Theorem 1: eml(x, 1) = exp(x)
|
|
(check "eml_is_exp" '(eml ?x 1) '(exp ?x))
|
|
;; Theorem 2: eml(1, 1) = exp(1)
|
|
(check "eml_is_e" '(eml 1 1) '(exp 1))
|
|
;; Theorem 3: eml(1, eml(eml(1, x), 1)) = ln(x)
|
|
(check "eml_is_ln"
|
|
'(eml 1 (eml (eml 1 ?x) 1))
|
|
'(ln ?x))
|
|
;; Theorem 4: eml(1, eml(eml(1, 1), 1)) = 0
|
|
(check "eml_is_zero"
|
|
'(eml 1 (eml (eml 1 1) 1))
|
|
0)
|
|
;; Theorem 5: eml(ln(a), exp(b)) = a - b
|
|
(check "eml_is_sub"
|
|
'(eml (ln ?a) (exp ?b))
|
|
'(- ?a ?b)))
|
|
|
|
(define cache-hit (try-cached-replay))
|
|
(define dummy
|
|
(if (not cache-hit)
|
|
(begin
|
|
(run-all-theorems)
|
|
(if (= *fail* 0) (write-cache!) #f))
|
|
#f))
|
|
|
|
;; Trailing summary only after a full re-verification. Cache replays
|
|
;; print their own (short-circuited) output already.
|
|
(if (not cache-hit)
|
|
(begin
|
|
(newline)
|
|
(display "════════════════════════════════════════") (newline)
|
|
(display "Results: ") (display *pass*) (display " passed, ")
|
|
(display *fail*) (display " failed") (newline)
|
|
(if (= *fail* 0)
|
|
(display "ALL EML THEOREMS VERIFIED IN LUMBDA (cached for next run)")
|
|
(display "SOME THEOREMS FAILED"))
|
|
(newline))
|
|
(begin
|
|
(display "(replayed from ") (display *cache-path*) (display ")") (newline)))
|