[Tutorial] Advanced Iterators (foreach/y_iterate).
#1

Aaaand credits to Y_Less..

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Introduction

Here we will look at how y_iterate (aka "foreach") works internally - what code
is generated by the numerous macros and why this code is good. To begin with,
lets review declaring and using a standard simple iterator. This will be the
example used throughout this tutorial:

pawn Code:
// Declare the iterator:
new
    Iterator:MyIter<7>;
// Add any value between 0 and 6 to the iterator:
Iter_Add(MyIter, 0);
Iter_Add(MyIter, 4);
Iter_Add(MyIter, 6);
// Use the iterator:
foreach (new i : MyIter)
{
    printf("%d", i);
}
Output:

pawn Code:
Code:
0
4
6
You can add any value less than the iterator limit to the iterator, so for this
example "7" cannot be added because it is not LESS THAN 7. We also can't add
negative numbers, so the following lines will not work (they will compile, but
do nothing):

pawn Code:
Iter_Add(MyIter, -11);
Iter_Add(MyIter, 7);
Iter_Add(MyIter, 100);
Multi-Dimensional Iterators

From the original release topic it should be known that you can have arrays of
iterators (multi-dimensional, or MD, iterators):

pawn Code:
new
    Iterator:OwnedVehicle[MAX_PLAYERS]<MAX_VEHICLES>;
Iter_Init(OwnedVehicle);
// Add vehicles to players here...
Iter_Add(OwnedVehicle[playerid], 42);
Iter_Add(OwnedVehicle[playerid], 43);
Iter_Add(OwnedVehicle[playerid], 44);
// Other code...
foreach (new vehicleid : OwnedVehicle[playerid])
{
    printf("Player %d owns vehicle %d", playerid, vehicleid).
}
Originally this used "IteratorArray:" instead of "Iterator:", but that has since
been changed so you can always use either (generally using just "Iterator:").
As a result, the only important difference is the use of "Iter_Init", the
reasons for which are addressed later. Everywhere you would normally use an
iterator you can instead use a selected dimension of a multi-dimensional
iterator (in exactly the same way as you can normally use a variable or array
slot in the same places).

As a side note. Currently everything but "Iter_Init" supports 3-dimensional
iterator arrays - this function must be called for all multi-dimensional
iterators, but can't be easily if there are more than two dimensions. You can
do:

pawn Code:
new
    Iterator:Iter2[5]<10>;
Iter_Init(Iter2);
Iter_Add(Iter2[3], 7);
You can also do:

pawn Code:
new
    Iterator:Iter3[5][8]<10>;
//Iter_Init(Iter3);
Iter_Add(Iter3[3][6], 7);
But you can't currently call "Iter_Init" directly on 3d iterator arrays, despite
the fact that you need to. Instead, you have to do:

pawn Code:
new
    Iterator:Iter3[5][8]<10>;
for (new i = 0; i != Iter_InternalSize(Iter3); ++i)
{
    Iter_Init(Iter3[i]);
}
Iter_Add(Iter3[3][6], 7);
A possibly clearer way is:

pawn Code:
#define SIZE 5
new
    Iterator:Iter3[SIZE][8]<10>;
for (new i = 0; i != SIZE; ++i)
{
    Iter_Init(Iter3[i]);
}
Iter_Add(Iter3[3][6], 7);
Note that you can only do even this as of very recently. If you have a slightly
older version the code becomes:

pawn Code:
for (new i = 0; i != Iter_InternalSize(Iter3); ++i)
{
    Iter_InitInternal(Iter_InternalArray(Iter3[i]), Iter_InternalSize(Iter3[]), Iter_InternalSize(Iter3[][]) - 1);
}
Macros

The "Iterator:" macro generates two variables - one to store the main iterator
data (the array) and the other to store the number of items in the iterator (the
count):

pawn Code:
new
    Iterator:MyIter<7>;
Becomes:

pawn Code:
new
    // Number of items currently in the iterator.
    MyIter@YSII_Cg,
    // The data in the iterator, plus internal data (all slots invalidated).
    MyIter@YSII_Ag[7 + 1] = {14, 13, ...};
"YSII" stands for "YSI Iterator", "C" stands for "Count", "A" stands for
"Array", and "g" stands for "global" (though this is inaccurate as iterators can
be local variables too). "MyIter@YSII_Cg" is the number of items currently
added to the iterator, and not the total size of the iterator. "MyIter@YSII_Ag"
is the values currently stored in the iterator.

Iterators are "linked lists", which means that each element points to the next
valid element. Before adding anything to the array it is declared using:

pawn Code:
MyIter@YSII_Ag[7 + 1] = {7 * 2, 7 * 2 - 1, ...};
This declares the array as 7 cells big to store all the valid data, with an
extra cell to store the start point of the list. The initialisation data is
carefully crafted to be invalid. The use of "- 1" for the second value means
that the compiler attempts to infer a pattern to the array declaration using the
first two values (and determines that the pattern is that each slot is one less
than the previous slot). This means that the array starts off looking like:

pawn Code:
14, 13, 12, 11, 10,  9,  8,  7
Most of those values are greater than 7, so they are invalid. Our extra slot
(the last one) has a value of "7" which is ALMOST valid. The code is designed
so that the last slot ALWAYS contains its own index, which equals the declared
size of the iterator. This is important for the linked list as will be shown
later.

After adding the values "0", "4", and "6" the array will look like:

pawn Code:
4, 13, 12, 11,  6,  9,  7,  0
FOUR slots have changed value - 0, 4, and 6 (the values we added to the
iterator), and slot 7 (our extra slot added by the "Iterator:" macro). Slots
1, 2, 3, and 5 have not been touched so are all still invalid (as shown by the
fact that they are all greater than the maximum valid slot value).

Linked Lists

That's interesting, but why is it like that; and why is slot 0 "4", slot 4 "6"
etc? Each valid slot has a value LESS THAN OR EQUAL TO the iterator size. So
slot 0 is valid and has a value "<= 7" to prove it, as do slots 4, 6, and 7.
But wait, slot 7 CAN'T be valid because it is too big - only slots 0 to 6 are
within the declared range of the iterator, so why does it have a valid value
stored in it? More to the point, why is that last slot initially declared as
"7", when it shouldn't be valid?

Each valid slot's value is the index of the next valid slot. If we start at
slot 0 we get the value "4", jumping to slot 4 gives us the value "6", and slot
6 is the last item we initially added to the iterator:

pawn Code:
Iter_Add(MyIter, 0);
Iter_Add(MyIter, 4);
Iter_Add(MyIter, 6);
Already it should be clear how "foreach" can loop over only values that exist,
unlike "for" which must loop over every possible value. From the information
above we can write:

pawn Code:
for (new i = MyIter@YSII_Ag[0]; ; i = MyIter@YSII_Ag[i])
{
    printf("%d", i);
}
That will loop forever and output:

pawn Code:
0
4
6
7
0
4
6
7
...
We need an end condition for the loop - which is exactly what we have the extra
slot at the end for. Slot 6 has the value "7", which is a value we have already
determined should be invalid, and indeed it is. So we can modify our loop to:

pawn Code:
for (new i = MyIter@YSII_Ag[0]; i != 7; i = MyIter@YSII_Ag[i])
{
    printf("%d", i);
}
New output:

pawn Code:
0
4
6
Start Point

Excellent. The linked list works and ends correctly - but does it START
correctly? In this example yes, but in the more general case NO! What happens
if we never added slot 0 to the iterator?

pawn Code:
new
    Iterator:MyIter<7>;
Iter_Add(MyIter, 4);
Iter_Add(MyIter, 6);
foreach (new i : MyIter)
{
    printf("%d", i);
}
The output SHOULD BE:

pawn Code:
4
6
But if we use our loop above we get:

pawn Code:
14
<crash>
The problem is here:

pawn Code:
new i = MyIter@YSII_Ag[0];
After adding only the values "4" and "6", the array looks like:

pawn Code:
14, 13, 12, 11,  6,  9,  7,  4
But we start at index 0, which has the value "14". We print that value, then
try use that value as an index in to the array. The array has size "7 + 1", so
accessing slot "14" is an out-of-bounds error and won't work (or will crash or
do any other unpredictable things). So instead we need:

pawn Code:
for (new i = MyIter@YSII_Ag[4]; i != 7; i = MyIter@YSII_Ag[i])
{
    printf("%d", i);
}
And if we never add ANY values to the array we still have the original:

pawn Code:
14, 13, 12, 11, 10,  9,  8,  7
And the resulting required code is below (note that this will end instantly
because the value of slot 7 is "7", so the condition will fail immediately):

pawn Code:
for (new i = MyIter@YSII_Ag[7]; i != 7; i = MyIter@YSII_Ag[i])
{
    printf("%d", i);
}
In each case, the "new i" value must be initialised to the value stored in the
first valid slot - so we need to determine WHICH is the first valid slot. That
just so happens to be stored in the last slot of the array:

pawn Code:
for (new first = MyIter@YSII_Ag[7], i = MyIter@YSII_Ag[first]; i != 7; i = MyIter@YSII_Ag[i])
{
    printf("%d", i);
}
This is, in essence, how "foreach" works, and that loop will now correctly
print all the added values (if any) in all cases.

Improvements

Now we have a basic understanding of how "foreach" works under the hood, lets
look at how the macro itself works and generates significantly better code than
that presented above. The simplest way to present this is to show it:

Doing:

pawn Code:
foreach (new i : MyIter)
{
    printf("%d", i);
}
Gives:

pawn Code:
for (new i = 7; (i = MyIter@YSII_Ag[i]) != 7; )
{
    printf("%d", i);
}
This is significantly simplified from the original version, but still
equivalent. The only possibly confusing part is the assignment within the
condition, but this is equivalent to:

pawn Code:
i = MyIter@YSII_Ag[i];
if (i != 7)
{
    printf("%d", i);
}
else break;
The first action in the loop is to get the NEXT item, then use that item. Thus
we have to start with an item that is invalid or else the very first element
would always get skipped because we would have the first item and instantly get
the second item.

Generic Loop Functions

It might help to think of the loop like this:

pawn Code:
for (new i = MyIter[7]; i != 7; i = MyIter[i])
{
}
Or more generically:

pawn Code:
for (new i = MyIter[START_SLOT]; i != END_VALUE; i = MyIter[i])
{
}
But slightly more efficient. It just so happens that "START_SLOT" and
"END_VALUE" are always the same, and always equal to the declared size of the
iterator). We can define and use the generic values:

pawn Code:
#define START_SLOT(%0) (sizeof (%0) - 1)
#define END_VALUE(%0)  (sizeof (%0) - 1)

for (new i = START_SLOT(MyIter@YSII_Ag); (i = MyIter@YSII_Ag[i]) != END_VALUE(MyIter@YSII_Ag); )
{
    printf("%d", i);
}
These macros already exist in y_iterate so you don't even need the "YSII_Ag"
suffixes:

pawn Code:
for (new i = Iter_Begin(MyIter); (i = Iter_Next(MyIter, i)) != Iter_End(MyIter); )
{
}
That code is EXACTLY equivalent to the default "foreach" use, plus it gives you
more control over the looping should you so desire:

pawn Code:
stock PrintFivePlayers()
{
    new
        printed = 0;
    foreach (new i : Player)
    {
        printf("%d", i);
        // Limit the display.
        ++printed;
        if (printed == 5) break;
    }
}
Using the alternate form:

pawn Code:
stock PrintFivePlayers()
{
    for (new i = Iter_Begin(Player), printed = 0; printed != 5 && (i = Iter_Next(MyIter, i)) != Iter_End(MyIter); ++printed)
    {
        printf("%d", i);
    }
}
Granted in this case the second version may be less appealing to look at, but it
does avoid the use of "break" and keeps all the loop end conditions in one
place. It also combines the efficiency of "foreach" with more code.

You may be tempted to "optimise" the loop by calling "Iter_End" just once with:

pawn Code:
stock PrintFivePlayers()
{
    for (new i = Iter_Begin(Player), printed = 0, end = Iter_End(MyIter); printed != 5 && (i = Iter_Next(MyIter, i)) != end; ++printed)
    {
        printf("%d", i);
    }
}
You can, and that will run, but "Iter_End" is actually a macro not a function
call and doing it this way is a rare case in which that will end up being LESS
efficient unfortunately.

Reverse Iteration

Using similar methods to those above you can also go BACKWARDS through an
iterator:

pawn Code:
stock PrintPlayersBackwards()
{
    for (new i = Iter_End(Player); (i = Iter_Prev(MyIter, i)) != Iter_Begin(MyIter); )
    {
        printf("%d", i);
    }
}
However, while this was added to "y_iterate" for completeness sake, it is
INCREDIBLY inefficient and you are MUCH better off using this where possible:

pawn Code:
stock PrintPlayersBackwards()
{
    for (new i = MAX_PLAYERS; i--; )
    {
        if (IsPlayerConnected)
        {
            printf("%d", i);
        }
    }
}
This is because iterators are one-way (and very good at doing things one way),
but when you try and go backwards they can still only go forwards so must go all
theway through the whole loop until they hit the current value and know where
they came from for EVERY iteration. Tracing the loop would give something like:

pawn Code:
7, 0, 4, 6, 7 - Print 6
7, 0, 4, 6 - Print 4
7, 0, 4 - Print 0
7, 0 - End
I did add efficient reverse iterators to the code once, but they doubled the
memory usage and were rarely (if ever) used so were basically worthless.
Reply
#2

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Functions

Beside the well known functions and those listed above, there are many more
available in "y_iterate". These are all documented in the main "foreach"
release topic, but are duplicated here for additional information.
  • Iter_Add(Iterator, value); - Add an in-range value from an iterator:

    pawn Code:
    // Assume "4, 9, 11" already added:
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    printf("Adding 16...");
    Iter_Add(MyIter, 16);
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    Output:

    pawn Code:
    4
    9
    11
    Adding 16...
    4
    9
    11
    16
    [/ul]
  • Iter_Remove(Iterator, value); - Remove an in-range value from an
    iterator:

    pawn Code:
    // Assume "4, 9, 11" already added:
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    printf("Removing 9...");
    Iter_Remove(MyIter, 9);
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    Output:

    pawn Code:
    4
    9
    11
    Removing 9...
    4
    11
  • Iter_Contains(Iterator, value); - Check if the given value is in the
    given iterator:

    pawn Code:
    // Assume "4, 9, 11" already added:
    printf("%d", Iter_Contains(MyIter, 6));
    Iter_Add(MyIter, 6);
    printf("%d", Iter_Contains(MyIter, 6));
    Output:

    pawn Code:
    0
    1
  • Iter_Free(Iterator); - Get the first INVALID value in the iterator.
    Useful for finding empty slots. Returns "-1" on failure (i.e. when there are
    no free slots).

    pawn Code:
    // Assume "0, 1, 3" already added:
    printf("%d", Iter_Free(MyIter));
    Iter_Add(MyIter, 2);
    printf("%d", Iter_Free(MyIter));
    Output:

    pawn Code:
    2
    4
  • Iter_Count(Iterator); - Get the number of UNIQUE elements already
    added to the iterator:

    pawn Code:
    new
        Iterator:MyIter<12>;
    Iter_Add(MyIter, 4);
    Iter_Add(MyIter, 5);
    Iter_Add(MyIter, 9);
    Iter_Add(MyIter, 9);
    printf("%d", Iter_Count(MyIter));
    Output:

    pawn Code:
    3
  • Iter_Clear(Iterator); - Reset the iterator entirely:

    pawn Code:
    // Assume "7, 10, 15" already added:
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    printf("Resetting...");
    Iter_Clear(MyIter);
    foreach (new i : MyIter)
    {
        printf("%d", i);
    }
    Output:

    pawn Code:
    7
    10
    15
    Resetting...
  • Iter_Random(Iterator); - Select a random added element from the
    iterator. This is vastly more efficient than most other implementations:

    pawn Code:
    // Assume "0, 1, 3, 4, 6, 7, 8" already added:
    printf("Random: %d", Iter_Random(MyIter));
    POSSIBLE Output:

    pawn Code:
    4
    POSSIBLE Output:

    pawn Code:
    8
    Etc...
  • Iter_Init(MDIterator); - Normal iterators set themselves up with the
    "{2 * s, 2 * s - 1, ...}" code shown above. However, you can't use that syntax
    with multi-dimensional iterators, so they need explicitly initiating:

    pawn Code:
    new
        Iterator:MyIters[4]<11>;
    Iter_Init(MyIters);
    Iter_SafeRemove(Iterator, value, next); - You can't use "Iter_Remove"
    inside a loop that uses that iterator or it may crash:

    pawn Code:
    foreach (new i : MyIter)
    {
        if (i == 5)
        {
            Iter_Remove(MyIter, i);
        }
    }
    The reason that won't work is that the loop needs to use the current slot to get
    the next slot, but we just removed the current slot so it is no longer valid for
    getting the next slot. Instead, we use "Iter_SafeRemove", which takes an
    additional parameter to store the next slot in BEFORE the current slot is
    removed:

    pawn Code:
    foreach (new i : MyIter)
    {
        new
            cur = i;
        if (cur == 5)
        {
            Iter_SafeRemove(MyIter, cur, i);
        }
    }
    This is equivalent to doing:

    pawn Code:
    new i = Iter_First(MyIter);
    while (i != Iter_End(MyIter))
    {
        new
            cur = i,
            nxt = Iter_Next(MyIter, cur);
        if (cur == 5)
        {
            Iter_Remove(MyIter, cur);
        }
        i = nxt;
    }
  • Iter_Begin(Iterator); - A technically invalid slot that comes before
    the first valid slot of an iterator. Doing "Iter_Next" on this value will give
    the first valid slot (if there is one at all).

  • Iter_End(Iterator); - A value that comes after the last valid slot, to
    check for having reached the end of an array.

  • Iter_Next(Iterator, cur); - Get the iterator value after the current
    one. Will return "Iter_End" when called on the very last valid value.

  • Iter_Prev(Iterator, cur); - Get the iterator value before the current
    one. Will return "Iter_Begin" when called on the very first valid value.

  • Iter_First(Iterator); - Returns the first VALID value of the iterator,
    unlike "Iter_Begin" which returns a value BEFORE the first valid value.

  • Iter_Last(Iterator); - Returns the last valid value of the iterator,
    unlike "Iter_End" which returns a value AFTER the last valid value.

  • Iter_Size(Iterator); - Returns the declared size of the iterator:

    pawn Code:
    new
        Iterator:MyIter<101>;
    printf("%d", Iter_Size(MyIter));
    Output:

    pawn Code:
    101
  • Iter_InternalSize(Iterator); - Returns the internal size of the
    iterator. This includes the additional slot at the end of the array added on by
    the "Iterator:" macro:

    pawn Code:
    new
        Iterator:MyIter<101>;
    printf("%d", Iter_Size(MyIter));
    Output:

    pawn Code:
    102
Reply
#3

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Special Iterators

By now you should have a pretty firm grasp on standard iterators. In terms of
how they are defined, used, and how they work internally.

pawn Code:
// Declare the iterator:
new
    Iterator:MyIter<55>;
// Add any value between 0 and 54 to the iterator:
Iter_Add(MyIter, 0);
Iter_Add(MyIter, 42);
Iter_Add(MyIter, 54);
// Use the iterator:
foreach (new i : MyIter)
{
    print("%d", i);
}
Output:

pawn Code:
0
4
6
This is how the "Player" iterator is defined and runs, as are many others.
However, there is another set of iterators, used indistinguishably from this
first set, but defined in a very different way - these are called "special
iterators".

Example 1

Lets say you want to write an iterator to loop through all positive even
numbers. You could try do this:

pawn Code:
#define IsOdd(%0)  ((%0) & 1)
#define IsEven(%0) (!IsOdd(%0))

new
    Iterator:EvenInt<cellmax>;
for (new i = 0; i != cellmax; ++i)
{
    if (IsEven(i))
    {
        Iter_Add(EvenInt, i);
    }
}
foreach (new j : EvenInt)
{
    printf("%d", j);
}
Output:

pawn Code:
0
2
4
...
At first glance that seems OK. We loop through every integer and add only the
even ones. But there are two issues Firstly the "Iterator:" macro adds on an
extra cell, so we end up with "EvenInt@YSII_Ag[cellmax + 1]". "cellmax" is the
highest possible integer value, so adding 1 to it is an invalid operation and
won't compile. But assuming that it is possible to do that, we would still end
up with an array consisting of 2147483648 4-byte cells - that's exactly 8Gb of
data in your compiled mode! Again, this won't compile.

Fortunately there is another way in the form of a special iterator function:

pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
stock EvenInt@YSII_Ag(cur)
{
    switch (cur)
    {
        case -1: return 0;
        case cellmax - 1: return -1;
    }
    return cur + 2;
}
As before we can now do:

pawn Code:
foreach (new i : EvenInt)
{
    printf("%d", j);
}
Output:

pawn Code:
0
2
4
...
The important difference is that this will compile and run, and won't be vast!

Explanation

Unfortunately the syntax for special iterators is a little clumsy so I'll
explain it very quickly:

pawn Code:
#define EvenInt@YSII_Ag%0- @iterfunc EvenInt()
This line declares a function as a special iterator. The syntax for the line is
always the same, regardless of the number of parameters to your iterator
function:

pawn Code:
#define IterName@YSII_Ag%0- @iterfunc IterName()
Notice the hyphen after "%0" - without that your code won't compile. The
spacing is also important here - don't try write "IterName ()" for example.

pawn Code:
stock EvenInt@YSII_Ag(cur)
This line actually starts ths special iterator function. "stock" is simply
because you don't know if your iterator will be used or not and you don't want a
warning (unless you have it in your mode and know you will use it, in which case
don't use "stock"). The next part is the function name, which again is:
"IterName@YSII_Ag". The "@YSII_Ag" suffix is a leftover from the standard
iterators.

pawn Code:
case -1: return 0;
Standard iterators compile to something similar to:

pawn Code:
for (new i = SIZE_MINUS_1; (i = ITER[i]) != SIZE_MINUS_1; )
For special iterators, there is no size, so there is no start or end point using
this scheme. Instead, "-1" is used:

pawn Code:
for (new i = -1; (i = FUNC(i)) != -1; )
As a side note, this won't work for regular iterators because "-1" is not a
valid array index, so can't be used as the start value without a significant
loss in efficiency.

Anyway: "-1" is passed to the special iterator function at the start of the loop
to get the first value. Here the first value is the first positive even
integer, i.e. 0, so we return that.

pawn Code:
case cellmax - 1: return -1;
As with "-1" as an input being the start of a special iterator, "-1" as a return
value marks the end of the special iterator. Note that this does mean that no
special iterators can ever have "-1" as a valid return unfortunately... In this
case, we need to return "-1" when we run out of positive even integers. The
last number available in signed 32bit integers is "2147483647" - defined in PAWN
as "cellmax", but this is odd so the last positive integer must be 1 less than
that, i.e. "2147483646", which can be written out in full or calculated as
"cellmax - 1". Therefore, when the input to the special iterator is the last
possible positive even integer there can be no further valid returns and instead
"-1" is returned to mark the end of the loop.

pawn Code:
return cur + 2;
This is very simple. For all other input numbers, return the next even number
after that - defined as "cur + 2".

Example 2

An odd numbers iterator would be almost identical, just with different start and
end values:

pawn Code:
#define OddInt@YSII_Ag%0- @iterfunc OddInt()
stock OddInt@YSII_Ag(cur)
{
    switch (cur)
    {
        case -1: return 1;
        case cellmax: return -1;
    }
    return cur + 2;
}
"@iterfunc" is a macro defined in "y_iterate", but we don't actually USE it here
- remember that "#define" creates a text-based replacement so that macro will
only be USED when "foreach" is used with "OddInt":

pawn Code:
foreach (new c : OddInt)
{
}
The fact that no macros from "y_iterate" are actually called here means that we
can DEFINE a special iterator without including "y_iterate", and can thus leave
that decision up to the USER of the library. If they never use the special
iterator and don't include "y_iterate" then because we never include it either
no excess code is generated.

Example 3

Lets say you want to write an iterator to loop through all currently connected
RCON admins. Resulting in the equivalent of:

pawn Code:
foreach (new playerid : Player) if (IsPlayerAdmin(playerid))
{
}
A first attempt might try to use standard iterators and hook
"OnRconLoginAttempt". This is possible, but not as easy as it would first
appear due to "OnRconLoginAttempt" not giving a playerid and the chance that
multiple players may share an IP:

pawn Code:
new
    Iterator:Admin<MAX_PLAYERS>;

hook OnRconLoginAttempt(ip[], password[], success)
{
    // We can't just access the logged in player directly.
    if (success)
    {
        // Rebuild the iterator.
        foreach (new playerid : Player)
        {
            if (IsPlayerAdmin(playerid) && !Iter_Contains(Admin, playerid))
            {
                // Add this admin not already in the array.
                Iter_Add(Admin, playerid);
            }
        }
    }
}

hook OnPlayerDisconnect(playerid, reason)
{
    if (Iter_Contains(Admin, playerid))
    {
        Iter_Remove(Admin, playerid);
    }
}
We can now do:

pawn Code:
foreach (new admin : Admin)
{
    // Loop over all the RCON admins.
}
Indeed, this is an acceptable solution (and it turns out this is actually the
BEST solution in terms of speed). However, there is an alternative route that
can be taken which is much simpler to write and avoids hooking callbacks:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    // Loop over all remaining players AFTER the current player.
    for (new i = cur + 1; i < MAX_PLAYERS; i++)
    {
        // Test if they are an admin (includes an "IsPlayerConnected" check).
        if (IsPlayerAdmin(i))
        {
            return i;
        }
    }
    // Generic "foreach" failure is "-1".
    return -1;
}
The first thing to note is that the "-1" input is never explicitly mentioned.
Instead it is handled generically by "i = cur + 1", which makes "i == 0" when
"cur == -1", and that is the correct initial value for looping over players.

The syntax to use this new special iterator version is identical to the syntax
for the original iterator version. The benefit to this being that the
implementation can be swapped out in your library without users having to
adjust their code at all:

pawn Code:
foreach (new admin : Admin)
{
    // Loop over all the RCON admins, using the special iterator.
}
Reply
#4

Instead of all these different people re-releasing y_less tutorials someone should make a thread and have only ONE person re-post these tutorials do you have any idea of how messy this is going to get ?
Reply
#5

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

Iterators In Iterators

The current implementation of the "Admin" special iterator is not great, but can
be improved slightly by rearranging the loop:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    do
    {
        // Move on to the next player.
        ++cur;
        // Test if they are an admin (includes a connection check).
        if (IsPlayerAdmin(cur)) return cur;
    }
    while (cur != MAX_PLAYERS);
    // Generic "foreach" failure is "-1".
    return -1;
}
This code is still using a normal player loop, which is the exactly the sort of
loop "foreach" was originally designed to replace. Doing so seems to defeat the
entire purpose of these loops, so lets use the real foreach instead via the
additional functions introduced earlier:

pawn Code:
#define Admin@YSII_Ag%0- @iterfunc Admin()
stock Admin@YSII_Ag(cur)
{
    // Get the pre-start value (translate "-1" in to "SIZE_MINUS_1").
    if (cur == -1) cur = Iter_Begin(Player);
    do
    {
        // Get the next player.
        cur = Iter_Next(Player, cur);
        // Run out of players, return failure.
        if (cur == Iter_End(Player)) return -1;
        // Test if the found player is an admin.
    }
    while (!IsPlayerAdmin(cur));
    // Return them.
    return cur;
}
You could also replicate the style used in the standard "foreach" loop with:

pawn Code:
if ((cur = Iter_Next(Player, cur)) == Iter_End(Player)) return -1;
Multi-Dimensional Special Iterators

The MD iterator example from the start of this tutorial was:

pawn Code:
new
    Iterator:OwnedVehicle[MAX_PLAYERS]<MAX_VEHICLES>;
Iter_Init(OwnedVehicle);
// Add vehicles to players here...
Iter_Add(OwnedVehicle[playerid], 42);
Iter_Add(OwnedVehicle[playerid], 43);
Iter_Add(OwnedVehicle[playerid], 44);
// Other code...
foreach (new vehicleid : OwnedVehicle[playerid])
{
    printf("Player %d owns vehicle %d", playerid, vehicleid).
}
Like the "Admin" example this code works fine, but uses a lot of memory. For
500 players and 2000 vehicles the main array takes up "500 * (2000 + 1)" cells,
which is just over 3Mb. That's not a vast amount of memory on modern computers,
but it might still be worth reducing. For some examples a reduction may not be
possible, but in this case a vehicle can only have one owner so there's no point
storing every vehicle for every player. A better storage option would be:

pawn Code:
new
    gVehicleOwner[MAX_VEHICLES];
Here "gVehicleOwner" stores the player ID of the owning player for each vehicle.
To print all the vehicles belonging to one player now looks like:

pawn Code:
for (new vehicleid = 0; vehicleid != MAX_VEHICLES; ++vehicleid)
{
    if (gVehicleOwner[vehicleid] == playerid)
    {
        printf("Player %d owns vehicle %d", playerid, vehicleid).
    }
}
This is a significant reduction in memory, but we had to re-write the code to
accommodate it. Given that we know we can write special iterators that are
functions, can we write a function to hide this change in internal
representation? Yes - quite easily in fact. The original loop was:

pawn Code:
foreach (new vehicleid : OwnedVehicle[playerid])
{
    printf("Player %d owns vehicle %d", playerid, vehicleid).
}
If "OwnedVehicle" is going to become a special iterator function, we need to
pass it the "playerid" we are interested in as an additional parameter. This
parameter comes before the "cur" parameter that holds the vehicle currently
being looked at. From here, using previous information, the code is quite
simple:

pawn Code:
#define OwnedVehicle@YSII_Ag%0- @iterfunc OwnedVehicle()
stock OwnedVehicle@YSII_Ag(playerid, cur)
{
    do
    {
        // The initial value is "-1", increment it to 0, and always increment
        // after that.
        if (++cur == MAX_VEHICLES) return -1;
        // Stay in this function until we find a vehicle this player owns, or
        // we run out of vehicles to test.
    }
    while (gVehicleOwner[cur] != playerid);
    return cur;
}
An additional benefit is that you can't modify special iterators directly. If
we use the first version of "OwnedVehicle" user code can include:

pawn Code:
Iter_Add(OwnedVehicle[playerid], 42);
If you use the special iterator version and try call that function it will
generate a compile-time error. This makes it essentially a read-only iterator
unless you have access to the underlying data store. If this iterator comes
from a vehicle ownership library you may have a function such as:

pawn Code:
Vehicle_SetOwner(vehicleid, playerid);
Which will add that owner to the "gVehicleOwner" array, while checking that the
vehicle isn't already owned and doing anything else. This way you can keep
"gVehicleOwner" as "static" to your library so that no-one can access private
data except through your well-defined API.

More Parameters

We have seen a function with one parameter, and a function with two parameters,
but you can go as far as you like:

pawn Code:
#define MDIter@YSII_Ag%0- @iterfunc MDIter()
stock MDIter@YSII_Ag(par1, par2, par3, cur)
{
    return -1;
}

foreach (new i : MDIter[par1][par2][par3])
{
}
Function-Like Iterators

We have already seen how special iterators are functions but look like normal
iterators. All the examples so far have assumed that the iterator is some data
internal to a library that you want players to be able to loop over. For
example, the "OwnedVehicle" iterator would be defined in a vehicle ownership
library where you would not want the user to have direct access to the internal
information ("gVehicleOwner"). In these cases the library user should not know
HOW the iterator is implemented (be it using special functions or standard
iterators), but sometimes you DON'T want them to look the same.

For example, consider the "y_bit" library. This provides functions for creating
bit arrays - if a user creates a bit array then THEIR code owns that data, not
the "y_bit" library. They know they have a bit array - the fact that they have
one is not an implementation detail of the "y_bit" library. How it WORKS is,
but the fact that it exists isn't.

When you want to access someone else's data you use their variables or arrays,
but when you want to manipulate your own data you pass it to functions. This
idea carries over in to iterators. Looping over other people's iterators looks
like regular variable or array access:

pawn Code:
foreach (new playerid : Player)
{
}
But manipulating your own data that you know is NOT an iterator should look like
a function call:

pawn Code:
new
    BitArray:arr<100>;
foreach (new c : Bits(arr))
{
}
This is a tricky distinction to explain, but hopefully you get the difference.
It doesn't help that all the previous examples didn't distinguish between what
is user code and what is library code. In general, the previous special
iterator function definitions would have been in a library, and the "foreach"
loops using those iterators would have been in user code using that library
(though you can of course use the iterator within the library). This was why I
made the point about not needing to include "y_iterate" in to your library at
all when only using special iterator functions.

"y_bit" provides the "Bits" iterator, which takes a bit array and loops over all
the bits set within it:

pawn Code:
new
    BitArray:arr<100>;
Bit_Set(arr, 42, true);
Bit_Set(arr, 82, true);
Bit_Set(arr, 11, true);
Bit_Set(arr, 99, true);
Bit_Set(arr, 7, true);
foreach (new c : Bits(arr))
{
    printf("%d", c);
}
Output:

pawn Code:
7
11
42
82
99
This iterator takes and manipulates YOUR data, so does NOT look like a variable
or array, but instead looks like a normal function. However, the definition in
code is actually exactly the same as normal:

pawn Code:
#define Bits@YSII_Ag%0- @iterfunc Bits()
stock Bits@YSII_Ag(BitArray:data[], cur)
This takes one parameter that is the bit array to loop over, and a second
parameter that is the standard current value. Actually, that's a slight lie, it
really looks like:

pawn Code:
#define Bits@YSII_Ag%0- @iterfunc Bits()
stock Bits@YSII_Ag(BitArray:data<>, start, size = sizeof (data))
Here we have an additional parameter AFTER the "cur" parameter (now called
"start", just because it can be called anything), but because that parameter
takes a default value it doesn't need to be specified in the "foreach" loop and
it MUST come last. It also uses the bit array "<>" syntax instead of the
normal array "[]" syntax, but this is well documented in the "y_bit" library.
Reply
#6

Contents

First Post

Information on the internal implementation of various parts of "y_iterate".

Introduction - A brief word on what this tutorial is about and a recap.
Multi-Dimensional Iterators - A review of multi-dimensional iterators required for later.
Macros - How the "Iterator:" macro works.
Linked Lists - An explanation of the data type used by iterators.
Start Point - How "foreach" loops over arbitrary arrays.
Improvements - Why "foreach" is better than "for" in most cases.
Generic Loop Functions - Using iterators without "foreach".
Reverse Iteration - Going backwards through iterators.

Second Post

Function list.

Functions - A list of all the functions provided by "y_iterate".

Third Post

An introduction to "special iterators" - those that are defined by functions and
not by using "Iterator:".

Special Iterators - An introduction to writing special iterator functions.
Example 1 - Writing an iterator for every positive even integer.
Explanation - A more detailed look at the code for Example 1.
Example 2 - Writing an iterator for every positive even integer.
Example 3 - Writing an iterator for RCON admins, which can be done multiple ways.

Fourth Post

More advanced special iterators.

Iterators In Iterators - Using iterators to create new iterators.
Multi-Dimensional Special Iterators - Special iterators that look like multi-dimensional iterators.
More Parameters - Passing more than one parameter to a special iterator.
Function-Like Iterators - Special iterators that look like regular functions.

Fifth Post

A detailed look at the macros that make up "y_iterate" and make all these
different iterator types work together.

"foreach" Types - A review of all the "foreach" syntax forms.
Redefining "new" - "y_iterate"'s dirty little secret.
Y_FOREACH_SECOND - Detecting one syntax form.
Y_FOREACH_THIRD - Detecting a second syntax form.
Why Redefine "new"? - Why the "#define new" hack is required.
The Remainder - All the other syntax form detections.

"foreach" Types

We will now look at one important chunk of the macros in "y_iterate", used to
determine exactly which version of "foreach" has been entered. To understand
the macros however, we must first understand what we are trying to parse. The
following code demonstrates all the possible variations of "foreach" that can
appear in [pawn]

pawn Code:
// Original version (implicit declaration):
foreach (Player, playerid)

// New version:
foreach (playerid : Player)

// New version with declaration:
foreach (new playerid : Player)

// New version with tag declaration:
foreach (new Group:group : PlayerGroup)
We need to write macros to correctly identify and process each of these versions
of the keyword. There is also an older version of the command that looked like:

pawn Code:
// Original version (no declaration):
foreachex (Player, playerid)
This was different from "foreach" in that it DIDN'T declare "playerid" as a new
variable, which the standard "foreach" keyword did. This has been deprecated in
the later versions of "y_iterate" because the "new" keyword is now explicit.
Fortunately, this version is much easier to express in the new system.

There are two things that we need to detect - number of colons, and number of
"new" keywords. Zero colons means the old syntax is in use, anything else is
the new syntax. You could in theory have:

pawn Code:
// Original version with tag (implicit declaration):
foreach (PlayerGroup, Group:group)
However, this was never supported and no tagged iterators were ever written for
this older version. We can therefore ignore this possibility.

At this point I'd love to detail the logical progression that led to my
development of these macros, in the hope that people could learn something from
the thought process, but quite simply I can't! I have no idea how I came up
with this system, it was just a lot of thinking resulting in a brainwave that I
can't explain - which is a real shame IMHO. As a result, I can only document
how the macros work.

Redefining "new"

The main "foreach" macro actually revolves around this macro:

pawn Code:
#define new%0|||%9|||%1:%2||| %9|||%0|||%1|||%2|||
That actually redefines the "new" keyword to something different (I'm not too
happy about having to do this). Because it redefines such a pivotal parf of the
PAWN language, I have made sure that it does not match common code. As you can
see, the pattern for matching is this:

pawn Code:
new  |||  |||  :  |||
I've used spaces here instead of macro parameters ("%0" etc) to make the pattern
clearer and represent optional code typed by the user. If you have any "new"
variable declarations with this exact sequence of operators after it then I
apologise - this will probably break you code. But I think that's unlikely!

The replacement for the macro is:

pawn Code:
%9|||%0|||%1|||%2|||
We detect the "new" keyword, but we don't have it present in the replacement, so
this code looks for "new" and removes it. It also shuffles the remaining
parameters and removes the detected colon, but the result is still not valid
code.

To further understand the macro, lets look at it being used:

pawn Code:
#define foreach%1(%0) for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||%0||| )
Here "%1" simply detects extra spaces and discards them. We then add the word
"new", which as we've seen is instantly detected and removed. Why not simply
write:

pawn Code:
#define foreach%1(%0) for (Y_FOREACH_THIRD|||Y_FOREACH_SECOND|||%0||| )
You might think that would be the result of the macros after calling the "new"
macro, but you would be wrong - sometimes...

The key fact to note here is that the "new" macro looks for three sets of "|||"
AND a colon. We didn't write the colon in the replacement text for "foreach",
so it must be part of the parameter "%0". If we type:

pawn Code:
foreach (new playerid : Player)
We have "%1" as a single space (discarded) and "%0" as "new playerid : Player".
Manually replacing macros gives:

pawn Code:
// Detect and replace "foreach"....
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||new playerid : Player||| )

// Detect and replace "new"...
for (Y_FOREACH_THIRD||| Y_FOREACH_SECOND|||new playerid ||| Player||| )
So what happens if we DON'T type a colon? The only time this happens is when we
use the old-style "foreach" syntax:

pawn Code:
foreach (Player, playerid)

// Detect and reaplce "foreach":
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||Player, playerid||| )

// DO NOT replace "new" because there is no colon to match.
Compare the outputs side-by-side:

pawn Code:
foreach (new playerid : Player)
// Gives:
for (Y_FOREACH_THIRD||| Y_FOREACH_SECOND|||new playerid ||| Player||| )

foreach (Player, playerid)
// Gives:
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||Player, playerid||| )
Each "foreach" line declares a new variable (this fact is hidden in the second
one, which the main motivation for developing the new syntax). In the resulting
generated code each "for" loop has a "new" keyword somewhere in it, and each one
starts with a DIFFERENT macro. The first one starts with "Y_FOREACH_THIRD", the
second one starts with "Y_FOREACH_SECOND" (don't worry that the first one isn't
called "Y_FOREACH_FIRST", that's just because of the order I am describing the
macros in). This demonstrates why we remove the "new" keyword, and why we
shuffle the first two parameters around.

Y_FOREACH_SECOND

This macro deals with the old version of the syntax, and is very simple to
parse. From the earlier parts of this tutorial, we know that the end product of
a "foreach" loop is:

pawn Code:
// This:
foreach (Iter, var)
// Or this:
foreach (new var : Iter)
// Becomes:
for (new var = sizeof (Iter@YSII_Ag) - 1; (var = Iter@YSII_Ag[var]) != sizeof (Iter@YSII_Ag) - 1; )
So we can very quickly construct the "Y_FOREACH_SECOND" macro and step through
its expansion:

pawn Code:
// Macro:
#define Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||%2,%1||| %1 = sizeof (%2@YSII_Ag) - 1; _:(%1 = %2@YSII_Ag[%1]) != sizeof (%2@YSII_Ag) - 1;

// Input:
foreach (Player, playerid)

// Steps:
// Detect and reaplce "foreach"...
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||Player, playerid||| )

// DO NOT replace "new" because there is no colon to match.

// Detect and replace "Y_FOREACH_SECOND":
for (new  playerid = sizeof (Player@YSII_Ag) - 1; _:( playerid = Player@YSII_Ag[ playerid]) != sizeof (Player@YSII_Ag) - 1; )

// Done!
Y_FOREACH_THIRD

If this macro is the first one listead, instead of "Y_FOREACH_SECOND", then the
user used the new syntax instead of the old syntax, so let us parse that
instead (here "%9" stores the other macro name and discards it):

pawn Code:
// Macro:
#define Y_FOREACH_THIRD|||%9|||%1|||%2||| %1 = sizeof (%2@YSII_Ag) - 1; _:(%1 = %2@YSII_Ag[%1]) != sizeof (%2@YSII_Ag) - 1;

// Input:
foreach (new playerid : Player)

// Steps:
// Detect and reaplce "foreach"...
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||new playerid : Player||| )

// Detect and replace "new"...
for (Y_FOREACH_THIRD||| Y_FOREACH_SECOND|||new playerid ||| Player||| )

// Detect and replace "Y_FOREACH_THIRD"...
for (new playerid  = sizeof ( Player@YSII_Ag) - 1; _:(new playerid  =  Player@YSII_Ag[new playerid ]) != sizeof ( Player@YSII_Ag) - 1; )

// DO NOT replace "new" because there are no "|||"s to match.

// Done!
BAH! So close! The "new" keyword is still attached to the "playerid" symbol,
so every time we use one we use the other. In this case, we need to again
detect and remove the "new" keyword - an operation that we already wrote a macro
for:

pawn Code:
#define new%0|||%9|||%1:%2||| %9|||%0|||%1|||%2|||
This is where the "foreach" macros start getting really clever by repeatedly
calling this "new" macro with different macro names as parameters. The first
time we passed the two macros called "Y_FOREACH_SECOND" and "Y_FOREACH_THIRD",
this time we will pass different ones. Let's have a second attempt at that
"Y_FOREACH_THIRD" macro:

pawn Code:
// We previously removed the colon, but now we must put it back to be detected.
#define Y_FOREACH_THIRD|||%9|||%1|||%2||| %1|||Y_FOREACH_FOURTH|||%1:%2|||

// If there is a "new" in "%1", then "Y_FOREACH_FOURTH" will be run.
#define Y_FOREACH_FOURTH|||%0|||%1|||%2||| new %0 = sizeof (%2@YSII_Ag) - 1; _:(%0 = %2@YSII_Ag[%0]) != sizeof (%2@YSII_Ag) - 1;

// Input:
foreach (new playerid : Player)

// Steps:
// Detect and reaplce "foreach"...
for (new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||new playerid : Player||| )

// Detect and replace "new"...
for (Y_FOREACH_THIRD||| Y_FOREACH_SECOND|||new playerid ||| Player||| )

// Detect and replace "Y_FOREACH_THIRD"...
for (new playerid |||Y_FOREACH_FOURTH|||new playerid : Player||| )

// Detect and replace "new" again...
for (Y_FOREACH_FOURTH||| playerid |||new playerid ||| Player||| )

// Detect and replace "Y_FOREACH_FOURTH"...
for (new  playerid  = sizeof ( Player@YSII_Ag) - 1; _:(  playerid  =  Player@YSII_Ag[  playerid ]) != sizeof ( Player@YSII_Ag) - 1; )

// DO NOT replace "new" because there are no "|||"s to match.

// Done!
By calling the "new" macro twice we have managed to strip off just the word
"new" from "playerid" so that we know it was present but can add it in only
where we want it.

Why Redefine "new"?

There are a couple of ways to detect specific pieces of text - a define is one,
putting the text directly in the pattern is another. To detect the keyword
"new" we could do:

pawn Code:
#define new%0|||
Or:

pawn Code:
#define OTHER%0new%1|||
The first will detect anything starting with "new", the second will detect
anything that starts with "OTHER" (just as an example) and that includes the
text "new" somewhere after it. The difference is in exactly how much is
matched. A define CALLED "new", as the first one is, will search for only that
exact word - the word "newt" will NOT be matched. The second one will match
exactly the word "OTHER", but then just searches for the three letters "n", "e",
and "w" in order after it, so WILL detect the following:

pawn Code:
MY OTHER newt|||
In that case, "%0" will be a single space and "%1" will be the letter "t". This
is a subtle point when dealing with the pre-processor. This is important
because were we to use the second method, any variable name with "new" within it
would cause a compilation error:

pawn Code:
foreach (newt : Animal)
There is a second reason as well - the result if the match is NOT made. If we
do:

pawn Code:
#define S: MACRO_1 MACRO_2
#define MACRO_1%0MACRO_2%0new%1||| new %1; // Found "new".
#define MACRO_2%1||| %1; // Didn't find "new".
(Side note, the use of "%0" twice in that example is valid - the second one will
overwrite the contents of the first one, but both are discarded anyway).

Then doing:

pawn Code:
S: new var|||
Will give:

pawn Code:
new var;
But doing:

pawn Code:
S: printf("%d", var)|||
Will give:

pawn Code:
MACRO_1   printf("%d", var);
Because we couldn't find "new", the "MACRO_1" macro is never matched and the
text remains, leaving us with the word "MACRO_1" in the source-code where it
shouldn't be, and thus giving a compilation error.

If we instead do something like:

pawn Code:
#define S: new MACRO_3|||
#define new%0|||%1||| new other,%1;
#define MACRO_3|||%1; %1
Then doing:

pawn Code:
S: var|||
Will give:

pawn Code:
new other, var;
And doing:

pawn Code:
S: var;
Will give:

pawn Code:
new var;
In the second case, we have an excess "new" left over from the first macro that
wasn't matched, but we have designed the macros such that this remainder is
still valid in this context, and so that we won't get any compile-time errors.
This is basically how all the macros in "y_iterate" are designed - if the "new"
keyword is not matched, it must be because we have a pattern that requires the
"new" keyword to be present and therefore leaving it in the source code output
is valid.

The Remainder

The code presented so far is a little inaccurate, but is enough to describe the
basic principles on which the whole code block is based. They show how we use
the "new" keyword to detect certain patters and strip out certain words, and how
the macros are shuffled around so that different code is run when "new" is there
and when it isn't. Given this grounding, the remaining macros will not be
detailed to the same extent the first few are, but just to get you started, here
is the REAL version of Y_FOREACH_THIRD (note that "%1" and "%2" are the other
way around here to what they were before):

pawn Code:
#define Y_FOREACH_THIRD|||%0|||%1|||%2||| %1=Y_FOREACH_FIFTH|||Y_FOREACH_FOURTH|||%1:%2|||
"Y_FOREACH_FOURTH" is still there, but "Y_FOREACH_FIFTH" has been added,
separated from whatever is in "%1" by an equals sign (if "%1" is a variable, a
lack of delimiter here could cause the to names to merge and no longer be
valid), to run more code when there is no "new" keyword:

pawn Code:
#define Y_FOREACH_FIFTH|||Y_FOREACH_FOURTH|||%1:%2||| sizeof (%2@YSII_Ag) - 1; _:(%1 = %2@YSII_Ag[%1]) != sizeof (%2@YSII_Ag) - 1;
// Handles:
foreach (playerid : Player)
Note that again, the real macro is slightly different, but the extra indirection
handles iterator arrays and special iterators, it can basically be read as the
code above:

pawn Code:
#define Y_FOREACH_FIFTH|||Y_FOREACH_FOURTH|||%1:%2||| _Y_ITER_FOREACH_SIZE(%2);_:(%1=_Y_ITER_ARRAY$%2$YSII_Ag[%1])!=_Y_ITER_MAYBE_ARRAY(%2);
"_Y_ITER_MAYBE_ARRAY" gets the size of a slot in an iterator, for both normal
iterators and iterator arrays. "_Y_ITER_FOREACH_SIZE" does the same thing, but
can also detect special iterators defined with "()" syntax. "_Y_ITER_ARRAY"
gets the next slot in the current iterator for both normal iterators and arrays.

The real definition of "Y_FOREACH_FOURTH" is not actually a final output - it is
another redirection designed to try and detect declarations with tag overrides.
It adds in the macros "Y_FOREACH_SIXTH" and "Y_FOREACH_SEVENTH":

pawn Code:
// Tries to detect a colon via "new" despite us not adding it back (as in 3rd).
#define Y_FOREACH_SEVENTH|||%9Y_FOREACH_SIXTH;%0|||%1|||%2||| new %0:%1 = %0:(sizeof (%2@YSII_Ag) - 1); _:(%1 = %0:_Y_ITER_ARRAY$%2$YSII_Ag[%1]) != sizeof (%2@YSII_Ag) - 1;
// Handles:
foreach (new Group:g : CreatedGroup)

#define Y_FOREACH_SIXTH;%1|||Y_FOREACH_SEVENTH|||%2||| %1 = sizeof (%2@YSII_Ag) - 1; _:(%1 = _Y_ITER_ARRAY$%2$YSII_Ag[%1]) != sizeof (%2@YSII_Ag) - 1;
// Handles:
foreach (new playerid : Player)
So to recap:

"Y_FOREACH_SECOND" - Generates code for the original syntax.
"Y_FOREACH_FIFTH" - Generates code for the new syntax with no "new".
"Y_FOREACH_SEVENTH" - Generates code for the new syntax with tags.
"Y_FOREACH_SIXTH" - Generates the remainder of the new syntax ("new", no tags).

The others just call "new" again to try resolve which version to use.

Hopefully from this description you should now be able to read and comprehend
this code, taken straight from "y_iterate":

pawn Code:
#define foreach%1(%0) for(new Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||%0|||)

// This allows us to use "new" multiple times - stripping off ONLY whole words.
#define new%0|||%9|||%1:%2||| %9|||%0|||%1|||%2|||

// This one is called if the new syntax is required, but the state of "new" is
// as-yet unknown.  This attempts to call "%1" as a macro, if it starts with
// "new" as a whole word then it will (and will also helpfully strip off the
// "new" keyword for us).
#define Y_FOREACH_THIRD|||%0|||%1|||%2||| %1=Y_FOREACH_FIFTH|||Y_FOREACH_FOURTH|||%1:%2|||

// This is called if the "new" macro is called for a second time.
#define Y_FOREACH_FOURTH|||%0=Y_FOREACH_FIFTH|||%1|||%2||| new Y_FOREACH_SIXTH;%0|||Y_FOREACH_SEVENTH|||%2|||

// This is called when there are tags on the "new" declaration.
#define Y_FOREACH_SEVENTH|||%9Y_FOREACH_SIXTH;%0|||%1|||%2||| new %0:%1=%0:_Y_ITER_FOREACH_SIZE(%2);_:(%1=%0:_Y_ITER_ARRAY$%2$YSII_Ag[%1])!=_Y_ITER_MAYBE_ARRAY(%2);

// This is called when there aren't.
#define Y_FOREACH_SIXTH;%0|||Y_FOREACH_SEVENTH|||%2||| %0=_Y_ITER_FOREACH_SIZE(%2);_:(%0=_Y_ITER_ARRAY$%2$YSII_Ag[%0])!=_Y_ITER_MAYBE_ARRAY(%2);

// This is called if "%1" didn't have "new" at the start.
#define Y_FOREACH_FIFTH|||Y_FOREACH_FOURTH|||%1:%2||| _Y_ITER_FOREACH_SIZE(%2);_:(%1=_Y_ITER_ARRAY$%2$YSII_Ag[%1])!=_Y_ITER_MAYBE_ARRAY(%2);

// This is the old version, but DON'T add "new" because that already exists from
// the failed "new" macro call above.
#define Y_FOREACH_SECOND|||Y_FOREACH_THIRD|||%1,%2||| %2=_Y_ITER_FOREACH_SIZE(%1);_:(%2=_Y_ITER_ARRAY$%1$YSII_Ag[%2])!=_Y_ITER_MAYBE_ARRAY(%1);
Reply
#7

So nobody has the latest multi-iterator examples? I don't have no clue how they work...
Reply
#8

Quote:
Originally Posted by Crayder
View Post
So nobody has the latest multi-iterator examples? I don't have no clue how they work...
https://sampforum.blast.hk/showthread.php?tid=571159
Reply
#9

Very helpful, i didn't know this existed, with foreach code explanation in it <3
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)