OVERVIEW This library introduces 7 macros , 1 symbol macro and 1 function. Full details can be found in the man page , this file is intended to give a taster so will not cover the full list or precisely define the syntax , etc. It's perfectly ok to skip the taster and go straight to the man page {see the INSTALLATION section below}. The central idea is to be able to create loops with additional functionality compared to what the standard DO and DO* offer. A loop establishing macro {LEM} is one of edo , edo* , edo-for , forever , all of which are defined in the library. An edo macro invocation with all trimmings might look something like this : (edo (loop-name :UBC) (:user-info "123" 512 'gbgbg) ((i 1 (+ i 1)) (:inh j 2 (+ j 4)) (:inh k :no-init (* k k)) var1 var2 var3 ((car some-cons) 'value (cons 'value (car some-cons))) ((car other-cons) (cons 'sumfing :@) (cons 'sumfing-else :@))) ((eql i 11) (pprint "Exiting") 'goodbye) (declare (integer i)) (declare (integer j)) go-tag (foo i) another-tag (bar j) (when (foobar) (go go-tag))) loop-name is the name of the loop and can be used with RETURN-FROM as well as the loop designator argument of macros edo-exit , edo-cont , edo-break or function edo-user-info of this library. :UBC is the sequence of events designator {SOED} where U stands for update , B stands for body and C stands for condition. This means that in this example , after the initial bindings or assignments , which always happen first , the sequence of events which will get repeated again and again {until the terminating condition becomes true} will be , , . Any of the 6 possible permutations of U , B , C is allowed or :D which stands for default and is the same as :CBU .This is the default because it's the same behavior as DO or DO* . The :user-info form allows the programmer to pass arbitrary Lisp objects to "edoloop-enclosed" forms. A form is edoloop-enclosed by a LEM if it appears in the lexical scope of the macro and at a point where the syntax of the macro no longer allows for the macro itself to create any new lexical bindings. The man page lists precisely which points in the lexical scope of each of the 4 LEMs of the library are to be considered edoloop-enclosed ; the body , terminating condition , result forms and update forms are always included. What follows is similar to the standard DO with some additional twists : (i 1 (+ i 1)) introduces a new lexical binding for a variable named i ; initial value is 1 and it gets increased by 1 in each repetition. This works exactly as with DO . (:inh j 2 (+ j 4)) : the :inh at the beginning stands for "inherit" and means that no new binding will be created but a binding for j must exist in the environment. Initially j will be assigned the value 2 and will be increased by 4 in each repetition. (:inh k :no-init (* k k)) : this is similar to the previous but :no-init means that no new value will be assigned to k before the repeating part of the loop ; :no-init has this special meaning only for inherited bindings. var1 var2 var3 introduces 3 bindings which will be implicitly assigned the value NIL and there are no update forms. ((car some-cons) 'value (cons 'value (car some-cons))) : this shows that the macro works not just with symbols but with arbitrary places i.e. SETF-able forms. Since it is not defined in the standard what it means to create a new binding for a compound form , these are always assumed to be inherited from the environment. So in this case , there must be in the environment a binding for some-cons which has a value which can be used as argument to CAR .Initially it will be as if the form (setf (car some-cons) 'value) gets evaluated and in each repetition it will be as if the form (setf (car some-cons) (cons 'value (car some-cons))) gets evaluated. ((car other-cons) (cons 'sumfing :@) (cons 'sumfing-else :@))) is the same as writing ((car other-cons) (cons 'sumfing (car other-cons)) (cons 'sumfing-else (car other-cons))) i.e. every appearance of the symbol :@ in the initialisation and update forms gets replaced by the SETF-able form. What follows further below has the same syntax and semantics as DO : ((eql i 11) (pprint "Exiting") 'goodbye) is the terminating condition and result forms. Then follow some declarations and then the body which is an implicit tagbody . The loop can be terminated prematurely by using (RETURN-FROM loop-name value) or (edo-exit value loop-name) or (edo-break loop-name) . RETURN-FROM may appear anywhere after the (:user-info ...) form ; edo-exit and edo-break may appear at any edoloop-enclosed point which for edo means anywhere after the (:user-info ...) form with the exception of the initialisation forms of the bindings or assignments. The difference between edo-exit and edo-break is that the former immediately terminates the loop and returns the value as if by calling RETURN-FROM whereas the latter transfers control as if by GO to the result forms and the return value of the loop will be what those forms return or NIL if no such forms exist. edo* works the same as edo but with edo* the initial bindings/assignments happen as if with LET* and the form updates happen as if with SETF whereas with edo the same happen as if with LET and PSETF respectively. So the difference is the same as the difference between DO* and DO . An edo-for invocation with all the garnishes might look something like this : (edo-for (name :BCU) (:user-info ...) (:let i (j 1) k (declare (symbol i)) (declare (integer j))) ((pprint "Init form") (setq k 12)) ((j < 12) (pprint "Exiting") 'goodbye) ((pprint "Update") (incf j) (setq i 'bbb)) go-tag (foo i) another-tag (bar j) (when (foobar) (go go-tag))) (name :BCU) and (:user-info ...) work the same as with edo . The (:let ...) form introduces new bindings and also has declarations ; it works the same as writing (let (i (j 1) k) (declare (symbol i)) (declare (integer j))) and everything that follows is within the lexical scope of this LET .Note that (:let ...) does not place parentheses around the list of variables ; the list of variables terminates with the first compound form whose car is the standard symbol DECLARE {comparison is done with EQ} or with the end of the (:let ) form itself if no declarations exist. It would have been possible to write (:let* ...) instead of (:let ...) with the obvious meaning. ((pprint "Init form") (setq k 12)) are the initialisation forms ; they will be evaluated left to right as an implicit progn .This will happen once , right after the bindings have been established and right before the repeating part of the loop starts. ((j < 12) (pprint "Exiting") 'goodbye) is the terminating condition followed by the result forms. edo-for is inspired by the for of C and some key choices work in a similar manner. So the loop gets repeated for as long as the terminating condition remains true which is the opposite of edo and edo* which get repeated until the terminating condition becomes true. The result forms will be evaluated as an implicit progn once the terminating condition becomes false or an (edo-break designator) gets evaluated where designator chooses this loop ; the return value of the loop will be what those forms return. If no result forms exist then the return value will be NIL . ((pprint "Update") (incf j) (setq i 'bbb)) are the update forms which will be evaluated as , you guessed it , an implicit progn . Then follows the body which is an implicity tagbody. Since for this example the SOED has the value :BCU , once we enter the repeat- ing part of the loop {i.e. after the initial bindings or assignments and after the initialisation forms have been evaluated} the forms in the body will be evaluated first , then (j < 12) , and if true then the update forms and this sequence of events will be repeated until (j < 12) evaluates to NIL . As with edo , the loop can be terminated prematurely with a desired return value using RETURN-FROM or edo-exit ; edo-break will transfer control to the result forms. RETURN-FROM may appear anywhere after the (:user-info ...) form {obviously it will have to be in a form which will be evaluated} ; edo-exit and edo-break anywhere after the (:let ...) form. Macros edo-exit , edo-cont , edo-break work in conjunction with a LEM and their invocation must appear in a position which is edoloop-enclosed by one or more LEMs ; the designator argument chooses a LEM as described below. The designator argument must be a symbol or an integer , is optional and defaults to 0. If it is a symbol it must be the name of an edoloop-enclosing LEM. If there are more than one with the same name the innermost will be chosen ; if none exist an error will be signalled. If the loop designator argument is an integer then 0 denotes the innermost edoloop-enclosing LEM , 1 the one after that , etc. ; if there aren't enough LEMs an error will be signalled. The functionality offered by edo-exit and edo-break has been described above. (edo-cont event designator) transfers control as if by GO to the event of the chosen LEM. As usual , "event" means one of , , . For which values the event argument may have and which actual event gets chosen depending on the argument see the man page. The edo-user-info function accepts amongst its arguments a lexical environment object. The only standard way to obtain such an object is through the use of an &environment parameter in a macro definition as in (defmacro mymacro (.... &environment env) ... ) .By passing to edo-user-info environment objects obtained in such a manner , the function has access to the content of (:user-info ) forms of all LEMs which are edoloop-enclosing the point where the mymacro invocation appears. The man page has examples. Whether edo-user-info would interact harmoniously with lexical environment objects obtained in other , implementation specific , ways I have no idea. But I don't think a Lisp object should be called a "lexical environment object" unless it is somehow associated with a point in the code and the concept of edoloop-enclosed {by some LEM} is meaningful for any point in the code regardless of whether such a point is associated with a macro invocation or not. HISTORY The idea of being able to continue or break out of lexically enclosing loops has of course been around for a long time ; for example Java , Perl and BASH support it. C only allows the programmer to break or continue the innermost loop. This has been an annoyance for me and not just for me , if you search comp.lang.c or comp.std.c for things like "labe(l)led loop(s)" or "labe(l)led break" you'll find several threads discussing the issue [2]. The C99 rationale [1] also mentions in paragraph 6.8.6.2 that adding such a functionality was considered but was not adopted due to insufficient prior art. So , starting several years ago , this lack of functionality in C was bugging me and I was thinking how nice it would be if it could be added. But there's no straightforward way to add it in C ; C macros in particular certainly won't suffice. You would need to write your own full-fledged preprocessor or modify an existing compiler and even then you would always face the dilemma of either writing non standard code which uses the enhanced functionality and has to be translated by the non standard tools or resort to using goto ; the latter is not a horrible option , I'm certainly not an anti-goto fanatic , but not ideal. And then at some point I learned of Common Lisp {CL}. I don't remember where I first heard it from but what sold me to it was Paul Graham's writings and in particular how powerful {he said} CL macros are [4]. I was thinking that if they were as powerful as Graham made them out to be then it would be possible to use them to add the functionality to CL if it didn't have it already. So the idea for this library existed in my head at a time when I hardly knew anything about CL apart from Graham's descriptions and I hadn't written a single line of CL code. When I did get around to start learning CL I saw that it didn't have the func- tionality I wanted , in fact it didn't even have a continue equivalent even for the innermost loop. So there was use for the library [3]. It took me a while until I got around to writing it but it's finally here. The long delay is perhaps for the best since with more CL knowledge I was able to write code which is more elegant than what I would have managed in the past. Also , some of the functionality , like the user-info facility , came to me quite a while after the idea of the library itself. IMPLEMENTATION This section exists for educational purposes and for giving credit where it's due ; you can skip it if you just want to use the library. The implementation is straightforward apart from one issue , "communication" of the macros {and function} of the library among themselves. In order to implement its central looping functionality each LEM has to create some GO tags which have to be uninterned symbols to avoid accidental capture. On the other hand the macros which transfer control {edo-break , edo-exit , edo-cont} have to know what these tags are in order to transfer control. Additionally , each LEM has to make available to edo-user-info the content of the LEM's :user-info subform. So how do the macros pass around this information among themselves ? I didn't know how to solve this and had to ask on comp.lang.lisp .I started the thread "Remembering information during compilation" [5]. The key idea was provided by Pascal Costanza in [6]. Further down Tobias C. Rittweiler shows an improvement [7]. With hindsight {and more CL experience} the solution is obvious. A CL macro is implemented through a function which accepts as "input" a piece of code and returns another piece of code ; *after* it has finished executing then the translator will examine the piece of code it returned for any other macros. Consequently there can be only 2 ways a macro can pass information to other macros {including other invocations of itself} : {A} By modifying a special variable. {B} By including the information in its expansion. And since for the semantics of this library information has to be made available to macros which are lexically enclosed then the only option which is going to work is {B}. Once one realises the central idea and the fact that option {B} can be implemented by use of SYMBOL-MACROLET the rest is straightforward although obviously it requires a certain amount of work to implement all the functionality this library offers. I also started 2 further threads to explore the issues further : "Remembering information during compilation , part 2" 2009-11-30 groups.google.co.uk/group/comp.lang.lisp/browse_thread/thread/fc79b1fac0cb94f2 and "Predicate for lexical bindings" 2009-11-19 groups.google.co.uk/group/comp.lang.lisp/browse_thread/thread/c07190cbba17535b .Unless you already are a CL macros guru I recommend going through all 3 threads , you're probably going to learn something new. DOWNLOAD , COMPILATION , INSTALLATION You can get it from vlaho.ninja/prog/enhanced-do/p.tar.gz . After you expand the tar file you start your favorite Common Lisp and do (load "tests") You'll get a huge amount of output but as long as it doesn't throw you in the debugger then the test succeeded and you have your brand new library. The testing will create file digits-test which you probably want to erase. It will also create source.fasl {or something with a similar name depending on your CL implementation} which you may want to keep depending on how you're planning to use the library. The first thing to do is to read the man page. It exists in HTML form {also online at vlaho.ninja/prog/enhanced-do/man.html} and as troff source. To read the latter without copying it in the proper hierarchy of man pages you can do something like man -l man Note that -l is specific to GNU man ; to find out whether the version of man on your system supports something similar you'll have to look in the man page for man ! It's even better if you copy the library to the usual man hierarchy so that doing man enhanced-do will display the page. For a Linux system the right place would be /usr/share/man/man3/ so if you do cp man /usr/share/man/man3/enhanced-do.3common-lisp then it should work. The "common-lisp" part at the end is so that in the unlikely case that there are other man pages named enhanced-do , doing man -e common-lisp enhanced-do will only pick the one with "common-lisp" extension. Finally , if you don't have root privileges you can create a hierarchy under a directory where you have write permissions and copy the man page there. For example mkdir ~/man ~/man/man3 cp man ~/man/man3/enhanced-do.3common-lisp man -M ~/man/man3 -e common-lisp enhanced-do Note again that -e and -M are from GNU man and may not exist in your system's version of man . LICENSE You are free to do anything you want with the code and documentation a.k.a. public domain. CONTACT For bug reports or comments my email {written backwards} is moc.liamg@uobips Spiros Bousbouras August 2017 NOTES [1] http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf [2] For a recent example see <71b9a468-22b0-4549-9bee-4bd908cbc34e@googlegroups.com> "Add optional arguments to the break and continue statements" [ started on 2016-11-09 ] groups.google.com/d/topic/comp.std.c/BCN6XMYVpiw http://www.webuse.net/messaggio.php?num=1202&ng_id=472&offset=&show_headers=yes [ Note that www.webuse.net removes from its archive old threads whereas googlegroups keeps threads indefinitely. ] [3] At least use for me. I realise there are people who absolutely renounce use of continue or break {never mind goto itself} even in C and instead use auxiliary flag variables and other such cumbersome solutions. "There's no accounting for taste" as the saying goes. [4] An example of Graham extolling the power of Lisp macros can be seen at www.paulgraham.com/avg.html . [5] on 2009-11-07. You can get a post itself from its ID at al.howardknight.net .The whole thread can be found at groups.google.co.uk/group/comp.lang.lisp/browse_thread/thread/460e44871936c99c .The thread is too old for www.webuse.net . [6] <7llum1F3cf8qlU1@mid.individual.net> [7] <87my2jh2g6.fsf@freebits.de>