Control flow macros
This commit is contained in:
parent
4449774b73
commit
0981a1131a
4 changed files with 68 additions and 14 deletions
44
control-flow.lisp
Normal file
44
control-flow.lisp
Normal 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)))))
|
26
exports.lisp
26
exports.lisp
|
@ -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))))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) ())
|
||||||
|
|
Loading…
Reference in a new issue