// PoVRay 3.8 include File " collections.inc" // author: Bruno Cabasson // date: Jan 2022 // // PROOF : collections. #ifndef (PROOF_COLLECTIONS_INC) #declare PROOF_COLLECTIONS_INC = version; #version 3.8; #include "primitives.inc" #declare COLLECTION_TYPES = dictionary { .list : 0, .listmixed : 1, ._names : array {"list", "listmixed"} } // + -----------------------+ // | Native-array utilities | // + -----------------------+ #macro extend(arr, extension_arr) // We cannot know whether an array is "array" or "array mixed". // So we let the parser generate an error if elements are of incompatible types. #local start = dimension_size(arr, 1); #for (i, 0, dimension_size(extension_arr, 1) - 1) #local arr[start + i] = extension_arr[i]; #end #end #macro _dump_array(arr, name, elt_type, indent_level) #debug concat(indent[indent_level], name, " = array\n") #debug concat(indent[indent_level], "{\n") #for (i, 0, dimension_size(arr, 1) - 1) #debug concat(indent[indent_level+1], "[", str(i, 0, 0), "] = ", tostring(arr[i], elt_type), "\n") #end #debug concat(indent[indent_level], "}\n") #end #macro dump_array(arr, name, elt_type, optional indent_level) #ifdef (local.indent_level) _dump_array(arr, name, elt_type, indent_level) #else _dump_array(arr, name, elt_type, 0) #end #end // + --------------------------------+ // | Dictionaries as lists or stacks | // + --------------------------------+ #macro list(name, optional type_, optional entries_array) #local actual_type = TYPES._int_; #ifdef (local.type_) #local actual_type = type_; #end #local entries = array {}; #local nb_entries = 0; #ifdef (local.entries_array) #local nb_entries = dimension_size(entries_array, 1); #for (i, 0, nb_entries - 1) #local entries[i] = check(entries_array[i], actual_type); #end #end dictionary { .collection_type : COLLECTION_TYPES.list, .name : name, .elt_type : actual_type, // Data stored in an array. All entries of the same type. // Reminder : Arrays can only grow. Thus, actual size is controlled with nb_entries. .nb_entries : nb_entries, .entries : entries } #end #macro mlist(name, optional entries_array) #local entries = array mixed {}; #local nb_entries = 0; #ifdef (local.entries_array) #local nb_entries = dimension_size(entries_array, 1); #for (i, 0, nb_entries - 1) #local entries[i] = entries_array[i]; #end #end dictionary { .collection_type : COLLECTION_TYPES.listmixed, .name : name, // Data stored in an array mixed. Cannot dump since type of elt may vary. // Reminder : Arrays can only grow. Thus, actual size is controlled with nb_entries. .nb_entries : nb_entries, .entries : entries } #end #macro _ldump(this, indent_level) #if (this.collection_type != COLLECTION_TYPES.list) #error concat("List \"", this.name, "\" is of type \"", enum_name(COLLECTION_TYPES, this.collection_type), "\". Only \"", enum_name(COLLECTION_TYPES, COLLECTION_TYPES.list), "\" can be dumped.") #end #local elt_type = this.elt_type; #debug concat(indent[indent_level], this.name, " = list\n") #debug concat(indent[indent_level], "{\n") #for (i, 0, this.nb_entries - 1) #debug concat(indent[indent_level+1], "[", str(i, 0, 0), "] = ", tostring(this.entries[i], elt_type), "\n") #end #debug concat(indent[indent_level], "}\n") #end #macro ldump(this, optional indent_level) #ifdef (local.indent_level) _ldump(this, indent_level) #else _ldump(this, 0) #end #end #macro push(this, value) #if (this.collection_type = COLLECTION_TYPES.list) #local this.entries[this.nb_entries] = check(value, this.elt_type); #else #local this.entries[this.nb_entries] = value; // no check for array mixed. #end #local this.nb_entries = this.nb_entries + 1; #end #macro pop(this) #if (this.nb_entries = 0) #error concat("List \"", this.name, "\" : list is empty, cannot pop().") #end #local _ret = this.entries[this.nb_entries -1 ]; #undef this.entries[this.nb_entries - 1] // WARNING : the array keeps its size. The top element is just #undef'ed. #local this.nb_entries = this.nb_entries - 1; _ret #end #macro lextend(this, ext) // Check we deal with lists ou listmixed. #if (this.collection_type != COLLECTION_TYPES.list & this.collection_type != COLLECTION_TYPES.listmixed) #error concat("lextend(): wrong collection type for \"", this.name, "\". Expected list or listmixed.") #end #if (ext.collection_type != COLLECTION_TYPES.list & ext.collection_type != COLLECTION_TYPES.listmixed) #error concat("lextend(): wrong collection type for extension \"", ext.name, "\". Expected list or listmixed.") #end // Check for compatibility : // lists can only be extended by lists of the same type. // listmixed can be extened by either lists of any type or mlists. #if (this.collection_type = COLLECTION_TYPES.list) #if (ext.collection_type != COLLECTION_TYPES.list) #error concat("lextend(): cannot extend list \"", this.name"\" with \"", ext.name, "\" : incompatible collection types.") #else #if (this.elt_type != ext.elt_type) #error concat("lextend(): cannot extend list \"", this.name"\" with \"", ext.name, "\" : incompatible elements* types.") #end #end #end // Cannot use "extend()" : top entries of arrays may have been #undef'd by pop(), but dimension_size() would not change. #local start = this.nb_entries; #if (this.collection_type = COLLECTION_TYPES.list) #for (i, 0, ext.nb_entries - 1) #local this.entries[start + i] = check(ext.entries[i], this.elt_type); #end #else #for (i, 0, ext.nb_entries - 1) #local this.entries[start + i] = ext.entries[i]; #end #end #local this.nb_entries = this.nb_entries + ext.nb_entries; #end #macro get(this, i) #local actual_i = int(i); #if (actual_i < 0 | actual_i >= this.nb_entries) #error concat("List \"", this.name, "\" : index out of bounds (", str(actual_i, 0, 0), ").") #end this.entries[actual_i] #end /* Basic equivalent to Python's 'map()' primitive. A macro named my_map_macro() should be defined prior to calling 'map()'. map() applies my_map_macro() on all lists elements, with parameters passed trough a dictionary. Use following template : #ifdef (my_map_macro) #undef my_map_macro #end #macro my_map_macro(my_list_el, my_param_dict, i, is_first, is_last) // Whatever processing of my_list_el, with parameters defined in my_param_dict. // Parameter 'i' is the index of the element in the list. // Parameter 'is_first' is true for first element. // Parameter 'is_last' is true for last element. #end */ #macro map(this, optional param_dict) #ifdef (local.param_dict) #for (i, 0, this.nb_entries-1) my_map_macro(this.entries[i], i, (i=0), (i=this.nb_entries-1), param_dict) #end #else #for (i, 0, this.nb_entries-1) my_map_macro(this.entries[i], i, (i=0), (i=this.nb_entries-1),) #end #end #end // Basic equivalent to Python's 'in' operator. // Suitable only for lists of homogeneous values that POV can compare : integers, floats, vectors, strings. #macro exists(this, value) #if (this.collection_type != COLLECTION_TYPES.list) #error concat("List \"", this.name, "\" is of type \"", enum_name(COLLECTION_TYPES, this.collection_type), "\". Only \"", enum_name(COLLECTION_TYPES, COLLECTION_TYPES.list), "\" can be addressed by exists().") #end #local _ret = false; #for (i, 0, this.nb_entries-1) #if (this.entries[i] = value) #local _ret = true; #break #end #end _ret #end // Basic equivalent to Python's list syntax used for slicing (e.g. mylist[start:end]). // Could not use 'slice' as it is a SDL keyword. // Returns in 'dest_dict' a list with same characteristics containing elements of the slice defined // by 'start' and 'end_' parameters inclusively. // Like python, negative values for 'start' and 'end_' mean "that much before the end of the input list", // and the elements of the returned list are stored in the direction implied by the values of the // actual indexes implied by 'start' and 'end_', ie foreward or backward. #macro chunk(src_list, start, end_) #if (src_list.collection_type != COLLECTION_TYPES.list) #error concat("List \"", src_list.name, "\" is of type \"", enum_name(COLLECTION_TYPES, src_list.collection_type), "\". Only \"", enum_name(COLLECTION_TYPES, COLLECTION_TYPES.list), "\" can be addressed by chunk().") #end #local start_name = str(start, 0, 0); #local end_name = str(end_, 0, 0); #local actual_start = int(start); #if (start = eol) #local actual_start = 0; #local start_name = ""; #elseif (start < 0) #local actual_start = src_list.nb_entries - abs(start); #end #local actual_end = int(end_); #if (end_ = eol) #local actual_end = src_list.nb_entries - 1; #local end_name = ""; #elseif (end_ < 0) #local actual_end = src_list.nb_entries - abs(int(end_)); #end // Check actual boundaries #if (actual_start < 0) #error concat("chunk(), negative start index : ", str(actual_start, 0, 0)) #end #if (actual_end > src_list.nb_entries - 1) #error concat("chunk(), end index out of bounds : ", str(actual_end, 0, 0)) #end #local increment = 1; #if (actual_end < actual_start) #local increment = -1; #end // Build a List #local chunk_name = concat("chunk of '", src_list.name, "' [", start_name, ":", end_name, "]"); #local ret = list(chunk_name, src_list.elt_type,); // Copy entries #local current = 0; #for(i, actual_start, actual_end, increment) //#warning concat("current, i = ", str(current, 0, 0), ", ", str(i, 0, 0)) #local ret.entries[current] = src_list.entries[i]; #local ret.nb_entries = ret.nb_entries + 1; #local current = current + 1; #end ret #end #version PROOF_COLLECTIONS_INC; #end