// PoVRay 3.8 include File " oocore.inc" // author: Bruno Cabasson // date: Jan 2022 // // PROOF : Pov Ray Object Oriented Features. Core features. // // Dictionaries allow the following possibilities : // . implement data structures such as lists, satcks, intervals. // . store object-oriented stuff, used by SLOPE's mechanisms implemented // through builtin macros and data structures descibed in this file. // . SLOPE object-oriented mechanics include : // - templates // - code injection : write a file with POV-Sdl syntax and #include it. To be used as less as possible : only for // - syntactic features and constraints, such as : // registration of a class and its methods as part of the new_Xxx() constructor macros // invoking methods // macro name mangling for methods and constructors // overloading of methods and _constructors (through mangling) // // Object-oriented primitives and reserved keywords // class() // describe() // new() // this // dump() // call() // super() // #ifndef (PROOF_OOCORE_INC) #declare PROOF_OOCORE_INC = version; #version 3.8; #include "primitives.inc" #include "collections.inc" #macro _merge_properties(parent, properties) #local ret = array {} // Python's equivalent of plist = [p[0] for p in parent.properties], in order to use "exists()" primitive. #local property_list = list("property_list", TYPES._string_,) #for (i, 0, dimension_size(parent.properties, 1) - 1) push(property_list, parent.properties[i][0]) #end //ldump(property_list,) // Perform actual copy. #local cur = 0; #for (i, 0, dimension_size(properties, 1) - 1) #if (exists(property_list, properties[i][0])) #debug concat("Class \"", classname, "\" redefines property \"", properties[i][0], "\". New definition ignored.\n") #else #local ret[cur] = properties[i]; #local cur = cur + 1; #end #end ret #end #macro _merge_methods(parent, methods) #local ret = array {} // Python's equivalent of plist = [m for m in parent.methods], in order to use "exists()" primitive. #local method_list = list("method_list", TYPES._string_, ) #for (i, 0, dimension_size(parent.methods, 1) - 1) push(method_list, parent.methods[i]) #end //ldump(method_list,) // Perform actual copy. #local cur = 0; #for (i, 0, dimension_size(methods, 1) - 1) #if (exists(method_list, methods[i])) #debug concat("Class \"", classname, "\" redefines method \"", methods[i], "\". New definition ignored.\n") #else #local ret[cur] = methods[i]; #local cur = cur + 1; #end #end ret #end // Returns dictionary (class dictionary) containing its name and the name of the methods of this class. // // Usage : // #declare MyClass = class // ( // "MyClass", // yes | no, // parent_class | None, // array // { // array mixed {"property1", TYPES._int_}, // array mixed {"property2", TYPES._string_}, // ... // }, // array // { // "method1", // "method2", // ... // } // ); // #macro class(classname, has_parent, optional parent, optional properties, optional methods) #local actual_parent = None; #if (has_parent) #ifdef (local.parent) #local actual_parent = parent; #else #error concat("Class \"", classname, "\" : parent class missing.") #end #end // Copy all non-duplicated properties (= that are not part of the parent class, if any) #ifdef (local.properties) #if (has_parent) #local actual_properties = _merge_properties(parent, properties) #else // No parent : take properties as-is. #local actual_properties = properties; #end #else #local actual_properties = array {} #end // Copy all non-duplicated methods (= that are not part of the parent class, if any) // TODO : Could be a separate "_copy_ethodss()" macro. #local actual_methods = array {} #ifdef (local.methods) #if (has_parent) #local actual_methods = _merge_methods(parent, methods) #else // No parent : take methods as-is. #local actual_methods = methods; #end #else #local actual_methods = array {} #end // Build and return class descriptor. dictionary { .classname : classname, .has_parent : has_parent, .parent : actual_parent, .properties : actual_properties, .methods : actual_methods, ._is_virtual : false }; #end // Convenience macro that outputs a structured descritpion of the class on the #debug channel. // Calls the describe() method ( = MyClass_describe() macro). #macro describe(class, optional indent_level) #ifndef(local.indent_level) #local indent_level = 0; #end #debug concat(indent[indent_level], "class ", class.classname, "\n") #debug concat(indent[indent_level], "{\n") #debug concat(indent[indent_level+1], ".classname : ", class.classname, "\n") #if (class.has_parent) #debug concat(indent[indent_level+1], ".parent : \n") describe(class.parent, indent_level+2) //#else //#debug concat(indent[indent_level+1], ".parent : None\n") #end #for (i, 0, dimension_size(class.properties, 1) - 1) #local cur = class.properties[i]; #local property_name = check(cur[0], TYPES._string_); #local property_type = check(cur[1], TYPES._int_); #if (property_type = TYPES._instance_) #local instance_class = cur[2]; #debug concat(indent[indent_level+1], ".", property_name, " : ", enum_name(TYPES, property_type)," of ", instance_class.classname, "\n") #else #debug concat(indent[indent_level+1], ".", property_name, " : ", enum_name(TYPES, property_type),"\n") #end #end #for (i, 0, dimension_size(class.methods, 1) - 1) #debug concat(indent[indent_level+1], ".", class.methods[i], "()\n") #end #debug concat(indent[indent_level], "}\n") #end // Method "dynamic linking" mechanism. // // Method mangling. // For the moment, mangling is simple : MyClass.method() ==> "MyClass_method" macro. // Overloading : the sgnature_number must be determined by the user. Cannot do otherwise. // Default methods, with signature_number = 0, ie MyClass_method() macros, // are mandatory. This is what is linked in the instance structure. Then, the call() operator // appends a suffix containing the signature number, eg MyClass_method_1(). // This also means that, untill now, ALL version of the same mathod must belong to the same class, // not to an ancestor nor to a descendent. // // Convention for signature_number : 0 --> default version of methods, especially default constructors. // != 0 --> other signatures // #macro _method_mangle(class, method_name) concat(class.classname, "_", method_name) #end // Returns true if the given class defines the method. #macro _class_has_defined_method(class, method_name) //#debug "==> _class_has_defined_method()\n" #local ret = false; #local method_macro_name = _method_mangle(class, method_name); #ifdef (global[method_macro_name]) #local ret = true; #end //#debug "<== _class_has_defined_method()\n" ret #end // Lookup in the class hierachy whether the given method is defined. #macro _method_ancestor(class, method_name) //#debug "==> _method_ancestor()\n" #local cur_class = class; #local last = false; #local found = false; #while (!found & !last) //#debug concat("_method_ancestor(): checking for method ", method_name, " in ", cur_class.classname, "\n") #local found = _class_has_defined_method(cur_class, method_name); #if (found) #local ret = cur_class; #end #local last = !cur_class.has_parent; #if (!last) #local cur_class = cur_class.parent; #end #end #if (!found) #error concat("Method \"", method_name, "()\" is not defined in ", class.classname, "'s class hierarchy.") #end //#debug "<== _method_ancestor()\n" ret #end // Recursive "dynamic linking" of methods in an instance structure (dictionary). #macro _link_methods(this, class) //#debug concat("==> _link_methods(this, ", class.classname, ")\n") #if (class.has_parent) _link_methods(this, class.parent) #end #local method_names = class.methods; #for (i, 0, dimension_size(method_names, 1) - 1) #local cur_name = method_names[i]; #local class_for_method = _method_ancestor(this.class, cur_name); #local this[cur_name] = _method_mangle(class_for_method, cur_name); //#debug concat("link(): linked this.", cur_name, "() to ", this[cur_name], "\n") #end //#debug concat("<== _link_methods(", class.classname, ")\n") #end // Recursive "dynamic linking" for properties. #macro _link_properties(this, class) //#debug concat("==> _link_properties(this, ", class.classname, ")\n") #if (class.has_parent) _link_properties(this, class.parent) #end #local properties = class.properties; #for (i, 0, dimension_size(properties, 1) - 1) #local cur = properties[i]; #local property_name = check(cur[0], TYPES._string_); #local property_type = check(cur[1], TYPES._int_); #local this[property_name] = None; // The property is only created. Waiting for actual assignment by new() operator. #end //#debug concat("<== _link_properties(", class.classname, ")\n") #end // Constructor mangling is a bit different. I consider constructors are not really methods. // I make the distinction through mangling. // See new() operator below. #macro _constructor_mangle(class, signature_number) #local ret = ""; #if (signature_number = 0) //#debug "signature_number = 0\n" #local ret = concat("new_", class.classname); #else //#debug "signature_number != 0\n" #local ret = concat("new_", str(signature_number, 0, 0), "_", class.classname); #end ret #end // Invoke the appropriate constructor. // Calling constructors and methods is through code injection by exec(), // which is equivalent to Parse_String(). #macro _constructor(this, signature_number, optional params_dict) #local _constructor_macro_name = _constructor_mangle(this.class, signature_number); #ifdef (local.params_dict) #local code = concat(_constructor_macro_name, "(this, params_dict)"); #else #local code = concat(_constructor_macro_name, "(this,)"); #end exec(code) #end // new() operator. // // By convention, constructor #0 is the default constructor, and constructor #1 is the copy constructor. // // As overloading of macros is absolutely not handled in POV-sdl, the programmer has to specify explicitly // which version of the constructor (or method) he(she) wants to invoke. See call() operator below. // // new(MyClass,, params_dict), new(MyClass, 0, params_dict) --> new_MyClass(this,,), default constructor, with or without parameters in params_dict. // new(MyClass, 1, params_dict) --> new_1_MyClass(this, ), copy constructor. #macro new(class, optional signature_number, optional params_dict) #if (class._is_virtual) #error concat("Class \"", class.classname, "\" is virtual and cannot be instanciated.") #end #local this = dictionary; #local this.class = class; _link_properties(this, class) _link_methods(this, class) #ifdef (local.signature_number) #if (signature_number < 0) #error concat("Class \"", class.classname, "\" negative .") #end #ifdef (local.params_dict) _constructor(this, signature_number, params_dict) #else _constructor(this, signature_number,) #end #else #ifdef (local.params_dict) _constructor(this, 0, params_dict) #else _constructor(this, 0,) #end #end this #end // Mangling for dump() methods. // dump(my_class_instance,) --> MyClass_dump(this,) #macro _dump_mangle(this) concat(this.class.classname, "_dump") #end // Convenience macro to invoke the dump() method that outputs the contents of an instance in the #debug channel. // Calling constructors and methods is through code injection by exec(), // which is equivalent to Parse_String(). #macro dump(this, optional indent_level) #ifdef (local.indent_level) #local code = concat(_dump_mangle(this), "(this, ", str(int(indent_level), 0, 0), ")") #else #local code = concat(_dump_mangle(this), "(this, 0)") #end exec(code) #end // Invoke a method. // // As overloading of macros is absolutely not handled in POV-sdl, the programmer has to specify explicitly // which version of the method he(she) wants to invoke. See new() operator above. // // Calling constructors and methods is through code injection by exec(), // which is equivalent to Parse_String(). // // The parameter 'method_macro_name' is valuated by using the correspondig method name within the // instance, accessed by bot notation. // // Usage : // call(my_instance.method,, my_instance,) // call(my_instance.method, 0, my_instance,) // // call(my_instance.method, 1, my_instance, dictionary {.param1 : 0, .param2 : "text"}) // #macro call(method_macro_name, optional signature_number, this, optional params_dict) #ifdef (local.signature_number) #if (signature_number = 0) #ifdef (local.params_dict) #local code = concat(method_macro_name, "(this, params_dict)"); #else #local code = concat(method_macro_name, "(this,)"); #end #else #ifdef (local.params_dict) #local code = concat(method_macro_name, "_", str(signature_number, 0, 0),"(this, params_dict)"); #else #error concat(method_macro_name, " : parameters are mandatory for overloaded methods.") #end #end #else // assume signature_number = 0 #ifdef (local.params_dict) #local code = concat(method_macro_name, "(this, params_dict)"); #else #local code = concat(method_macro_name, "(this,)"); #end #end exec(code) #end // Invoke the parent's constructor. #macro super(this, optional signature_number, optional params_dict) #ifdef (local.signature_number) #local _constructor_macro_name = _constructor_mangle(this.class.parent, signature_number); #else #local _constructor_macro_name = _constructor_mangle(this.class.parent, 0); #end #local actual_class = this.class; #local this.class = this.class.parent; // consider this as an instance of its parent class #ifdef (local.params_dict) #local code = concat(_constructor_macro_name, "(this, params_dict)"); #else #local code = concat(_constructor_macro_name, "(this,)"); #end exec(code) #local this.class = actual_class; // back to actual class #end // Lookup in the class hierarchy to find if 'this' is an instance of some class in that hierachy. #macro instanceof(this, class) //#debug "==> instanceof()\n" #local ret = false; #local cur_class = this.class; #local last = false; #local found = false; #while (!found & !last) //#debug concat("instanceof(): checking for class ", class.classname, " for ", cur_class.classname, "\n") #if (cur_class.classname = class.classname) #local found = true; #local ret = true; #end #local last = !cur_class.has_parent; #if (!last) #local cur_class = cur_class.parent; #end #end //#debug "<== instanceof()\n" ret #end // Alias for instanceof() #macro isinstance(this, class) instanceof(this, class) #end // A class set as virtual cannot be instanciated. See new() operator. #macro setvirtual(class) #local class.is_virtual = true; #end #version PROOF_OOCORE_INC; #end