(defmacro for ((index start end) &body body) "Iterates body between start and end inclusive. Returns the last evaluated form in the body." (labels ((replace-symbol (from to list) ; I dunno how to dispose of this. (cond ((eql list from) to) ((atom list) list) ((null list) nil) (t (cons (replace-symbol from to (car list)) (replace-symbol from to (cdr list))))))) (alexandria:once-only (start end) ; Not gonna bother with being hygenic on my own. (alexandria:with-gensyms (counter^ out^ start-tag^) ; Names ending with ^ hold gensymed symbols. Rename at your own leisure. `(let ((,counter^ ,start) ,out^) (tagbody ,start-tag^ (setf ,out^ (progn ,@(replace-symbol index counter^ body))) (when (< ,counter^ ,end) (incf ,counter^) (go ,start-tag^))) ,out^))))) ;; Comments and other stuff stripped (defmacro for ((index start end) &body body) (labels ((replace-symbol (from to list) (cond ((eql list from) to) ((atom list) list) ((null list) nil) (t (cons (replace-symbol from to (car list)) (replace-symbol from to (cdr list))))))) (alexandria:once-only (start end) (alexandria:with-gensyms (counter^ out^ start-tag^) `(let ((,counter^ ,start) ,out^) (tagbody ,start-tag^ (setf ,out^ (progn ,@(replace-symbol index counter^ body))) (when (< ,counter^ ,end) (incf ,counter^) (go ,start-tag^))) ,out^))))) ;; I have no idea what in the bloody balls the replace-symbol ;; function is supposed to achieve. (defmacro for ((index start end) &body body) (alexandria:once-only (start end) (alexandria:with-gensyms (counter^ out^ start-tag^) `(let ((,counter^ ,start) ,out^) (tagbody ,start-tag^ (setf ,out^ (progn ,@body)) (when (< ,counter^ ,end) (incf ,counter^) (go ,start-tag^))) ,out^)))) ;; I don't like automatic once-only/gensyms. We don't need to ;; gensym start since it is only ever evaluated once. (defmacro for ((index start end) &body body) (let ((end-value (gensym "END")) (loop (gensym "LOOP"))) `(let ((,end-value ,end) (,index ,start)) (tagbody ,loop (setf ,out^ (progn ,@body)) (when (< ,index ,end-value) (incf ,index) (go ,loop))) ,out^))) ;; Finally we use a return to get the result instead of ;; always saving it in a variable. (defmacro for ((index start end) &body body) (let ((end-value (gensym "END")) (loop (gensym "LOOP")) (block (gensym "BLOCK"))) `(let ((,end-value ,end) (,index ,start)) (block ,block (tagbody ,loop (return-from ,block (prog1 (progn ,@body) (when (< ,index ,end-value) (incf ,index) (go ,loop))))))))) ;; But since I never said that the last value has to be ;; returned (dolist and similar do not either), we can ;; get rid of that too! (defmacro for ((index start end) &body body) (let ((end-value (gensym "END")) (loop (gensym "LOOP"))) `(let ((,end-value ,end) (,index ,start)) (tagbody ,loop (progn ,@body) (when (< ,index ,end-value) (incf ,index) (go ,loop)))))) ;; Now we can go ahead and extend the syntax a bit more ;; add some tests, etc. (defmacro for ((index start end &key (step 1) (test #'<)) &body body) (assert (symbolp index) () "Index must be a symbol!") (let ((end-value (gensym "END")) (step-value (gensym "STEP")) (test-value (gensym "TEST")) (loop (gensym "LOOP"))) `(let ((,end-value ,end) (,step-value ,step) (,test-value ,test) (,index ,start)) (tagbody ,loop (progn ,@body) (when (funcall ,test-value ,index ,end-value) (incf ,index ,step-value) (go ,loop)))))) ;; If we ignore the NIL block, we can do this all much more ;; easily as you pointed out before. (defmacro for ((index start end &key (step 1)) &body body) `(loop for ,index from ,start to ,end by ,step do (progn ,@body)))