Advertisement

Implementing the range-based for loop (aka foreach)

Started by September 30, 2024 05:39 PM
29 comments, last by WitchLord 4 weeks, 1 day ago

I see from the bytecode that when you're calling the methods on the Range object (opForBegin, opForEnd, etc) you don't push the Range object on the stack.. That's why you see the stack space reducing after each call (which leads to the assert failure).

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

WitchLord said:

I see from the bytecode that when you're calling the methods on the Range object (opForBegin, opForEnd, etc) you don't push the Range object on the stack.

So how can I push the range object to the stack? I use CompileAssignment to create an asCExprContext containing the range object. I can't figure out how to pass this object to the MakeFunctionCall method.

None

Advertisement

I'm away from my computer so I can't give precise details at the moment, but I think you can have a look at CompileExpressionPostOp for the ttDot operator and calling class methods.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I manually generate a PshVPtr byte code with the address of the range object (v2 in this case) for each method call, and it seems fine. The foreach keyword passed a simple test case. I'll make more complex test case for further checking.

Besides, how can I eliminate the compiler warning saying the variable use for iteration (i in this case) is uninitialized? Since the foreach works correctly, this variable is initialized by the compiler-generated code.

The testing script:

class Range
{
  int[] data(4);
  Range() { data = {1, 2, 3, 4}; }
  uint opForBegin() { return 0; }
  bool opForEnd(uint iter) const { return iter == 4; }
  int& opForValue(uint iter) { return data[iter]; }
  uint opForNext(uint iter) { return iter + 1; }
};

void Test()
{
  Range r;
  string s = 'foreach: ';
  foreach(int i : r)
    s += i;
  Print(s + '\n');
}

The screenshot of output

output

None

Nice work. I look forward to seeing the implementation.

PshVPtr is OK, assuming the range object is always a local variable.

To get rid of the Warning, you need to tell the compiler that the variable 'i' is initialized, by setting sVariable::isInitialized = true for the ‘i’ variable.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Nice. If this is approved and merged can't wait to add it to my scripts!

None

Advertisement

WitchLord said:

Nice work. I look forward to seeing the implementation.

PshVPtr is OK, assuming the range object is always a local variable.

To get rid of the Warning, you need to tell the compiler that the variable 'i' is initialized, by setting sVariable::isInitialized = true for the ‘i’ variable.

  1. So the PshVPtr solution might have problem with a global variable as range object? If the global variable is a problem, is there any better solution?
  2. I added code to tell the variable is initialized. Now the compiler won't complain about this.

I also added code to resolve the auto type using the return type of opForValue. Thus, the scripter can write code like this

foreach(auto i : range)
  // Do something. Don't care about the element type of range

However, I tried to implement a reference type for iteration, in order to support script like

foreach(int& i : range)
  i = some_function(i); // Modifying the element

But I found that asCParser::ParseType cannot parse a reference type. Should I combine it with the asCParser::ParseTypeMod and add corresponding compiler code to support reference in foreach loop?

None

For global variables and class members, there are other instructions needed, check for example CompileInitialization. If the range object is a reference type, ideally a handle to it should first be stored in a local variable, so the PshVPtr instruction can be used. But if the range object doesn't allow references, then it will be necessary to evaluate the expression with each call.

I suggest you leave the int& to me. Local references to primitives is not currently supported, due to the difficulty in ensuring that they are safe (i.e that the reference cannot become invalid while it is still in scope).

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

After completing multiple values support (for iterating a dictionary-like object) and regression tests, I will send the patch at once. But the implementation might be naive and lack of handing corner cases, e.g. global variable as range, possible overloading of those 4 operators. I've marked them with comments.

Besides, if the type is a reference to primitive, maybe we can redirect the call to set_opForValue & get_opForValue properties (like get/set_opIndex) to simulate a reference?

None

Thanks, I look forward to seeing the code. You can send it to my e-mail.

I'll be reviewing it carefully before merging it into the WIP repository. I'll do my own tests cases and may revise some of the code to suite my own taste 🙂.

It's OK if it is not feature complete. I can continue on your work to complete the remaining features.

It will probably be a while before I can have it merged into the repository, but I promise this will make it into the next release.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement