Monorepo for Aesthetic.Computer aesthetic.computer
at main 134 lines 5.4 kB view raw
1;;; kidlisp-parser.lisp — Tokenizer + reader for KidLisp dialect 2;;; Handles auto-wrapping bare lines, comma separation, timing syntax. 3 4(in-package :ac-native.kidlisp) 5 6(defun timing-token-p (s) 7 "Is S a timing token like '1s', '2s...', '0.5s!'?" 8 (and (stringp s) 9 (> (length s) 1) 10 (let ((base (string-right-trim ".!" s))) 11 (and (> (length base) 0) 12 (char= (char base (1- (length base))) #\s) 13 (every (lambda (c) (or (digit-char-p c) (char= c #\.))) 14 (subseq base 0 (1- (length base)))))))) 15 16(defun parse-number (s) 17 "Try to parse S as a number. Returns number or NIL." 18 (handler-case 19 (let ((n (read-from-string s))) 20 (when (numberp n) n)) 21 (error () nil))) 22 23(defun tokenize (source) 24 "Split KidLisp source into tokens (strings)." 25 (let ((tokens nil) 26 (i 0) 27 (len (length source))) 28 (flet ((peek () (when (< i len) (char source i))) 29 (advance () (prog1 (char source i) (incf i)))) 30 (loop while (< i len) do 31 (let ((c (peek))) 32 (cond 33 ;; Whitespace 34 ((member c '(#\Space #\Tab #\Newline #\Return)) 35 (advance)) 36 ;; Comment 37 ((char= c #\;) 38 (loop while (and (< i len) (not (char= (peek) #\Newline))) 39 do (advance))) 40 ;; Parens and comma 41 ((char= c #\() (push "(" tokens) (advance)) 42 ((char= c #\)) (push ")" tokens) (advance)) 43 ((char= c #\,) (advance)) ; skip commas as separators 44 ;; Quoted string 45 ((or (char= c #\") (char= c #\')) 46 (let ((quote c) 47 (start i)) 48 (advance) ; skip opening quote 49 (loop while (and (< i len) (not (char= (peek) quote))) 50 do (when (char= (peek) #\\) (advance)) ; skip escape 51 (advance)) 52 (when (< i len) (advance)) ; skip closing quote 53 (push (subseq source (1+ start) (1- i)) tokens))) 54 ;; Atom (symbol, number, color name, timing token) 55 (t 56 (let ((start i)) 57 (loop while (and (< i len) 58 (not (member (peek) '(#\Space #\Tab #\Newline #\Return 59 #\( #\) #\, #\;)))) 60 do (advance)) 61 (push (subseq source start i) tokens))))))) 62 (nreverse tokens))) 63 64(defun bare-line-p (line) 65 "Is LINE a bare expression that needs auto-wrapping in parens? 66Bare lines start with a word (not a paren) and have arguments." 67 (let ((trimmed (string-trim '(#\Space #\Tab) line))) 68 (and (> (length trimmed) 0) 69 (not (char= (char trimmed 0) #\()) 70 (not (char= (char trimmed 0) #\;)) 71 ;; Has a space (i.e., has arguments) 72 (position #\Space trimmed)))) 73 74(defun preprocess (source) 75 "Pre-process KidLisp source: auto-wrap bare lines, handle commas." 76 (let ((lines (uiop:split-string source :separator '(#\Newline)))) 77 ;; Process each line 78 (let ((processed 79 (mapcar (lambda (line) 80 (let ((trimmed (string-trim '(#\Space #\Tab #\Return) line))) 81 (cond 82 ;; Empty or comment 83 ((or (= (length trimmed) 0) 84 (char= (char trimmed 0) #\;)) 85 "") 86 ;; Already wrapped in parens 87 ((char= (char trimmed 0) #\() 88 trimmed) 89 ;; Bare line: auto-wrap 90 ((bare-line-p trimmed) 91 (format nil "(~A)" trimmed)) 92 ;; Single word (color name on line 1, etc.) 93 (t trimmed)))) 94 lines))) 95 ;; Join all lines, wrap in implicit progn 96 (format nil "(progn ~{~A ~})" processed)))) 97 98(defun read-tokens (tokens) 99 "Read a list of tokens into a nested list AST." 100 (let ((pos 0)) 101 (labels ((read-expr () 102 (when (>= pos (length tokens)) 103 (return-from read-expr nil)) 104 (let ((tok (nth pos tokens))) 105 (cond 106 ((string= tok "(") 107 (incf pos) 108 (let ((items nil)) 109 (loop while (and (< pos (length tokens)) 110 (not (string= (nth pos tokens) ")"))) 111 do (push (read-expr) items)) 112 (when (and (< pos (length tokens)) 113 (string= (nth pos tokens) ")")) 114 (incf pos)) 115 (nreverse items))) 116 ((string= tok ")") 117 (incf pos) 118 nil) 119 (t 120 (incf pos) 121 ;; Try to parse as number 122 (or (parse-number tok) tok)))))) 123 (let ((results nil)) 124 (loop while (< pos (length tokens)) 125 do (push (read-expr) results)) 126 (if (= (length results) 1) 127 (first results) 128 (cons "progn" (nreverse results))))))) 129 130(defun kidlisp-parse (source) 131 "Parse KidLisp source string into an AST (nested lists)." 132 (let* ((preprocessed (preprocess source)) 133 (tokens (tokenize preprocessed))) 134 (read-tokens tokens)))