GMLscripts.com

Discuss and collaborate on GML scripts
Invert

You are not logged in.

#1 2021-12-16 05:58:17

maras
Member
Registered: 2021-04-25
Posts: 28
Website

Foreach [v3.0.1]

A foreach loop for arrays, lists, maps, structs, grids, strings and number ranges.
This foreach was made using macros so you don't have to pass variables like arguments. You can access them inside of the loop directly.

Reserved keywords: foreach, foreach_rev, into, exec, as_list, as_grid, as_map, fe, fe_break, fe_continue, fe_return, global._FE_*

Changes

[v3.0.1] Renamed "invar" to "into"

[v3.0.0] A complete refractor
    managed to speed it up by about 40%
    no more warnings
    DATA and VAR had to SWITCH PLACES

Syntax

Expandforeach <data> into <var> exec

reversed loop:

Expandforeach_rev <data> into <var> exec

    <var> - an unused variable name to use
    <data> - any supported data
    Only data that dont use keys as indexes can be reversed

DS datatypes require a specification:

Expandforeach <some_ds_map> as_map into v exec
foreach <some_ds_list> as_list into v exec
foreach <some_ds_grid> as_grid into v exec

The macro variable "fe" contains these variables of the current loop body:

    fe.i - for array, list, grid, number range, string
    fe.key - for map, struct
    fe.xpos, fe.ypos - for grid
    fe.set(val) - for anything but string and number range since those aren't references

To return from a function from within the loop use fe_return(val, [depth=1]);
To break the loop use fe_break;
To continue the loop use fe_continue;

While using fe_return you need to pay attention how "deep" the return is. If fe_return is called in a nested fe loop you have to specify how many fe loops to break using the depth parameter otherwise the stack doesn't get cleared correctly and it will cause unpredictable behaviour. (tbh I wouldn't return from within fe loops at all)

EXAMPLES HERE:
https://github.com/mh-cz/GameMaker-Foreach

Expand/// Foreach 3.0.1
//
// foreach <data> into <var> exec
// 
//     <var> - variable
//     <data> - any supported data
//     
/// GMLscripts.com/license

#region GLOBAL

global._FE_STACK = array_create(10, undefined);
global._FE_CURR_STACK_INDEX = -1;
global._FE = undefined;

#endregion

#region MACROS

#macro fe global._FE
#macro fe_break fe.fn = fe.end_loop; break
#macro fe_continue break
#macro fe_return return _FE_RETURN

#macro as_list ,undefined,ds_type_list
#macro as_map ,undefined,ds_type_map
#macro as_grid ,undefined,ds_type_grid

#macro foreach \
	for(_FE_AUTO_(

#macro foreach_rev \
	for(_FE_AUTO_REV_(

#macro into \
	); fe.fn(); ) for(var 

#macro exec \
	 = fe.get(); true; break)

#endregion

#region RETURN

function _FE_RETURN(val, _depth = 1) {
	repeat(_depth) fe.end_loop();
	return val;
}

#endregion

#region AUTO

function _FE_AUTO_(a1, a2 = undefined, a3 = undefined) {
	
	if a2 == undefined and a3 != undefined switch(a3) {
		case ds_type_list: fe = new _FE_LIST_(a1); break;
		case ds_type_map: fe = new _FE_MAP_(a1); break;
		case ds_type_grid: fe = new _FE_GRID_(a1); break;
		default: throw "Unsupported DS type";
	}
	else if is_array(a1) fe = new _FE_ARRAY_(a1);
	else if is_string(a1) fe = new _FE_STRING_(a1);
	else if is_struct(a1) fe = new _FE_STRUCT_(a1);
	else if is_numeric(a1) {
		if a2 == undefined fe = new _FE_RANGE_(0, a1, 1);
		else if a3 == undefined fe = new _FE_RANGE_(a1, a2, 1)
		else fe = new _FE_RANGE_(a1, a2, a3)
	}
	else throw "Unsupported type";
	
	global._FE_STACK[@ ++global._FE_CURR_STACK_INDEX] = fe;
}

function _FE_AUTO_REV_(a1, a2 = undefined, a3 = undefined) {
	
	if a2 == undefined and a3 != undefined switch(a3) {
		case ds_type_list: fe = new _FE_LIST_REV_(a1); break;
		case ds_type_map: fe = new _FE_MAP_(a1); break;
		case ds_type_grid: fe = new _FE_GRID_REV_(a1); break;
		default: throw "Unsupported DS type";
	}
	else if is_array(a1) fe = new _FE_ARRAY_REV_(a1);
	else if is_string(a1) fe = new _FE_STRING_REV_(a1);
	else if is_struct(a1) fe = new _FE_STRUCT_(a1);
	else if is_numeric(a1) {
		if a2 == undefined fe = new _FE_RANGE_(a1, 0, 1);
		else if a3 == undefined fe = new _FE_RANGE_(a2, a1, 1)
		else fe = new _FE_RANGE_(a2, a1, a3)
	}
	else throw "Unsupported type";
	
	global._FE_STACK[@ ++global._FE_CURR_STACK_INDEX] = fe;
}

#endregion

#region ARRAY

function _FE_ARRAY_() constructor {

	data = argument0;
	len = array_length(data);
	i = -1;

	static set = function(v) {
		data[@ i] = v;
	}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		return data[@ i];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

function _FE_ARRAY_REV_() constructor {

	data = argument0;
	len = array_length(data);
	i = len;

	static set = function(v) {
		data[@ i] = v;
	}

	static next = function() {
		return --i > -1 ? true : end_loop();
	}
	
	static get = function() {
		return data[@ i];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region STRING

function _FE_STRING_() constructor {

	data = argument0;
	len = string_length(data);
	i = -1;

	static set = function(v) {}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		return string_char_at(data, i+1);
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

function _FE_STRING_REV_() constructor {

	data = argument0;
	len = string_length(data);
	i = len;

	static set = function(v) {}

	static next = function() {
		return --i > -1 ? true : end_loop();
	}
	
	static get = function() {
		return string_char_at(data, i+1);
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region LIST

function _FE_LIST_() constructor {

	data = argument0;
	len = ds_list_size(data);
	i = -1;

	static set = function(v) {
		data[| i] = v;
	}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		return data[| i];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

function _FE_LIST_REV_() constructor {

	data = argument0;
	len = ds_list_size(data);
	i = len;

	static set = function(v) {
		data[| i] = v;
	}

	static next = function() {
		return --i > -1 ? true : end_loop();
	}
	
	static get = function() {
		return data[| i];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region STRUCT

function _FE_STRUCT_() constructor {

	data = argument0;
	key = "";
	keys = variable_struct_get_names(data);
	len = array_length(keys);
	i = -1;

	static set = function(v) {
		data[$ i] = v;
	}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		key = keys[@ i];
		return data[$ key];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region MAP

function _FE_MAP_() constructor {

	data = argument0;
	key = "";
	keys = ds_map_keys_to_array(data);
	len = array_length(keys);
	i = -1;

	static set = function(v) {
		data[? key] = v;
	}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		key = keys[@ i];
		return data[? key];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region GRID

function _FE_GRID_() constructor {

	data = argument0;
	xpos = 0;
	ypos = 0;
	w = ds_grid_width(data);
	h = ds_grid_height(data);
	len = w * h;
	i = -1;

	static set = function(v) {
		data[# xpos, ypos] = v;
	}

	static next = function() {
		return ++i < len ? true : end_loop();
	}
	
	static get = function() {
		xpos = i mod w;
		ypos = i div h;
		return data[# xpos, ypos];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

function _FE_GRID_REV_() constructor {

	data = argument0;
	xpos = 0;
	ypos = 0;
	w = ds_grid_width(data);
	h = ds_grid_height(data);
	len = w * h;
	i = len;

	static set = function(v) {
		data[# xpos, ypos] = v;
	}

	static next = function() {
		return --i > -1 ? true : end_loop();
	}
	
	static get = function() {
		xpos = i mod w;
		ypos = i div h;
		return data[# xpos, ypos];
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

#region RANGE

function _FE_RANGE_() constructor {

	from = argument0;
	to = argument1;
	step = abs(argument2) * sign(to - from);
	i = from - step;
	
	static set = function(v) {}

	static next = function() {
		i += step;
		return (from < to and i <= to) or (from > to and i >= to) ? true : end_loop();
	}
	
	static get = function() {
		return i;
	}
	
	static end_loop = function() {
		global._FE = --global._FE_CURR_STACK_INDEX != -1 ? global._FE_STACK[@ global._FE_CURR_STACK_INDEX] : undefined;
		return false;
	}
	
	fn = next;
}

#endregion

Last edited by maras (2024-03-07 08:46:10)


I'm on the official GM discord > maras_cz

Offline

#2 2022-01-10 16:35:20

xot
Administrator
Registered: 2007-08-18
Posts: 1,239

Re: Foreach [v3.0.1]

Whoa! I'll have to think about this.


Abusing forum power since 1986.

Offline

#3 2022-06-27 04:37:37

gnysek
Member
Registered: 2011-12-29
Posts: 36

Re: Foreach [v3.0.1]

Main issue with this script is that it uses macros to make some tricks with GML, and replace things on compiling game. While this is OK for very advanced programmers, I think it's not a style that should be promoted for clean code.

Offline

#4 2022-07-24 03:10:37

maras
Member
Registered: 2021-04-25
Posts: 28
Website

Re: Foreach [v3.0.1]

gnysek wrote:

Main issue with this script is that it uses macros to make some tricks with GML, and replace things on compiling game. While this is OK for very advanced programmers, I think it's not a style that should be promoted for clean code.

Well it was never really meant to be clean. The main goal was to make it look simple, quick to type and being able to access variables without passing them as arguments first. Dirty macros were the only thing that came to mind

Last edited by maras (2022-07-25 10:18:44)


I'm on the official GM discord > maras_cz

Offline

Board footer

Powered by FluxBB