|
| 1 | +.. _tutorial_macro_for_loop_macro: |
| 2 | + |
| 3 | +.. index:: |
| 4 | + single: Tutorial; Macros; For-Loop Macro |
| 5 | + single: Tutorial; Macros; for_loop_macro |
| 6 | + single: Tutorial; Macros; AstForLoopMacro |
| 7 | + single: Tutorial; Macros; table iteration |
| 8 | + |
| 9 | +==================================================== |
| 10 | + Macro Tutorial 9: For-Loop Macros |
| 11 | +==================================================== |
| 12 | + |
| 13 | +Previous tutorials transformed calls, functions, structures, blocks, and |
| 14 | +variants. For-loop macros operate on **for-loop expressions** — they |
| 15 | +intercept ``for (... in ...)`` at compile time and can rewrite the entire |
| 16 | +loop before type inference finalises. |
| 17 | + |
| 18 | +``[for_loop_macro(name="X")]`` registers a class that extends |
| 19 | +``AstForLoopMacro``. The compiler calls the macro's single method |
| 20 | +after each for-loop's sources have been type-checked: |
| 21 | + |
| 22 | +``visitExprFor(prog, mod, expr)`` |
| 23 | + Called after visiting the for-loop body, during type inference. |
| 24 | + Source types are resolved. Return a replacement ``ExpressionPtr`` |
| 25 | + to transform the loop; return ``default<ExpressionPtr>`` to skip. |
| 26 | + |
| 27 | + |
| 28 | +Motivation |
| 29 | +========== |
| 30 | + |
| 31 | +daslang tables (``table<K;V>``) are not directly iterable. The standard |
| 32 | +idiom to iterate a table requires the verbose ``keys()`` and ``values()`` |
| 33 | +built-in functions: |
| 34 | + |
| 35 | +.. code-block:: das |
| 36 | +
|
| 37 | + for (k, v in keys(tab), values(tab)) { |
| 38 | + print("{k} => {v}\n") |
| 39 | + } |
| 40 | +
|
| 41 | +This tutorial builds a for-loop macro that supports a much more natural |
| 42 | +tuple-destructuring syntax: |
| 43 | + |
| 44 | +.. code-block:: das |
| 45 | +
|
| 46 | + for ((k,v) in tab) { // rewrites to keys/values automatically |
| 47 | + print("{k} => {v}\n") |
| 48 | + } |
| 49 | +
|
| 50 | +The ``(k,v)`` syntax is **already supported by the parser** — it |
| 51 | +produces a backtick-joined iterator name (``k`v``) and sets a |
| 52 | +tuple-expansion flag. All the macro needs to do is detect a table |
| 53 | +source, split the name, and rewrite the loop. |
| 54 | + |
| 55 | +.. note:: |
| 56 | + |
| 57 | + Macros cannot be used in the module that defines them. This tutorial |
| 58 | + has **two** source files: a *module* file containing the macro |
| 59 | + definition and a *usage* file that requires the module and exercises it. |
| 60 | + |
| 61 | + |
| 62 | +The macro module |
| 63 | +================ |
| 64 | + |
| 65 | +Prerequisites |
| 66 | +------------- |
| 67 | + |
| 68 | +:: |
| 69 | + |
| 70 | + require ast // AST node types (ExprFor, ExprCall, etc.) |
| 71 | + require daslib/ast_boost // AstForLoopMacro base class, [for_loop_macro] |
| 72 | + require strings // find, slice — for splitting the iterator name |
| 73 | + |
| 74 | + |
| 75 | +Step 1 — Registration |
| 76 | +--------------------- |
| 77 | + |
| 78 | +.. literalinclude:: ../../../../../tutorials/macros/for_loop_macro_mod.das |
| 79 | + :language: das |
| 80 | + :lines: 15-18 |
| 81 | + |
| 82 | +``[for_loop_macro(name=table_kv)]`` registers ``TableKVForLoop`` so the |
| 83 | +compiler calls ``visitExprFor`` for every for-loop in modules that |
| 84 | +``require`` this one. |
| 85 | + |
| 86 | + |
| 87 | +Step 2 — Detect a table source with tuple expansion |
| 88 | +---------------------------------------------------- |
| 89 | + |
| 90 | +Each for-loop's ``ExprFor`` node has several parallel vectors: |
| 91 | + |
| 92 | +``sources`` |
| 93 | + Source expressions (the ``in`` parts). |
| 94 | +``iterators`` |
| 95 | + Iterator variable names. |
| 96 | +``iteratorsAt`` |
| 97 | + Source locations for each iterator. |
| 98 | +``iteratorsAka`` |
| 99 | + Alias names (from ``aka``). |
| 100 | +``iteratorsTags`` |
| 101 | + Tag expressions. |
| 102 | +``iteratorsTupleExpansion`` |
| 103 | + ``uint8`` flag per iterator — nonzero if the parser saw ``(k,v)`` syntax. |
| 104 | +``iteratorVariables`` |
| 105 | + Resolved variables (populated by inference, cleared on rewrite). |
| 106 | + |
| 107 | +The macro scans for a source where the tuple-expansion flag is set |
| 108 | +**and** the source type is a table: |
| 109 | + |
| 110 | +.. literalinclude:: ../../../../../tutorials/macros/for_loop_macro_mod.das |
| 111 | + :language: das |
| 112 | + :lines: 23-31 |
| 113 | + |
| 114 | + |
| 115 | +Step 3 — Split the backtick-joined name |
| 116 | +---------------------------------------- |
| 117 | + |
| 118 | +When the user writes ``(k,v)``, the parser stores the iterator name as |
| 119 | +``"k`v"`` (joined with a backtick). The macro splits this into |
| 120 | +``key_name`` and ``val_name``: |
| 121 | + |
| 122 | +.. literalinclude:: ../../../../../tutorials/macros/for_loop_macro_mod.das |
| 123 | + :language: das |
| 124 | + :lines: 36-42 |
| 125 | + |
| 126 | + |
| 127 | +Step 4 — Clone and rewrite |
| 128 | +--------------------------- |
| 129 | + |
| 130 | +The transformation follows the same pattern as the standard library's |
| 131 | +``SoaForLoop`` (in ``daslib/soa.das``): |
| 132 | + |
| 133 | +1. **Clone** the entire ``ExprFor``. |
| 134 | +2. **Erase** the table entry at ``tab_index`` from all parallel vectors. |
| 135 | +3. **Add** two new sources — ``keys(tab)`` and ``values(tab)`` — with |
| 136 | + the split iterator names. |
| 137 | +4. **Clear** ``iteratorVariables`` so inference rebuilds them on the |
| 138 | + next pass. |
| 139 | + |
| 140 | +.. literalinclude:: ../../../../../tutorials/macros/for_loop_macro_mod.das |
| 141 | + :language: das |
| 142 | + :lines: 43-72 |
| 143 | + |
| 144 | +The helper ``make_kv_call`` builds an ``ExprCall`` node for ``keys()`` |
| 145 | +or ``values()``: |
| 146 | + |
| 147 | +.. literalinclude:: ../../../../../tutorials/macros/for_loop_macro_mod.das |
| 148 | + :language: das |
| 149 | + :lines: 77-80 |
| 150 | + |
| 151 | + |
| 152 | +Using the macro |
| 153 | +=============== |
| 154 | + |
| 155 | +Section 1 — Verbose (without macro) |
| 156 | +------------------------------------ |
| 157 | + |
| 158 | +.. literalinclude:: ../../../../../tutorials/macros/09_for_loop_macro.das |
| 159 | + :language: das |
| 160 | + :lines: 16-21 |
| 161 | + |
| 162 | +This is the standard idiom: ``keys()`` and ``values()`` return separate |
| 163 | +iterators that advance in lock-step. |
| 164 | + |
| 165 | + |
| 166 | +Section 2 — Tuple destructuring |
| 167 | +--------------------------------- |
| 168 | + |
| 169 | +.. literalinclude:: ../../../../../tutorials/macros/09_for_loop_macro.das |
| 170 | + :language: das |
| 171 | + :lines: 27-30 |
| 172 | + |
| 173 | +Exactly the same output, but the syntax is more natural. The macro |
| 174 | +rewrites this to the verbose form transparently. |
| 175 | + |
| 176 | + |
| 177 | +Section 3 — Mixed sources |
| 178 | +--------------------------- |
| 179 | + |
| 180 | +The macro handles the table source independently; other sources in the |
| 181 | +same loop are preserved: |
| 182 | + |
| 183 | +.. literalinclude:: ../../../../../tutorials/macros/09_for_loop_macro.das |
| 184 | + :language: das |
| 185 | + :lines: 37-40 |
| 186 | + |
| 187 | +Here ``(k,v)`` iterates over the table while ``idx`` counts from |
| 188 | +``range(100)``. All three advance in parallel. |
| 189 | + |
| 190 | + |
| 191 | +Running the tutorial |
| 192 | +==================== |
| 193 | + |
| 194 | +:: |
| 195 | + |
| 196 | + daslang.exe tutorials/macros/09_for_loop_macro.das |
| 197 | + |
| 198 | +Expected output:: |
| 199 | + |
| 200 | + --- Section 1: table iteration without the macro --- |
| 201 | + one => 1 |
| 202 | + three => 3 |
| 203 | + two => 2 |
| 204 | + |
| 205 | + --- Section 2: for ((k,v) in tab) with the macro --- |
| 206 | + one => 1 |
| 207 | + three => 3 |
| 208 | + two => 2 |
| 209 | + |
| 210 | + --- Section 3: mixed sources --- |
| 211 | + [0] apple => 10 |
| 212 | + [1] banana => 20 |
| 213 | + [2] cherry => 30 |
| 214 | + |
| 215 | +(Table iteration order may vary.) |
| 216 | + |
| 217 | + |
| 218 | +Real-world example |
| 219 | +================== |
| 220 | + |
| 221 | +The standard library module ``daslib/soa.das`` uses the same mechanism. |
| 222 | +Its ``SoaForLoop`` macro rewrites ``for (it in soa_struct)`` to iterate |
| 223 | +over the individual arrays of a structure-of-arrays layout — a more |
| 224 | +complex transformation but the same ``AstForLoopMacro`` pattern. |
| 225 | + |
| 226 | + |
| 227 | +.. seealso:: |
| 228 | + |
| 229 | + Full source: |
| 230 | + :download:`09_for_loop_macro.das <../../../../../tutorials/macros/09_for_loop_macro.das>`, |
| 231 | + :download:`for_loop_macro_mod.das <../../../../../tutorials/macros/for_loop_macro_mod.das>` |
| 232 | + |
| 233 | + Previous tutorial: :ref:`tutorial_macro_variant_macro` |
| 234 | + |
| 235 | + Standard library: ``daslib/soa.das`` (``SoaForLoop`` macro) |
| 236 | + |
| 237 | + Language reference: :ref:`Macros <macros>` — full macro system documentation |
0 commit comments