Skip to content

Commit efe4f6d

Browse files
authored
Merge pull request GaijinEntertainment#2101 from GaijinEntertainment/doc-doc
for loop macro
2 parents 03a6543 + d9b8fd7 commit efe4f6d

8 files changed

Lines changed: 372 additions & 12 deletions

File tree

doc/source/reference/tutorials.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,5 @@ Run any tutorial from the project root::
152152
tutorials/macros/05_tag_function_macro.rst
153153
tutorials/macros/06_structure_macro.rst
154154
tutorials/macros/07_block_macro.rst
155-
tutorials/macros/08_variant_macro.rst
155+
tutorials/macros/08_variant_macro.rst
156+
tutorials/macros/09_for_loop_macro.rst

doc/source/reference/tutorials/macros/08_variant_macro.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ Key takeaways
342342

343343
Previous tutorial: :ref:`tutorial_macro_block_macro`
344344

345+
Next tutorial: :ref:`tutorial_macro_for_loop_macro`
346+
345347
Standard library:
346348
``daslib/interfaces.das`` (``InterfaceAsIs`` macro),
347349
``daslib/ast_boost.das`` (``BetterRttiVisitor``),
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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

src/ast/ast_infer_type.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4412,8 +4412,9 @@ namespace das {
44124412
if (pVar->type && !pVar->type->isTuple()) {
44134413
error("for loop iterator variable " + pVar->name + " is not a tuple", "", "",
44144414
expr->at, CompilationError::invalid_iteration_source);
4415+
} else {
4416+
expandTupleName(pVar->name, pVar->at);
44154417
}
4416-
expandTupleName(pVar->name, pVar->at);
44174418
}
44184419
++idx;
44194420
}

tests/interfaces/test_missing_inherited.das

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,3 @@ class Partial {
2424
return "bar"
2525
}
2626
}
27-
28-
[export]
29-
def main() {
30-
pass
31-
}

tests/interfaces/test_missing_method.das

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,3 @@ class Incomplete {
1717
pass
1818
}
1919
}
20-
21-
[export]
22-
def main() {
23-
pass
24-
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
options gen2
2+
3+
// Tutorial 09 — For-loop macro
4+
//
5+
// Demonstrates AstForLoopMacro (registered via [for_loop_macro]).
6+
// The macro module defines a for_loop_macro that enables
7+
// for ((k,v) in tab)
8+
// syntax on tables, rewriting it to
9+
// for (k, v in keys(tab), values(tab))
10+
11+
require for_loop_macro_mod
12+
13+
// ---- Section 1: without the macro (the verbose way) -------------------------
14+
15+
def section1() {
16+
print("--- Section 1: table iteration without the macro ---\n")
17+
var tab <- { "one" => 1, "two" => 2, "three" => 3 }
18+
for (k, v in keys(tab), values(tab)) {
19+
print(" {k} => {v}\n")
20+
}
21+
}
22+
23+
// ---- Section 2: with the for_loop_macro -------------------------------------
24+
25+
def section2() {
26+
print("\n--- Section 2: for ((k,v) in tab) with the macro ---\n")
27+
var tab <- { "one" => 1, "two" => 2, "three" => 3 }
28+
for ((k, v) in tab) {
29+
print(" {k} => {v}\n")
30+
}
31+
}
32+
33+
// ---- Section 3: mixed sources -----------------------------------------------
34+
35+
def section3() {
36+
print("\n--- Section 3: mixed sources ---\n")
37+
var tab <- { "apple" => 10, "banana" => 20, "cherry" => 30 }
38+
for ((k, v), idx in tab, range(100)) {
39+
print(" [{idx}] {k} => {v}\n")
40+
}
41+
}
42+
43+
[export]
44+
def main() {
45+
section1()
46+
section2()
47+
section3()
48+
}

0 commit comments

Comments
 (0)