Advertisement

Implementing the range-based for loop (aka foreach)

Started by September 30, 2024 05:39 PM
29 comments, last by WitchLord 2 months ago

Now the foreach keyword supports multiple iterating values. All of the basic implementation is done.

class Enumerate
{
  int[] data(4);
  Enumerate() { data = {101, 102, 103, 104}; }
  uint opForBegin() { return 0; }
  bool opForEnd(uint iter) const { return iter == 4; }
  uint opForValue0(uint iter) { return iter; }
  int& opForValue1(uint iter) { return data[iter]; }
  uint opForNext(uint iter) { return iter + 1; }
};

void Test()
{
  Enumerate r;
  string s;
  foreach(uint idx, int val : r)
  {
    if(idx != 0) s += ', ';
    s += idx + ': ' + val;
  }
  Print(s); // Prints '0: 101, 1: 102, 2: 103, 3: 104'
}

Besides, the patch has been sent via email. @WitchLord

None

Thanks. I've received the patch. I'll review it and update here when I've merged it into the library.

Given that we decided to introduce the new keyword I'll also add an engine property to disable this feature for backwards compatibility in case some older script is already using that keyword.

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

I've merged the patch into the repository now.

https://sourceforge.net/p/angelscript/code/2975/

It should work quite well already, but it is still a work in progress.

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

Thank you both!

A simple source reiteration now works as:

foreach (auto field : c.GetFields())  { … }

where in my case auto resolves to a DataField@.

Was thinking, if merging of opForEnd and opForNext is possible somehow.
But it is a much better UX for scripting having foreach already (I need a VS Code syntax highlighter update though :P)

None

I'll give it some thought but I think merging opForEnd and opForNext won't work well. At least it won't remove the need for opForEnd, which is needed to determine if the value returned by opForBegin is already beyond the end.

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

Thank you for merging this! And huge thanks to Henry for working on this, it is very awesome.

I have yet to play with it more, but it seems to be pretty stable so far. As for an implementation for the scriptarray addon, this does the job, if anyone needs it: (based on Henry's example, but modified to have the index as the optional second iterating value)

static void ScriptArray_OpForBegin(asIScriptGeneric* gen)
{
	gen->SetReturnDWord(0);
}

static void ScriptArray_OpForEnd(asIScriptGeneric* gen)
{
	auto arr = (CScriptArray*)gen->GetObject();
	asUINT iter = gen->GetArgDWord(0);
	gen->SetReturnByte(iter == arr->GetSize() ? 1 : 0);
}

static void ScriptArray_OpForNext(asIScriptGeneric* gen)
{
	gen->SetReturnDWord(gen->GetArgDWord(0) + 1);
}

static void ScriptArray_OpForValue0(asIScriptGeneric* gen)
{
	auto arr = (CScriptArray*)gen->GetObject();
	asUINT iter = gen->GetArgDWord(0);
	gen->SetReturnAddress(arr->At(iter));
}

static void ScriptArray_OpForValue1(asIScriptGeneric* gen)
{
	gen->SetReturnDWord(gen->GetArgDWord(0));
}
	r = engine->RegisterObjectMethod("array<T>", "uint opForBegin()", asFUNCTION(ScriptArray_OpForBegin), asCALL_GENERIC); ASSERT(r >= 0);
	r = engine->RegisterObjectMethod("array<T>", "bool opForEnd(uint iter)", asFUNCTION(ScriptArray_OpForEnd), asCALL_GENERIC); ASSERT(r >= 0);
	r = engine->RegisterObjectMethod("array<T>", "uint opForNext(uint iter)", asFUNCTION(ScriptArray_OpForNext), asCALL_GENERIC); ASSERT(r >= 0);
	r = engine->RegisterObjectMethod("array<T>", "T& opForValue0(uint iter)", asFUNCTION(ScriptArray_OpForValue0), asCALL_GENERIC); ASSERT(r >= 0);
	r = engine->RegisterObjectMethod("array<T>", "uint opForValue1(uint iter)", asFUNCTION(ScriptArray_OpForValue1), asCALL_GENERIC); ASSERT(r >= 0);

Dictionary will require some kind of different iterator type, I didn't want to jump into that right away.

I've also updated the Sublime Text syntax highlighter package: https://github.com/wronex/sublime-angelscript

Advertisement

Some initial things I noticed.

I can't receive my values by reference, even though opForValue returns by reference, AND I have asEP_ALLOW_UNSAFE_REFERENCES turned on:

	array<string> arr = { "foo", "bar", "foobar" };
	foreach (string& value : arr) {
		value = "aaaa";
	}

Trying to modify using auto does not work either: (is a copy being made for each value returned?)

	array<string> arr = { "foo", "bar", "foobar" };
	foreach (auto value : arr) {
		value = "aaaa";
	}
	foreach (auto value : arr) {
		print(value);
	}

It would be cool if we could omit types in multiple values if they match the same type:

foreach (uint n, nn : s) {

Or even if they are different types using auto:

foreach (auto n, something : s) {

Or maybe even omitting the type altogether (implying auto), but that may be a bit too much:

foreach (n, something : s) {

Constness seems to be ignored for these operators:

class Constness
{
	int opIndex(uint iter) { return 1234; }

	uint opForBegin() { return 0; }
	bool opForEnd(uint iter) { return iter == 5; }
	uint opForNext(uint iter) { return iter + 1; }
	uint opForValue(uint iter) { return (iter + 1) * 10; }
}

void Main()
{
	const Constness b;

	// Compiles fine (unexpected)
	foreach (auto n : b) {
		print(n);
	}

	// Compiler error (as expected)
	int n = b[123];
}

I think I saw a TODO item about overloading not working as it should yet, so perhaps this is part of that and still on the list of things to fix. On that note, I would expect something like this to work:

	uint opForValue(uint iter) { return (iter + 1) * 10; }
	const uint opForValue(uint iter) const { return (iter + 1) * 10; }

Hope this helps to improve this feature 💪

Miss said:
I can't receive my values by reference, even though opForValue returns by reference, AND I have asEP_ALLOW_UNSAFE_REFERENCES turned on

Returning references from the opForValue should be just fine. The issue is that AngelScript does not yet support declaring variables as references (it requires further changes to ensure the references are guaranteed throughout the life span of the variable). Even for asEP_ALLOW_UNSAFE_REFERENCES this is not available yet.

Once declaring variables as references is supported it can be made to work for foreach as well.

Miss said:
is a copy being made for each value returned?

Yes, the array value is assigned to a local variable, which can then be used in the expressions within the statement block.

Miss said:
Or maybe even omitting the type altogether (implying auto), but that may be a bit too much

Yes, I was actually thinking of supporting this. But it will be for a future enhancement. First I want to make sure the basic works before expanding further. 🙂

Miss said:
Constness seems to be ignored for these operators:

Yes, the const overloads is something I haven't tested yet. I'll try to have that done as soon as I can.

Thanks for sharing the changes for script array add-on. I'll include this.

Also thanks informing about the syntax highlighter. I'll add a link to it on the site.

I'll be working on the dictionary add-on. I haven't decided yet on how it will be implemented.

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've added the support for const overloads now.

https://sourceforge.net/p/angelscript/code/2978/

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've now implemented the support for foreach in the script array add-on.

https://sourceforge.net/p/angelscript/code/2980/

Next I'll probably work on the foreach support for the dictionary.

--

I also gave some thought on the ability to declare the value by reference. I think I can make this work with a slightly different implementation, than an actual reference that isn't safe.

foreach( auto &v : arr )
  v = v + 1;

this would be equivalent to:

for( uint iter = arr.opForBegin(); !arr.opForEnd(iter); iter = arr.opForNext(iter) )
  arr.opForValue0(iter) = arr.opForValue0(iter) + 1;

So, in this case the value wouldn't be copied to a local variable. Instead, each time the value is used in an expression it will be replaced with arr.opForValue.

Maybe I'll try this out still for 2.38.0 WIP.

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

This topic is closed to new replies.

Advertisement