diff --git a/fedac/native/cl/ac-native.asd b/fedac/native/cl/ac-native.asd --- a/fedac/native/cl/ac-native.asd +++ b/fedac/native/cl/ac-native.asd @@ -34,6 +34,13 @@ (:file "config") ;; QuickJS bridge (JS piece runner) (:file "quickjs-ffi") (:file "js-bridge") + ;; KidLisp CL-native evaluator + (:file "kidlisp-package") + (:file "kidlisp-colors") + (:file "kidlisp-parser") + (:file "kidlisp-eval") + (:file "kidlisp-builtins") + (:file "kidlisp-runner") + ;; Main + Build (:file "main") - ;; Build (:file "build"))) diff --git a/fedac/native/cl/kidlisp-builtins.lisp b/fedac/native/cl/kidlisp-builtins.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-builtins.lisp @@ -0,0 +1,213 @@ +;;; kidlisp-builtins.lisp — Built-in function table for KidLisp +;;; Each builtin is (lambda (instance evaluated-args) ...) + +(in-package :ac-native.kidlisp) + +(defvar *builtins* (make-hash-table :test #'equal)) + +(defmacro defbuiltin (name params &body body) + `(setf (gethash ,name *builtins*) + (lambda ,params ,@body))) + +;;; ── Graphics ── + +(defbuiltin "wipe" (inst args) + (let ((rgb (resolve-color args (kidlisp-instance-frame-count inst)))) + (when (kidlisp-instance-graph inst) + (ac-native.graph:graph-wipe + (kidlisp-instance-graph inst) + (ac-native.color:make-color + :r (first rgb) :g (second rgb) :b (third rgb) :a 255))))) + +(defbuiltin "ink" (inst args) + (let ((rgb (resolve-color args (kidlisp-instance-frame-count inst)))) + (setf (kidlisp-instance-ink-r inst) (first rgb) + (kidlisp-instance-ink-g inst) (second rgb) + (kidlisp-instance-ink-b inst) (third rgb)) + (when (kidlisp-instance-graph inst) + (ac-native.graph:graph-ink + (kidlisp-instance-graph inst) + (ac-native.color:make-color + :r (first rgb) :g (second rgb) :b (third rgb) :a 255))))) + +(defbuiltin "line" (inst args) + (when (and (kidlisp-instance-graph inst) (>= (length args) 4)) + (ac-native.graph:graph-line + (kidlisp-instance-graph inst) + (round (or (nth 0 args) 0)) (round (or (nth 1 args) 0)) + (round (or (nth 2 args) 0)) (round (or (nth 3 args) 0))))) + +(defbuiltin "box" (inst args) + (when (and (kidlisp-instance-graph inst) (>= (length args) 4)) + (ac-native.graph:graph-box + (kidlisp-instance-graph inst) + (round (or (nth 0 args) 0)) (round (or (nth 1 args) 0)) + (round (or (nth 2 args) 0)) (round (or (nth 3 args) 0)) + 1))) + +(defbuiltin "circle" (inst args) + (when (and (kidlisp-instance-graph inst) (>= (length args) 3)) + (ac-native.graph:graph-circle + (kidlisp-instance-graph inst) + (round (or (nth 0 args) 0)) (round (or (nth 1 args) 0)) + (round (or (nth 2 args) 0))))) + +(defbuiltin "plot" (inst args) + (when (and (kidlisp-instance-graph inst) (>= (length args) 2)) + (ac-native.graph:graph-plot + (kidlisp-instance-graph inst) + (round (or (nth 0 args) 0)) (round (or (nth 1 args) 0))))) + +;;; ── Pixel transforms ── + +(defbuiltin "scroll" (inst args) + (when (kidlisp-instance-graph inst) + (let ((dx (round (or (nth 0 args) 0))) + (dy (round (or (nth 1 args) 0))) + (fb (ac-native.graph:graph-fb (kidlisp-instance-graph inst)))) + (when fb + (ac-native.framebuffer:fb-scroll fb dx dy))))) + +(defbuiltin "zoom" (inst args) + (when (kidlisp-instance-graph inst) + (let ((factor (or (nth 0 args) 1.0)) + (fb (ac-native.graph:graph-fb (kidlisp-instance-graph inst)))) + (when (and fb (numberp factor)) + (ac-native.framebuffer:fb-zoom fb factor))))) + +(defbuiltin "spin" (inst args) + (when (kidlisp-instance-graph inst) + (let ((angle (or (nth 0 args) 0.0)) + (fb (ac-native.graph:graph-fb (kidlisp-instance-graph inst)))) + (when (and fb (numberp angle)) + (ac-native.framebuffer:fb-spin fb angle))))) + +(defbuiltin "contrast" (inst args) + (when (kidlisp-instance-graph inst) + (let ((factor (or (nth 0 args) 1.0)) + (fb (ac-native.graph:graph-fb (kidlisp-instance-graph inst)))) + (when (and fb (numberp factor)) + (ac-native.framebuffer:fb-contrast fb factor))))) + +;;; ── Math ── + +(defbuiltin "+" (inst args) + (declare (ignore inst)) + (apply #'+ (remove-if-not #'numberp args))) + +(defbuiltin "-" (inst args) + (declare (ignore inst)) + (if (= (length args) 1) + (- (first args)) + (apply #'- (remove-if-not #'numberp args)))) + +(defbuiltin "*" (inst args) + (declare (ignore inst)) + (apply #'* (remove-if-not #'numberp args))) + +(defbuiltin "/" (inst args) + (declare (ignore inst)) + (if (and (>= (length args) 2) (not (zerop (second args)))) + (/ (float (first args)) (float (second args))) + 0)) + +(defbuiltin "%" (inst args) + (declare (ignore inst)) + (if (and (>= (length args) 2) (not (zerop (second args)))) + (mod (first args) (second args)) + 0)) +(setf (gethash "mod" *builtins*) (gethash "%" *builtins*)) + +(defbuiltin "sin" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (sin (float (first args))))) + +(defbuiltin "cos" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (cos (float (first args))))) + +(defbuiltin "abs" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (abs (first args)))) + +(defbuiltin "floor" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (floor (first args)))) + +(defbuiltin "ceil" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (ceiling (first args)))) + +(defbuiltin "round" (inst args) + (declare (ignore inst)) + (when (numberp (first args)) (round (first args)))) + +(defbuiltin "sqrt" (inst args) + (declare (ignore inst)) + (when (and (numberp (first args)) (>= (first args) 0)) + (sqrt (float (first args))))) + +(defbuiltin "min" (inst args) + (declare (ignore inst)) + (apply #'min (remove-if-not #'numberp args))) + +(defbuiltin "max" (inst args) + (declare (ignore inst)) + (apply #'max (remove-if-not #'numberp args))) + +(defbuiltin "pow" (inst args) + (declare (ignore inst)) + (when (and (numberp (first args)) (numberp (second args))) + (expt (float (first args)) (float (second args))))) + +(defbuiltin "random" (inst args) + (let ((n (or (first args) 1))) + (when (numberp n) + (if (integerp n) + (random n (kidlisp-instance-random-state inst)) + (* n (random 1.0 (kidlisp-instance-random-state inst))))))) + +(defbuiltin "wiggle" (inst args) + (let ((n (or (first args) 1))) + (when (numberp n) + (random n (kidlisp-instance-random-state inst))))) + +;;; ── System ── + +(defbuiltin "width" (inst args) + (declare (ignore args)) + (kidlisp-instance-screen-w inst)) + +(defbuiltin "height" (inst args) + (declare (ignore args)) + (kidlisp-instance-screen-h inst)) + +(defbuiltin "frame" (inst args) + (declare (ignore args)) + (kidlisp-instance-frame-count inst)) + +;;; ── Audio (stubs for now) ── + +(defbuiltin "tone" (inst args) + (declare (ignore inst args)) + nil) + +(defbuiltin "overtone" (inst args) + (declare (ignore inst args)) + nil) + +(defbuiltin "melody" (inst args) + (declare (ignore inst args)) + nil) + +;;; ── Output ── + +(defbuiltin "log" (inst args) + (declare (ignore inst)) + (format *error-output* "[kidlisp] ~{~A ~}~%" args) + (force-output *error-output*)) + +(defbuiltin "print" (inst args) + (declare (ignore inst)) + (format *error-output* "[kidlisp] ~{~A ~}~%" args) + (force-output *error-output*)) diff --git a/fedac/native/cl/kidlisp-colors.lisp b/fedac/native/cl/kidlisp-colors.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-colors.lisp @@ -0,0 +1,85 @@ +;;; kidlisp-colors.lisp — Color table + rainbow/zebra for KidLisp + +(in-package :ac-native.kidlisp) + +(defvar *colors* (make-hash-table :test #'equal)) + +(macrolet ((defcolors (&rest pairs) + `(progn + ,@(loop for (name r g b) on pairs by #'cddddr + collect `(setf (gethash ,name *colors*) (list ,r ,g ,b)))))) + (defcolors + "red" 255 0 0 "green" 0 128 0 "blue" 0 0 255 + "white" 255 255 255 "black" 0 0 0 + "cyan" 0 255 255 "magenta" 255 0 255 "yellow" 255 255 0 + "orange" 255 165 0 "pink" 255 192 203 "purple" 128 0 128 + "lime" 0 255 0 "aqua" 0 255 255 "navy" 0 0 128 + "maroon" 128 0 0 "olive" 128 128 0 "teal" 0 128 128 + "silver" 192 192 192 "gray" 128 128 128 "grey" 128 128 128 + "coral" 255 127 80 "salmon" 250 128 114 + "gold" 255 215 0 "indigo" 75 0 130 "violet" 238 130 238 + "crimson" 220 20 60 "turquoise" 64 224 208)) + +(defun hsv-to-rgb (h s v) + "Convert HSV (h=0-360, s=0-1, v=0-1) to (r g b) in 0-255." + (let* ((c (* v s)) + (x (* c (- 1.0 (abs (- (mod (/ h 60.0) 2.0) 1.0))))) + (m (- v c)) + (r1 0.0) (g1 0.0) (b1 0.0)) + (cond ((< h 60) (setf r1 c g1 x)) + ((< h 120) (setf r1 x g1 c)) + ((< h 180) (setf g1 c b1 x)) + ((< h 240) (setf g1 x b1 c)) + ((< h 300) (setf r1 x b1 c)) + (t (setf r1 c b1 x))) + (list (round (* (+ r1 m) 255)) + (round (* (+ g1 m) 255)) + (round (* (+ b1 m) 255))))) + +(defun rainbow-color (frame &optional (speed 1.0)) + "Generate a cycling rainbow color based on frame count." + (hsv-to-rgb (mod (* frame speed 2.0) 360.0) 1.0 1.0)) + +(defun zebra-color (frame) + "Alternating black/white." + (if (evenp frame) '(255 255 255) '(0 0 0))) + +(defun color-name-p (name) + "Is NAME a known color name, rainbow, or zebra?" + (or (gethash name *colors*) + (string= name "rainbow") + (string= name "zebra"))) + +(defun resolve-color (args frame) + "Resolve KidLisp color arguments to (r g b). +ARGS can be: a color name string, (r g b) numbers, 'rainbow', 'zebra', +or a single grayscale number." + (cond + ((null args) '(255 255 255)) + ;; Single string: color name + ((and (= (length args) 1) (stringp (first args))) + (let ((name (first args))) + (cond ((string= name "rainbow") (rainbow-color frame)) + ((string= name "zebra") (zebra-color frame)) + ((gethash name *colors*) (gethash name *colors*)) + (t '(255 255 255))))) + ;; Single number: grayscale + ((and (= (length args) 1) (numberp (first args))) + (let ((v (max 0 (min 255 (round (first args)))))) + (list v v v))) + ;; Three numbers: r g b + ((and (>= (length args) 3) + (numberp (first args)) + (numberp (second args)) + (numberp (third args))) + (list (max 0 (min 255 (round (first args)))) + (max 0 (min 255 (round (second args)))) + (max 0 (min 255 (round (third args)))))) + ;; Four numbers: r g b a (ignore alpha for now) + ((and (>= (length args) 4) + (numberp (first args))) + (list (max 0 (min 255 (round (first args)))) + (max 0 (min 255 (round (second args)))) + (max 0 (min 255 (round (third args)))))) + ;; Fallback + (t '(255 255 255)))) diff --git a/fedac/native/cl/kidlisp-eval.lisp b/fedac/native/cl/kidlisp-eval.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-eval.lisp @@ -0,0 +1,227 @@ +;;; kidlisp-eval.lisp — Tree-walking interpreter for KidLisp + +(in-package :ac-native.kidlisp) + +(defstruct kidlisp-instance + (ast nil) + (global-def (make-hash-table :test #'equal)) + (frame-count 0 :type fixnum) + (once-executed (make-hash-table :test #'equal)) + (timing-last (make-hash-table :test #'equal)) + (timing-seq (make-hash-table :test #'equal)) + (random-state (make-random-state t)) + ;; Graphics state (set each frame) + graph + screen-w + screen-h + audio + (ink-r 255) (ink-g 255) (ink-b 255)) + +(defun kidlisp-frame (inst) + "Increment frame counter and evaluate the AST for one frame." + (incf (kidlisp-instance-frame-count inst)) + (kidlisp-eval inst (kidlisp-instance-ast inst) nil)) + +;;; ── Variable resolution ── + +(defun lookup-var (inst name env) + "Resolve a variable: env -> global-def -> magic vars -> color." + (or (cdr (assoc name env :test #'equal)) + (gethash name (kidlisp-instance-global-def inst)) + (magic-var inst name) + (when (color-name-p name) name))) + +(defun magic-var (inst name) + "Resolve magic variables: width, height, w, h, w/2, h/2, frame." + (let ((sw (kidlisp-instance-screen-w inst)) + (sh (kidlisp-instance-screen-h inst))) + (cond + ((or (string= name "width") (string= name "w")) sw) + ((or (string= name "height") (string= name "h")) sh) + ((string= name "w/2") (floor sw 2)) + ((string= name "h/2") (floor sh 2)) + ((string= name "frame") (kidlisp-instance-frame-count inst)) + (t nil)))) + +;;; ── Timing ── + +(defun parse-timing (head) + "Parse a timing token. Returns (seconds iterating-p bang-p) or NIL." + (when (timing-token-p head) + (let* ((has-dots (search "..." head)) + (has-bang (and (not has-dots) (find #\! head))) + (num-str (string-right-trim ".!s" head))) + (let ((secs (or (parse-number num-str) 1.0))) + (values secs (not (null has-dots)) (not (null has-bang))))))) + +(defun handle-timing (inst secs iterating-p args env) + "Handle timed execution. Returns the evaluated body at the right interval." + (let* ((key (format nil "~A:~A" secs args)) + (now (/ (get-internal-real-time) (float internal-time-units-per-second))) + (last (gethash key (kidlisp-instance-timing-last inst) 0.0))) + (when (>= (- now last) secs) + (setf (gethash key (kidlisp-instance-timing-last inst)) now) + (if iterating-p + ;; Iterating: cycle through args + (let* ((idx (gethash key (kidlisp-instance-timing-seq inst) 0)) + (val (nth (mod idx (length args)) args))) + (setf (gethash key (kidlisp-instance-timing-seq inst)) (1+ idx)) + (kidlisp-eval inst val env)) + ;; Single execution + (let ((result nil)) + (dolist (a args result) + (setf result (kidlisp-eval inst a env)))))))) + +;;; ── Core evaluator ── + +(defun kidlisp-eval (inst expr env) + "Evaluate a KidLisp expression." + (cond + ;; NIL + ((null expr) nil) + ;; Number + ((numberp expr) expr) + ;; String atom (variable or color name) + ((stringp expr) + (or (lookup-var inst expr env) expr)) + ;; List (function call or special form) + ((listp expr) + (let ((head (first expr)) + (args (rest expr))) + (cond + ;; Empty list + ((null head) nil) + ;; Head is a list (nested expression) — evaluate head first + ((listp head) + (let ((result (kidlisp-eval inst head env))) + (declare (ignore result)) + ;; Evaluate remaining as sequence + (let ((last nil)) + (dolist (a args last) + (setf last (kidlisp-eval inst a env)))))) + ;; Timing token + ((timing-token-p head) + (multiple-value-bind (secs iterating-p) (parse-timing head) + (handle-timing inst secs iterating-p args env))) + ;; Integer as timing shorthand (e.g., (2 body...) = every 2 frames) + ((and (numberp head) (integerp head) args) + (when (zerop (mod (kidlisp-instance-frame-count inst) head)) + (let ((result nil)) + (dolist (a args result) + (setf result (kidlisp-eval inst a env)))))) + ;; String head — function call or special form + ((stringp head) + (eval-call inst head args env)) + ;; Fallback + (t nil)))) + ;; Fallback + (t expr))) + +(defun eval-call (inst head args env) + "Evaluate a named function call or special form." + (cond + ;; ── Special forms (don't pre-evaluate args) ── + ((string= head "progn") + (let ((result nil)) + (dolist (a args result) + (setf result (kidlisp-eval inst a env))))) + + ((string= head "def") + (when (>= (length args) 2) + (let ((name (first args)) + (val (kidlisp-eval inst (second args) env))) + (when (stringp name) + (setf (gethash name (kidlisp-instance-global-def inst)) val))))) + + ((string= head "later") + ;; (later name (params...) body...) + (when (>= (length args) 2) + (let ((name (first args)) + (params (if (listp (second args)) (second args) nil)) + (body (if (listp (second args)) (cddr args) (cdr args)))) + (when (stringp name) + (setf (gethash name (kidlisp-instance-global-def inst)) + (list :later params body)))))) + + ((string= head "if") + (let ((cond-val (kidlisp-eval inst (first args) env))) + (if (and cond-val (not (eql cond-val 0))) + (kidlisp-eval inst (second args) env) + (when (third args) + (kidlisp-eval inst (third args) env))))) + + ((string= head "not") + (let ((val (kidlisp-eval inst (first args) env))) + (if (and val (not (eql val 0))) 0 1))) + + ((string= head "once") + (let ((key (format nil "once:~A" args))) + (unless (gethash key (kidlisp-instance-once-executed inst)) + (setf (gethash key (kidlisp-instance-once-executed inst)) t) + (let ((result nil)) + (dolist (a args result) + (setf result (kidlisp-eval inst a env))))))) + + ((or (string= head "?") (string= head "choose")) + ;; Random choice from args + (when args + (let ((idx (random (length args) (kidlisp-instance-random-state inst)))) + (kidlisp-eval inst (nth idx args) env)))) + + ((string= head "repeat") + ;; (repeat N [i] body...) + (let* ((n (kidlisp-eval inst (first args) env)) + (count (if (numberp n) (round n) 0)) + (has-iter (and (>= (length args) 3) (stringp (second args)))) + (iter-name (when has-iter (second args))) + (body (if has-iter (cddr args) (cdr args)))) + (let ((result nil)) + (dotimes (i count result) + (let ((new-env (if iter-name + (acons iter-name i env) + env))) + (dolist (b body) + (setf result (kidlisp-eval inst b new-env)))))))) + + ;; ── Comparison operators ── + ((string= head ">") + (let ((a (kidlisp-eval inst (first args) env)) + (b (kidlisp-eval inst (second args) env))) + (if (and (numberp a) (numberp b) (> a b)) 1 0))) + ((string= head "<") + (let ((a (kidlisp-eval inst (first args) env)) + (b (kidlisp-eval inst (second args) env))) + (if (and (numberp a) (numberp b) (< a b)) 1 0))) + ((string= head "=") + (let ((a (kidlisp-eval inst (first args) env)) + (b (kidlisp-eval inst (second args) env))) + (if (equal a b) 1 0))) + + ;; ── Built-in functions (evaluate args first) ── + (t + (let ((evaled (mapcar (lambda (a) (kidlisp-eval inst a env)) args))) + (call-builtin inst head evaled))))) + +(defun call-builtin (inst name args) + "Call a built-in KidLisp function with evaluated args." + (let ((fn (gethash name *builtins*))) + (if fn + (funcall fn inst args) + ;; Check user-defined (later) functions + (let ((def (gethash name (kidlisp-instance-global-def inst)))) + (cond + ((and (listp def) (eq (first def) :later)) + (let* ((params (second def)) + (body (third def)) + (env (mapcar #'cons + (mapcar (lambda (p) (if (stringp p) p (format nil "~A" p))) + params) + args))) + (let ((result nil)) + (dolist (b body result) + (setf result (kidlisp-eval inst b env)))))) + ;; If it resolves to a color, return it + ((color-name-p name) + (resolve-color (list name) (kidlisp-instance-frame-count inst))) + ;; Unknown — return nil silently + (t nil)))))) diff --git a/fedac/native/cl/kidlisp-package.lisp b/fedac/native/cl/kidlisp-package.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-package.lisp @@ -0,0 +1,10 @@ +;;; kidlisp-package.lisp — Package definition for CL-native KidLisp evaluator + +(defpackage :ac-native.kidlisp + (:use :cl) + (:export #:make-kidlisp-instance + #:kidlisp-parse + #:kidlisp-evaluate + #:kidlisp-frame + #:run-kidlisp-piece + #:resolve-color)) diff --git a/fedac/native/cl/kidlisp-parser.lisp b/fedac/native/cl/kidlisp-parser.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-parser.lisp @@ -0,0 +1,134 @@ +;;; kidlisp-parser.lisp — Tokenizer + reader for KidLisp dialect +;;; Handles auto-wrapping bare lines, comma separation, timing syntax. + +(in-package :ac-native.kidlisp) + +(defun timing-token-p (s) + "Is S a timing token like '1s', '2s...', '0.5s!'?" + (and (stringp s) + (> (length s) 1) + (let ((base (string-right-trim ".!" s))) + (and (> (length base) 0) + (char= (char base (1- (length base))) #\s) + (every (lambda (c) (or (digit-char-p c) (char= c #\.))) + (subseq base 0 (1- (length base)))))))) + +(defun parse-number (s) + "Try to parse S as a number. Returns number or NIL." + (handler-case + (let ((n (read-from-string s))) + (when (numberp n) n)) + (error () nil))) + +(defun tokenize (source) + "Split KidLisp source into tokens (strings)." + (let ((tokens nil) + (i 0) + (len (length source))) + (flet ((peek () (when (< i len) (char source i))) + (advance () (prog1 (char source i) (incf i)))) + (loop while (< i len) do + (let ((c (peek))) + (cond + ;; Whitespace + ((member c '(#\Space #\Tab #\Newline #\Return)) + (advance)) + ;; Comment + ((char= c #\;) + (loop while (and (< i len) (not (char= (peek) #\Newline))) + do (advance))) + ;; Parens and comma + ((char= c #\() (push "(" tokens) (advance)) + ((char= c #\)) (push ")" tokens) (advance)) + ((char= c #\,) (advance)) ; skip commas as separators + ;; Quoted string + ((or (char= c #\") (char= c #\')) + (let ((quote c) + (start i)) + (advance) ; skip opening quote + (loop while (and (< i len) (not (char= (peek) quote))) + do (when (char= (peek) #\\) (advance)) ; skip escape + (advance)) + (when (< i len) (advance)) ; skip closing quote + (push (subseq source (1+ start) (1- i)) tokens))) + ;; Atom (symbol, number, color name, timing token) + (t + (let ((start i)) + (loop while (and (< i len) + (not (member (peek) '(#\Space #\Tab #\Newline #\Return + #\( #\) #\, #\;)))) + do (advance)) + (push (subseq source start i) tokens))))))) + (nreverse tokens))) + +(defun bare-line-p (line) + "Is LINE a bare expression that needs auto-wrapping in parens? +Bare lines start with a word (not a paren) and have arguments." + (let ((trimmed (string-trim '(#\Space #\Tab) line))) + (and (> (length trimmed) 0) + (not (char= (char trimmed 0) #\()) + (not (char= (char trimmed 0) #\;)) + ;; Has a space (i.e., has arguments) + (position #\Space trimmed)))) + +(defun preprocess (source) + "Pre-process KidLisp source: auto-wrap bare lines, handle commas." + (let ((lines (uiop:split-string source :separator '(#\Newline)))) + ;; Process each line + (let ((processed + (mapcar (lambda (line) + (let ((trimmed (string-trim '(#\Space #\Tab #\Return) line))) + (cond + ;; Empty or comment + ((or (= (length trimmed) 0) + (char= (char trimmed 0) #\;)) + "") + ;; Already wrapped in parens + ((char= (char trimmed 0) #\() + trimmed) + ;; Bare line: auto-wrap + ((bare-line-p trimmed) + (format nil "(~A)" trimmed)) + ;; Single word (color name on line 1, etc.) + (t trimmed)))) + lines))) + ;; Join all lines, wrap in implicit progn + (format nil "(progn ~{~A ~})" processed)))) + +(defun read-tokens (tokens) + "Read a list of tokens into a nested list AST." + (let ((pos 0)) + (labels ((read-expr () + (when (>= pos (length tokens)) + (return-from read-expr nil)) + (let ((tok (nth pos tokens))) + (cond + ((string= tok "(") + (incf pos) + (let ((items nil)) + (loop while (and (< pos (length tokens)) + (not (string= (nth pos tokens) ")"))) + do (push (read-expr) items)) + (when (and (< pos (length tokens)) + (string= (nth pos tokens) ")")) + (incf pos)) + (nreverse items))) + ((string= tok ")") + (incf pos) + nil) + (t + (incf pos) + ;; Try to parse as number + (or (parse-number tok) tok)))))) + (let ((results nil)) + (loop while (< pos (length tokens)) + do (push (read-expr) results)) + (if (= (length results) 1) + (first results) + (cons "progn" (nreverse results))))))) + +(defun kidlisp-parse (source) + "Parse KidLisp source string into an AST (nested lists)." + (let* ((preprocessed (preprocess source)) + (tokens (tokenize preprocessed))) + (read-tokens tokens))) diff --git a/fedac/native/cl/kidlisp-runner.lisp b/fedac/native/cl/kidlisp-runner.lisp new file mode 100644 --- /dev/null +++ b/fedac/native/cl/kidlisp-runner.lisp @@ -0,0 +1,87 @@ +;;; kidlisp-runner.lisp — Run KidLisp pieces in the CL main loop + +(in-package :ac-native.kidlisp) + +(defun run-kidlisp-piece (source &key (label "$code")) + "Run a KidLisp piece with full DRM graphics, audio, and input. +SOURCE is the KidLisp source code string." + (format *error-output* "~%════════════════════════════════════~%") + (format *error-output* " KidLisp (~A)~%" label) + (format *error-output* " SBCL ~A~%" (lisp-implementation-version)) + (format *error-output* "════════════════════════════════════~%~%") + (force-output *error-output*) + + ;; Parse the source + (let ((ast (handler-case (kidlisp-parse source) + (error (e) + (format *error-output* "[kidlisp] Parse error: ~A~%" e) + (force-output *error-output*) + (return-from run-kidlisp-piece))))) + (format *error-output* "[kidlisp] Parsed ~A: ~A top-level forms~%" + label (if (and (listp ast) (string= (first ast) "progn")) + (1- (length ast)) + 1)) + (force-output *error-output*) + + ;; Initialize display + (let ((display (ac-native.drm:drm-init))) + (unless display + (format *error-output* "[kidlisp] DRM init failed~%") + (return-from run-kidlisp-piece)) + + (let* ((dw (ac-native.drm:display-width display)) + (dh (ac-native.drm:display-height display)) + (scale (cond ((>= (min dw dh) 1440) 6) + ((>= (min dw dh) 1080) 4) + ((>= (min dw dh) 720) 3) + (t 2))) + (sw (floor dw scale)) + (sh (floor dh scale)) + (screen (ac-native.framebuffer:fb-create sw sh)) + (graph (ac-native.graph:graph-create screen)) + (input (ac-native.input:input-init dw dh scale)) + (audio (ac-native.audio:audio-init))) + + (format *error-output* "[kidlisp] ~Dx~D scale:~D -> ~Dx~D~%" dw dh scale sw sh) + (force-output *error-output*) + + ;; Create KidLisp instance + (let ((inst (make-kidlisp-instance + :ast ast + :graph graph + :screen-w sw + :screen-h sh + :audio audio))) + + ;; Main loop + (unwind-protect + (let ((running t)) + (loop while running do + ;; Input + (ac-native.input:input-poll input) + (dotimes (i (ac-native.input:input-event-count input)) + (let ((ev (ac-native.input:input-event input i))) + (when (and (= (ac-native.input:event-type ev) 1) ; keyboard down + (= (ac-native.input:event-code ev) 1)) ; ESC + (setf running nil)))) + + ;; Evaluate one frame + (handler-case + (kidlisp-frame inst) + (error (e) + (format *error-output* "[kidlisp] Runtime error: ~A~%" e) + (force-output *error-output*))) + + ;; Present + (ac-native.drm:drm-present display screen scale) + + ;; 60fps sync + (sleep 1/60))) + + ;; Cleanup + (when audio (ac-native.audio:audio-destroy audio)) + (ac-native.input:input-destroy input) + (ac-native.framebuffer:fb-destroy screen) + (ac-native.drm:drm-destroy display) + (format *error-output* "[kidlisp] Exited~%") + (force-output *error-output*)))))))