Control flow macros

This commit is contained in:
~keith 2024-06-13 18:50:15 -04:00
parent 4449774b73
commit 0981a1131a
Signed by: keith
GPG key ID: 5BEBEEAB2C73D520
4 changed files with 68 additions and 14 deletions

44
control-flow.lisp Normal file
View file

@ -0,0 +1,44 @@
(in-package #:syrup)
(defun translate-if* (test body)
(let ((then-forms
;; Collect all un-enclosed forms at the start of the body, and skip past them.
;; These forms are considered part of an implicit :THEN clause.
(loop :for tail :on body
:for form := (car tail)
:until (and (consp form) (find (car form) '(:then :else-if :else)))
:collect form
:finally (setf body tail)))
(else-if-forms nil)
(else-forms nil))
;; Handle :THEN, :ELSE, and :ELSE-IF clauses
(loop :for tail :on body
:for form := (car tail)
:do
(unless (consp form) (error "Not a valid IF* clause: ~S" form))
(case (car form)
(:then
(when then-forms (error "Duplicate :THEN clause"))
(setf then-forms (cdr form)))
(:else
(when (cdr tail) (error "Junk after :ELSE clause: ~S" (cdr tail)))
(setf else-forms (cdr form))
(loop-finish))
(:else-if ;; handle these recursively
(setf else-forms (list (translate-if* (cadr form) `((:then ,@(cddr form)) ,@(cdr tail)))))
(loop-finish))
(t (error "Not a valid IF* clause: ~S" form))))
;; Now build the if statement
(labels ((maybe-progn (x)
(if (cdr x) (cons 'progn x) (car x))))
(if else-forms
`(if ,test ,(maybe-progn then-forms) ,(maybe-progn else-forms))
`(when ,test ,@then-forms)))))
(defmacro if* (test &body body)
"An easier-to-read conditional statement."
(translate-if* test body))
(defmacro while (test &body body)
"Repeatedly evaluate BODY while TEST is true. Return the result of the last iteration, a la PROGN."
(let ((result-var (gensym)))
`(do (,result-var) ((not ,test) ,result-var) (setf ,result-var (progn ,@body)))))

View file

@ -1,7 +1,8 @@
(in-package #:syrup) (in-package #:syrup)
;; Support for inline and in-file symbol exports.
(defvar *capture-exports* nil (defvar *capture-exports* nil
"If non-nil, #^ doesn't export symbols. Either T, NIL, or the package to be monitored.") "If non-nil, #^ won't export symbols, and will instead add to *exports*. Either T, NIL, or a specific package to monitor.")
(defvar *exports* nil (defvar *exports* nil
"List of captured symbol exports.") "List of captured symbol exports.")
@ -50,7 +51,7 @@
"Read Lisp source code from STREAM to determine the list of symbols it exports." "Read Lisp source code from STREAM to determine the list of symbols it exports."
(when target-package (when target-package
(let ((pkg (find-package target-package))) (let ((pkg (find-package target-package)))
(assert pkg () "No such package ~S" target-package) (unless pkg (error "No such package: ~S" target-package))
(setf target-package pkg))) (setf target-package pkg)))
(let ((*package* *package*) (let ((*package* *package*)
(*readtable* *readtable*) (*readtable* *readtable*)
@ -60,15 +61,16 @@
(loop for form = (read stream nil eof-sentinel) (loop for form = (read stream nil eof-sentinel)
until (eq form eof-sentinel) until (eq form eof-sentinel)
do (when (consp form) do (when (consp form)
(if (eql (car form) 'in-package) (case (car form)
(let ((pkg (find-package (cadr form)))) (in-package
(assert pkg () "No such package ~S" (cadr form)) (let ((pkg (find-package (cadr form))))
(setf *package* pkg) (unless pkg (error "No such package: ~S" (cadr form)))
(if (eq t *capture-exports*) (setf *package* pkg)
(setf *capture-exports* pkg))) (when (eq t *capture-exports*)
(when (and (eql (car form) 'export*) (setf *capture-exports* pkg))))
(eq *package* *capture-exports*)) (export*
(setf *exports* (nconc (mapcar #'ensure-symbol (cdr form)) *exports*)))))) (when (eq *package* *capture-exports*)
(setf *exports* (nconc (mapcar #'ensure-symbol (cdr form)) *exports*)))))))
*exports*)) *exports*))
(defmacro defpackage* (package-name &rest options) (defmacro defpackage* (package-name &rest options)
@ -76,6 +78,8 @@
(let ((extra-exports nil) (let ((extra-exports nil)
(pkg (find-package package-name))) (pkg (find-package package-name)))
(when pkg (when pkg
;; We're redefining the package, so let's scan the files we know about for exported symbols.
;; This prevents them from being un-exported if the files are re-loaded out of order.
(dolist (pathname (gethash pkg *package-files-table*)) (dolist (pathname (gethash pkg *package-files-table*))
(let ((exports (with-open-file (stream pathname :if-does-not-exist nil) (let ((exports (with-open-file (stream pathname :if-does-not-exist nil)
(read-stream-for-exports stream pkg)))) (read-stream-for-exports stream pkg))))

View file

@ -1,9 +1,10 @@
(asdf:defsystem "syrup" (asdf:defsystem "syrup"
:description "Assorted syntactic sugar for Lisp." :description "Assorted syntactic sugar for Lisp."
:version "1.0" :version "1.1"
:author "~keith <keith@keithhacks.cyou>" :author "~keith <keith@keithhacks.cyou>"
:homepage "https://bytes.keithhacks.cyou/keith/syrup" :homepage "https://bytes.keithhacks.cyou/keith/syrup"
:license "Public Domain/CC0" :license "Public Domain/CC0"
:components ((:file "syrup") :components ((:file "syrup")
(:file "exports")) (:file "exports")
(:file "control-flow"))
:serial t) :serial t)

View file

@ -1,7 +1,12 @@
(defpackage #:syrup (defpackage #:syrup
(:use #:cl) (:use #:cl)
(:export (:export
#:*capture-exports* #:*exports* #:export* #:defpackage* #:mark-package-file)) #:simple-reader-error
;; exports.lisp
#:export* #:defpackage*
;; control-flow.lisp
#:if* #:while
))
(in-package #:syrup) (in-package #:syrup)
(define-condition simple-reader-error (simple-condition reader-error) ()) (define-condition simple-reader-error (simple-condition reader-error) ())