(rontolisp) docs
← Macros

loop

(loop clause...) (extended) or (loop form...) (simple)

A bounded subset of the ANSI loop macro. It expands to the existing iteration core (do*-style stepping wrapped in the internal block boundary), so it works identically on the interpreter and both compilers.

If every top-level subform is a compound form, loop is a simple loop: it repeats those forms forever until a return exits it.

Otherwise it is an extended loop built from clauses. The supported clauses are:

  • Numeric stepping: for VAR from LO [to|upto|below|downto|above HI] [by STEP] (also upfrom/downfrom; a limit keyword with no from starts at 0).
  • List stepping: for VAR in LIST [by FN] and for VAR on LIST [by FN] (VAR may be a destructuring pattern).
  • Sequence stepping: for VAR across SEQ binds VAR to each character of a string or each element of a vector in turn.
  • General stepping: for VAR = INIT [then STEP] (VAR may be a destructuring pattern).
  • Local variables: with VAR [= INIT] (VAR may be a destructuring pattern; and-joined with bindings are parallel).
  • Accumulation: collect, append, nconc, sum, count, maximize, minimize, each with an optional into VAR.
  • Termination tests: thereis EXPR, always EXPR, never EXPR.
  • Control: while/until (honoring their textual position), repeat N, do FORM..., return EXPR, (loop-finish) inside body forms, initially FORM..., finally FORM..., and the conditionals when/if/unless with optional else and end (the tested value is available as it in the selected clauses).

Multiple for clauses step together, and the loop ends as soon as the shortest driver is exhausted — the idiomatic indexed map. Sequential clauses step in order (a later clause's init and step forms see the values the earlier clauses just produced, and stepping stops at the first exhausted driver, so for x in xs for a = (f x) then (g a x) works as in CL):

and joins for clauses into one group whose inits and steps are computed against the previous iteration's values (like do's parallel stepping versus do*):

Accumulation and numeric ranges cover the common cases directly:

A while/until after body clauses (or after a for that assigns its variable at the top of the body, such as in/on/across) tests at its textual position, so it can reference the current element:

Inside a when/if/unless, the anaphoric it names the value the test produced:

thereis returns the first non-nil value of its expression; always/never short-circuit to nil on the first failure and return t on normal completion. Like return, an early exit from these skips finally:

(loop-finish) inside a body form terminates the iteration normally: finally still runs and the loop result is produced (unlike return, which skips both):

A for/with variable may be a destructuring pattern — a proper list of variables (nested patterns allowed, nil ignores a position):

for ... across walks a string character by character, or a vector element by element:

The package form of beingfor VAR being {the|each} {symbols|present-symbols|external-symbols} {of|in} PACKAGE — is accepted but lite: rontolisp has no runtime intern table, so the clause parses and iterates the empty sequence. The body never runs and accumulation yields nil. It exists so libraries whose load-time code walks a package (such as cl-who's hyperdoc table) load without error:

Limitations: the being hash-table iteration (hash-key/hash-value) and named/return-from are not supported. Destructuring patterns are proper lists of variables — dotted patterns like (a . b) are unavailable because the reader rejects dotted-pair syntax, and lambda-list keywords are not recognized. (loop-finish) must appear in statement position (not mid-expression) and not inside a nested iteration form. thereis/always/never cannot be combined with accumulation into the default result (use into). Accumulation clauses without into must all be of the same kind; collecting clauses build the result list in source order.