;; Common Lisp reader — converts token stream to CL AST forms. ;; ;; Depends on: lib/common-lisp/reader.sx (cl-tokenize) ;; ;; AST representation: ;; integer/float → SX number (or {:cl-type "float"/:ratio ...}) ;; string → SX string ;; symbol FOO → SX string "FOO" (upcase) ;; symbol NIL → nil ;; symbol T → true ;; :keyword → {:cl-type "keyword" :name "FOO"} ;; #\char → {:cl-type "char" :value "a"} ;; #:uninterned → {:cl-type "uninterned" :name "FOO"} ;; ratio 1/3 → {:cl-type "ratio" :value "1/3"} ;; float 3.14 → {:cl-type "float" :value "3.14"} ;; proper list (a b c) → SX list (a b c) ;; dotted pair (a . b) → {:cl-type "cons" :car a :cdr b} ;; vector #(a b) → {:cl-type "vector" :elements (list a b)} ;; 'x → ("QUOTE" x) ;; `x → ("QUASIQUOTE" x) ;; ,x → ("UNQUOTE" x) ;; ,@x → ("UNQUOTE-SPLICING" x) ;; #'x → ("FUNCTION" x) ;; ;; Public API: ;; (cl-read src) — parse first form from string, return form ;; (cl-read-all src) — parse all top-level forms, return list ;; ── number conversion ───────────────────────────────────────────── (define cl-hex-val (fn (c) (let ((o (cl-ord c))) (cond ((and (>= o 48) (<= o 57)) (- o 48)) ((and (>= o 65) (<= o 70)) (+ 10 (- o 65))) ((and (>= o 97) (<= o 102)) (+ 10 (- o 97))) (:else 0))))) (define cl-parse-radix-str (fn (s radix start) (let ((n (string-length s)) (i start) (acc 0)) (define loop (fn () (when (< i n) (do (set! acc (+ (* acc radix) (cl-hex-val (substring s i (+ i 1))))) (set! i (+ i 1)) (loop))))) (loop) acc))) (define cl-convert-integer (fn (s) (let ((n (string-length s)) (neg false)) (cond ((and (> n 2) (= (substring s 0 1) "#")) (let ((letter (downcase (substring s 1 2)))) (cond ((= letter "x") (cl-parse-radix-str s 16 2)) ((= letter "b") (cl-parse-radix-str s 2 2)) ((= letter "o") (cl-parse-radix-str s 8 2)) (:else (parse-int s 0))))) (:else (parse-int s 0)))))) ;; ── reader ──────────────────────────────────────────────────────── ;; Read one form from token list. ;; Returns {:form F :rest remaining-toks} or {:form nil :rest toks :eof true} (define cl-read-form (fn (toks) (if (not toks) {:form nil :rest toks :eof true} (let ((tok (nth toks 0)) (nxt (rest toks))) (let ((type (get tok "type")) (val (get tok "value"))) (cond ((= type "eof") {:form nil :rest toks :eof true}) ((= type "integer") {:form (cl-convert-integer val) :rest nxt}) ((= type "float") {:form {:cl-type "float" :value val} :rest nxt}) ((= type "ratio") {:form {:cl-type "ratio" :value val} :rest nxt}) ((= type "string") {:form val :rest nxt}) ((= type "char") {:form {:cl-type "char" :value val} :rest nxt}) ((= type "keyword") {:form {:cl-type "keyword" :name val} :rest nxt}) ((= type "uninterned") {:form {:cl-type "uninterned" :name val} :rest nxt}) ((= type "symbol") (cond ((= val "NIL") {:form nil :rest nxt}) ((= val "T") {:form true :rest nxt}) (:else {:form val :rest nxt}))) ;; list forms ((= type "lparen") (cl-read-list nxt)) ((= type "hash-paren") (cl-read-vector nxt)) ;; reader macros that wrap the next form ((= type "quote") (cl-read-wrap "QUOTE" nxt)) ((= type "backquote") (cl-read-wrap "QUASIQUOTE" nxt)) ((= type "comma") (cl-read-wrap "UNQUOTE" nxt)) ((= type "comma-at") (cl-read-wrap "UNQUOTE-SPLICING" nxt)) ((= type "hash-quote") (cl-read-wrap "FUNCTION" nxt)) ;; skip unrecognised tokens (:else (cl-read-form nxt)))))))) ;; Wrap next form in a list: (name form) (define cl-read-wrap (fn (name toks) (let ((inner (cl-read-form toks))) {:form (list name (get inner "form")) :rest (get inner "rest")}))) ;; Read list forms until ')'; handles dotted pair (a . b) ;; Called after consuming '(' (define cl-read-list (fn (toks) (let ((result (cl-read-list-items toks (list)))) {:form (get result "items") :rest (get result "rest")}))) (define cl-read-list-items (fn (toks acc) (if (not toks) {:items acc :rest toks} (let ((tok (nth toks 0))) (let ((type (get tok "type"))) (cond ((= type "eof") {:items acc :rest toks}) ((= type "rparen") {:items acc :rest (rest toks)}) ;; dotted pair: read one more form then expect ')' ((= type "dot") (let ((cdr-result (cl-read-form (rest toks)))) (let ((cdr-form (get cdr-result "form")) (after-cdr (get cdr-result "rest"))) ;; skip the closing ')' (let ((close (if after-cdr (nth after-cdr 0) nil))) (let ((remaining (if (and close (= (get close "type") "rparen")) (rest after-cdr) after-cdr))) ;; build dotted structure (let ((dotted (cl-build-dotted acc cdr-form))) {:items dotted :rest remaining})))))) (:else (let ((item (cl-read-form toks))) (cl-read-list-items (get item "rest") (concat acc (list (get item "form")))))))))))) ;; Build dotted form: (a b . c) → ((DOTTED a b) . c) style ;; In CL (a b c . d) means a proper dotted structure. ;; We represent it as {:cl-type "cons" :car a :cdr (list->dotted b c d)} (define cl-build-dotted (fn (head-items tail) (if (= (len head-items) 0) tail (if (= (len head-items) 1) {:cl-type "cons" :car (nth head-items 0) :cdr tail} (let ((last-item (nth head-items (- (len head-items) 1))) (but-last (slice head-items 0 (- (len head-items) 1)))) {:cl-type "cons" :car (cl-build-dotted but-last (list last-item)) :cdr tail}))))) ;; Read vector #(…) elements until ')' (define cl-read-vector (fn (toks) (let ((result (cl-read-vector-items toks (list)))) {:form {:cl-type "vector" :elements (get result "items")} :rest (get result "rest")}))) (define cl-read-vector-items (fn (toks acc) (if (not toks) {:items acc :rest toks} (let ((tok (nth toks 0))) (let ((type (get tok "type"))) (cond ((= type "eof") {:items acc :rest toks}) ((= type "rparen") {:items acc :rest (rest toks)}) (:else (let ((item (cl-read-form toks))) (cl-read-vector-items (get item "rest") (concat acc (list (get item "form")))))))))))) ;; ── lambda-list parser ─────────────────────────────────────────── ;; ;; (cl-parse-lambda-list forms) — parse a list of CL forms (already read) ;; into a structured dict: ;; {:required (list sym ...) ;; :optional (list {:name N :default D :supplied S} ...) ;; :rest nil | "SYM" ;; :key (list {:name N :keyword K :default D :supplied S} ...) ;; :allow-other-keys false | true ;; :aux (list {:name N :init I} ...)} ;; ;; Symbols arrive as SX strings (upcase). &-markers are strings like "&OPTIONAL". ;; Key params: keyword is the upcase name string; caller uses it as :keyword. ;; Supplied-p: nil when absent. (define cl-parse-opt-spec (fn (spec) (if (list? spec) {:name (nth spec 0) :default (if (> (len spec) 1) (nth spec 1) nil) :supplied (if (> (len spec) 2) (nth spec 2) nil)} {:name spec :default nil :supplied nil}))) (define cl-parse-key-spec (fn (spec) (if (list? spec) (let ((first (nth spec 0))) (if (list? first) ;; ((:keyword var) default supplied-p) {:name (nth first 1) :keyword (get first "name") :default (if (> (len spec) 1) (nth spec 1) nil) :supplied (if (> (len spec) 2) (nth spec 2) nil)} ;; (var default supplied-p) {:name first :keyword first :default (if (> (len spec) 1) (nth spec 1) nil) :supplied (if (> (len spec) 2) (nth spec 2) nil)})) {:name spec :keyword spec :default nil :supplied nil}))) (define cl-parse-aux-spec (fn (spec) (if (list? spec) {:name (nth spec 0) :init (if (> (len spec) 1) (nth spec 1) nil)} {:name spec :init nil}))) (define cl-parse-lambda-list (fn (forms) (let ((state "required") (required (list)) (optional (list)) (rest-name nil) (key (list)) (allow-other-keys false) (aux (list))) (define scan (fn (items) (when (> (len items) 0) (let ((item (nth items 0)) (tail (rest items))) (cond ((= item "&OPTIONAL") (do (set! state "optional") (scan tail))) ((= item "&REST") (do (set! state "rest") (scan tail))) ((= item "&BODY") (do (set! state "rest") (scan tail))) ((= item "&KEY") (do (set! state "key") (scan tail))) ((= item "&AUX") (do (set! state "aux") (scan tail))) ((= item "&ALLOW-OTHER-KEYS") (do (set! allow-other-keys true) (scan tail))) ((= state "required") (do (append! required item) (scan tail))) ((= state "optional") (do (append! optional (cl-parse-opt-spec item)) (scan tail))) ((= state "rest") (do (set! rest-name item) (set! state "done") (scan tail))) ((= state "key") (do (append! key (cl-parse-key-spec item)) (scan tail))) ((= state "aux") (do (append! aux (cl-parse-aux-spec item)) (scan tail))) (:else (scan tail))))))) (scan forms) {:required required :optional optional :rest rest-name :key key :allow-other-keys allow-other-keys :aux aux}))) ;; Convenience: parse lambda list from a CL source string (define cl-parse-lambda-list-str (fn (src) (cl-parse-lambda-list (cl-read src)))) ;; ── public API ──────────────────────────────────────────────────── (define cl-read (fn (src) (let ((toks (cl-tokenize src))) (get (cl-read-form toks) "form")))) (define cl-read-all (fn (src) (let ((toks (cl-tokenize src))) (define loop (fn (toks acc) (if (or (not toks) (= (get (nth toks 0) "type") "eof")) acc (let ((result (cl-read-form toks))) (if (get result "eof") acc (loop (get result "rest") (concat acc (list (get result "form"))))))))) (loop toks (list)))))