Update documentation of operators

This commit is contained in:
Patrick Pelissier committed 2021-03-27 21:15:03 +01:00
1 parent 8433fc178c
commit 52e745f905
1 file changed
+49 -47
+49 -47
View File
@@ -477,13 +477,13 @@ means of the calls.
An oplist has no real form from a C language point of view. It is just an abstraction
that disappears after the macro expansion step of the preprocessing.
For each object / container, an oplist is needed and the following operators are
For each object / container, an oplist is needed. The following operators are
usually expected for an object:
* INIT constructor(obj): initialize the object 'obj' into a valid state.
* INIT\_SET constructor(obj, org): initialize the object 'obj' into the same state as the object 'org'.
* SET operator(obj, org): set the initialized object 'obj' into the same state as the initialized object org.
* CLEAR destructor(obj): destroy the initialized object 'obj', releasing any attached memory. This method shall never fail.
* INIT(obj): initialize the object 'obj' into a valid state (constructor).
* INIT\_SET(obj, org): initialize the object 'obj' into the same state as the object 'org' (constructor).
* SET(obj, org): set the initialized object 'obj' into the same state as the initialized object org (operator).
* CLEAR(obj): destroy the initialized object 'obj', releasing any attached memory (destructor). This method shall never fail.
INIT, INIT\_SET & SET methods shall only fail due to ***memory errors***.
@@ -506,68 +506,70 @@ Other documented operators are:
* NAME() --> prefix: Return the base name (prefix) used to construct the container.
* TYPE() --> type: Return the base type associated to this oplist.
* SUBTYPE() --> type: Return the type of the element stored in the container.
* SUBTYPE() --> type: Return the type of the element stored in the container (used to iterate over the container).
* OPLIST() --> oplist: Return the oplist of the type of the elements stored in the container.
* KEY\_TYPE() --> key\_t: Return the type of key for associative containers.
* VALUE\_TYPE() --> value\_t: Return the type of value for associative containers.
* KEY\_OPLIST() --> oplist: Return the oplist of the key for associative containers.
* VALUE\_OPLIST() --> oplist: Return the oplist of the value for associative containers.
* NEW (type) -> type pointer: allocate a new object (with suitable alignment and size) and return a pointer to it. The returned object is **not initialized** (INIT operator shall be called afterward). The default method is M\_MEMORY\_ALLOC (that allocates from the heap). It returns NULL in case of failure.
* DEL (&obj): free the allocated uninitialized object 'obj'. The object is not cleared before being free (CLEAR operator shall be called before). The object shall have been allocated by the associated NEW method. The default method is M\_MEMORY\_DEL (that frees to the heap).
* REALLOC(type, type pointer, number) --> type pointer: realloc the given array referenced by type pointer (either a NULL pointer or a pointer returned by the associated REALLOC method itself) to an array of the number of objects of this type and return a pointer to this new array. Previously objects pointed by the pointer are kept up to the minimum of the new size and old one. New objects are not initialized (INIT operator shall be called afterward). Freed objects are not cleared (CLEAR operator shall be called before). The default is M\_MEMORY\_REALLOC (that allocates from the heap). It returns NULL in case of failure in which case the original array is not modified.
* KEY\_TYPE() --> key\_t: Return the key type for associative containers.
* VALUE\_TYPE() --> value\_t: Return the value type for associative containers.
* KEY\_OPLIST() --> oplist: Return the oplist of the key type for associative containers.
* VALUE\_OPLIST() --> oplist: Return the oplist of the value type for associative containers.
* NEW (type) -> type pointer: allocate a new object (with suitable alignment and size) and return a pointer to it. The returned object is **not initialized** (a constructor operator shall be called afterward). The default method is M\_MEMORY\_ALLOC (that allocates from the heap). It returns NULL in case of failure.
* DEL (&obj): free the allocated uninitialized object 'obj'. The object is not cleared before being free (A destructor operator shall be called before). The object shall have been allocated by the associated NEW method. The default method is M\_MEMORY\_DEL (that frees to the heap).
* REALLOC(type, type pointer, number) --> type pointer: realloc the given array referenced by type pointer (either a NULL pointer or a pointer returned by the associated REALLOC method itself) to an array of the number of objects of this type and return a pointer to this new array. Previously objects pointed by the pointer are kept up to the minimum of the new size and old one. New objects are not initialized (a constructor operator shall be called afterward). Freed objects are not cleared (A destructor operator shall be called before). The default is M\_MEMORY\_REALLOC (that allocates from the heap). It returns NULL in case of failure in which case the original array is not modified.
* FREE (&obj) : free the allocated uninitialized array object 'obj'. The objects are not cleared before being free (CLEAR operator has to be called before). The object shall have been allocated by the associated REALLOC method. The default is M\_MEMORY\_FREE (that frees to the heap).
* INC\_ALLOC(size\_t s) -> size\_t: Define the growing policy of an array (or equivalent structure). It returns a new allocation size based on the old allocation size ('s'). Default policy is to get the max between '2*s' and 16. If the returned value is lower than the old one, it is assumed that the size has overflowed.
* INIT\_MOVE(objd, objc): Initialize 'objd' to the same state than 'objc' by stealing as much resources as possible from 'objc', and then clear 'objc'. It is semantically equivalent to calling INIT\_SET(objd,objc) then CLEAR(objc) but is usually way faster. By default, all objects are assumed to be **trivially movable** (i.e. using memcpy to move an object is safe). Most C objects (even complex structure) are trivially movable. A notable exception are (in general) intrusive objects. If an object is not trivially movable, it shall provide an INIT\_MOVE method or disable the INIT\_MOVE method entirely. An INIT\_MOVE operator cannot fail due to memory failure.
* MOVE(objd, objc): Set 'objd' to the same state than 'objc' by stealing as resources as possible from 'objc' and then clear 'objc'. It is equivalent to calling SET(objd,objc) then CLEAR(objc) or CLEAR(objd) and then INIT\_MOVE(objd, objc). TBC if operator is really needed as calling CLEAR then INIT\_MOVE is what do all known implementation. A MOVE operator cannot fail due to memory failure.
* INIT\_WITH(obj, ...): Initialize the object 'obj' with a variable set of arguments. Arguments can be of different types and is up to the method to decide how to initialize the object based on this set. This is used in the M\_LET macro to initialize objects with the given values.
* INC\_ALLOC(size\_t s) -> size\_t: Define the growing policy of an array (or equivalent structure). It returns a new allocation size based on the old allocation size ('s'). Default policy is to get the maximum between '2*s' and 16. NOTE: It doesn't check for overflow: if the returned value is lower than the old one, the user shall raise an overflow error.
* INIT\_MOVE(objd, objc): Initialize 'objd' to the same state than 'objc' by stealing as much resources as possible from 'objc', and then clear 'objc' (constructor of objd + destructor of objc). It is semantically equivalent to calling INIT\_SET(objd,objc) then CLEAR(objc) but is usually way faster. By default, all objects are assumed to be **trivially movable** (i.e. using memcpy to move an object is safe). Most C objects (even complex structure) are trivially movable. A notable exception are intrusive objects. If an object is not trivially movable, it shall provide an INIT\_MOVE method or disable the INIT\_MOVE method entirely (NOTE: Some containers always assume that the objects are trivially movable). An INIT\_MOVE operator shall not fail.
* MOVE(objd, objc): Set 'objd' to the same state than 'objc' by stealing as resources as possible from 'objc' and then clear 'objc' (destructor of 'objc'). It is equivalent to calling SET(objd,objc) then CLEAR(objc) or CLEAR(objd) and then INIT\_MOVE(objd, objc). TBC if this operator is really needed as calling CLEAR then INIT\_MOVE is what do all known implementation, and is efficient. A MOVE operator shall not fail.
* INIT\_WITH(obj, ...): Initialize the object 'obj' with the given variable set of arguments (constructor). Arguments can be of different types. It is up to the method of the objet to decide how to initialize the object based on this initialization array. This operator is used in the M\_LET macro to initialize objects with their given values.
* SWAP(objd, objc): Swap the states of the object 'objc' and the object 'objd'.
* CLEAN(obj): Empty the container from all its objects. Nearly like CLEAR except that the container 'obj' remains initialized (but empty).
* EMPTY\_P(obj) --> bool: Test if the object is empty (true) or not.
* GET\_SIZE (container) --> size\_t: Return the number of elements in the container.
* EMPTY\_P(obj) --> bool: Test if the container object is empty (true) or not.
* GET\_SIZE (container) --> size\_t: Return the number of elements in the container object.
* HASH (obj) --> size\_t: return a hash of the object (not a secure hash but one that is usable for a hash table). Default is performing a hash of the memory representation of the object. This default implementation is invalid if the object holds pointer to other objects.
* EQUAL(obj1, obj2) --> bool: Compare the object for equality. return true if both objects are equal, false otherwise. Default is using the C comparison operator. The method may be called with OOR object for the Open Addressing dictionary (in which case it shall return false).
* CMP(obj1, obj2) --> int: Provide a complete order the objects. return a negative integer if obj1 < obj2, 0 if obj1 = obj2, a positive integer otherwise. Default is C comparison operator.
* EQUAL(obj1, obj2) --> bool: Compare the two objects for equality. Return true if both objects are equal, false otherwise. Default is using the C comparison operator. 'obj1' may be an OOR object (Out of Representation) for the Open Addressing dictionary (see OOR\_* operators): in such cases, it shall return false.
* CMP(obj1, obj2) --> int: Provide a complete order the objects. return a negative integer if obj1 < obj2, 0 if obj1 = obj2, a positive integer otherwise. Default is C comparison operator. NOTE: The equivalence between EQUAL(a,b) and CMP(a,b)==0 is not required, but usually welcome.
* ADD(obj1, obj2, obj3) : Set obj1 to the sum of obj2 and obj3. Default is '+' C operator.
* SUB(obj1, obj2, obj3) : Set obj1 to the difference of obj2 and obj3. Default is '-' C operator.
* MUL(obj1, obj2, obj3) : Set obj1 to the product of obj2 and obj3. Default is '*' C operator.
* DIV(obj1, obj2, obj3) : Set obj1 to the division of obj2 and obj3. Default is '/' C operator.
* GET\_KEY (container, key) --> &obj: Return a pointer to the value object within the container associated to the key 'key' or return NULL if no object is associated to this key. The pointer to the value object remains valid until any modification of the container.
* SET\_KEY (container, key, object): Associate the key 'key' to the value object 'object' in the given container.
* GET\_SET\_KEY (container, key) --> &obj: return a pointer to the value object within the container associated to the key 'key' or create a new object in the container, associate it to the key 'key' (with default initialization) and return its pointer. The pointer to the object remains valid until any modification of the container. The returned pointer is therefore never NULL.
* ERASE\_KEY (container, key) --> bool: Erase the object associated to the key 'key' within the container. Return true if successful, false if the key is not found.
* PUSH(container, obj) : Push 'object' into 'container'. How and where it is pushed is container dependent.
* POP(&obj, container) : Pop an object from 'container' and save it in the initialized object '*obj' if obj is not NULL (giving back the ownership to the caller). Which object is popped is container dependent. The container shall have at least one object.
* PUSH\_MOVE(container, &obj) : Push and move the object '*obj' into 'container'. How it is pushed is container dependent. '*obj' is cleared afterward and shall not be used anymore.
* POP\_MOVE(&obj, container) : Pop an object from 'container' and **init & move** it in the unitialized object '*obj'. Which object is popped is container dependent. '*obj' shall be uninitialized. The container shall have at least one object.
* GET\_KEY (container, key) --> &value: Return a pointer to the value object within the container associated to the key 'key' or return NULL if no object is associated to this key. The pointer to the value object remains valid until any modification of the container.
* SET\_KEY (container, key, value): Associate the key object 'key' to the value object 'value' in the given container.
* GET\_SET\_KEY (container, key) --> &value: return a pointer to the value object within the container associated to the key 'key' if it exists, or create a new entry in the container and associate it to the key 'key' with the default initialization before returning its pointer. The pointer to the object remains valid until any modification of the container. The returned pointer is therefore never NULL.
* ERASE\_KEY (container, key) --> bool: Erase the object associated to the key 'key' within the container. Return true if successful, false if the key is not found (nothing is done).
* PUSH(obj, subobj) : Push 'subobj' (of type SUBTYPE() ) into the container 'obj'. How and where it is pushed is container dependent.
* POP(&subobj, obj) : Pop an object from the container 'obj' and save it in the object '*subobj' (of type SUBTYPE()) if subobj is not NULL (giving back the ownership of the sub object to the caller). Which object is popped is container dependent. The container shall have at least one object.
* PUSH\_MOVE(obj, &subobj) : Push and move the object '*subobj' (of type SUBTYPE()) into the container 'obj' (subobj destructor). How it is pushed is container dependent. '*subobj' is cleared afterward and shall not be used anymore.
* POP\_MOVE(&subobj, obj) : Pop an object from the container'obj' and **init & move** it in the unitialized object '*subobj' (*subobj constructor). Which object is popped is container dependent. '*subobj' shall be uninitialized. The container shall have at least one object.
* IT\_TYPE() --> type: Return the type of the iterator object of this container.
* IT\_FIRST(it\_obj, container): Set the iterator it\_obj to the first sub-element of container. What is the first element is container dependent (it may be front or back, or something else). However, iterating from FIRST to LAST (included) or END (excluded) through IT\_NEXT ensures going through all elements of the container. If there is no sub-element in the container, it references an end of the container.
* IT\_LAST(it\_obj, container): Set the iterator it\_obj to the last sub-element of container. What is the last element is container dependent (it may be front or back, or something else). However, iterating from LAST to FIRST (included) or END (excluded) through IT\_PREVIOUS ensures going through all elements of the container. If there is no sub-element in the container, it references an end of the container.
* IT\_END(it\_obj, container): Set the iterator it\_obj to an end of the container. Once an iterator has reached an end, it can't use PREVIOUS or NEXT operators. If an iterator has reached an END, it means that there is no object referenced by the iterator (kind of NULL pointer). There can be multiple end of a container, but all of then share the same properties.
* IT\_FIRST(it\_obj, obj): Set the iterator it\_obj to the first sub-element of the container 'obj'. What is the first element is container dependent (it may be front or back, or something else). However, iterating from FIRST to LAST (included) or END (excluded) through IT\_NEXT ensures going through all elements of the container. If there is no sub-element in the container, it references an end of the container.
* IT\_LAST(it\_obj, obj): Set the iterator it\_obj to the last sub-element of the container 'obj'. What is the last element is container dependent (it may be front or back, or something else). However, iterating from LAST to FIRST (included) or END (excluded) through IT\_PREVIOUS ensures going through all elements of the container. If there is no sub-element in the container, it references an end of the container.
* IT\_END(it\_obj, obj): Set the iterator it\_obj to an end of the container 'obj'. Once an iterator has reached an end, it can't use PREVIOUS or NEXT operators. If an iterator has reached an END, it means that there is no object referenced by the iterator (kind of NULL pointer). There can be multiple representation of the end of a container, but all of then share the same properties.
* IT\_SET(it\_obj, it\_obj2): Set the iterator it\_obj to reference the same sub-element as it\_obj2.
* IT\_END\_P(it\_obj)--> bool: Return true if the iterator it\_obj references an end of the container, false otherwise.
* IT\_LAST\_P(it\_obj)--> bool: Return true if the iterator it\_obj references the last element of the container (just before reaching an end) or has reached an end of the container, false otherwise.
* IT\_EQUAL\_P(it\_obj, it\_obj2) --> bool: Return true if both iterators reference the same element, false otherwise.
* IT\_NEXT(it\_obj): Move the iterator to the next sub-element or an end of the container if there is no more sub-element. The direction of IT\_NEXT is container dependent. it\_obj shall not be an end of the container.
* IT\_PREVIOUS(it\_obj): Move the iterator to the previous sub-element or an end of the container if there is no more sub-element. The direction of PREVIOUS is container dependent, but it is the reverse of the IT\_NEXT operator. it\_obj shall not be an end of the container.
* IT\_CREF(it\_obj) --> &obj: Return a constant pointer to the object referenced by the iterator. This pointer remains valid until any modifying operation on the container, or until another reference is taken from this container through an iterator (some containers may reduce theses constraints, for example a list). The iterator shall not be an end.
* IT\_REF(it\_obj) --> &obj: Same as IT\_CREF, but return a modifiable pointer to the object referenced by the iterator.
* IT\_INSERT(container, it\_obj, obj): Insert 'obj' after 'it\_obj' in the container and update it\_obj to point to the inserted object. All other iterators of the same container become invalidated. If 'it\_obj' is an end of the container, it inserts the object as the first one.
* IT\_REMOVE(container, it\_obj): Remove it\_obj from the container (clearing the associated object) and update it\_obj to point to the next object (as per IT\_NEXT). As it modifies the container, all other iterators of the same container become invalidated. it\_obj shall not be an end of the container.
* SPLICE\_BACK(containerDst, containerSrc, it): Move the object referenced by the iterator 'it' from the container 'containerSrc' into 'containerDst'. Where it is moved is container dependent (it is however likely to be just like for the PUSH method). Afterward 'it' references the next item in 'containerSrc'. 'it' shall not be an end of the container.
* SPLICE\_AT(containerDst, itDst, containerSrc, itSrc): Move the object referenced by the iterator 'itSrc' from the container 'containerSrc' just after the object referenced by the iterator 'itDst' in the container 'containerDst'. If 'itDst' references an end of the container, it is inserted as the first item of the container (See operator 'IT\_INSERT'). Afterward 'itSrc' references the next item in the container 'containerSrc', and 'itDst' references the moved item in the container 'containerDst'. 'itSrc' shall not be an end of the container.
* IT\_CREF(it\_obj) --> &subobj: Return a constant pointer to the object referenced by the iterator (of type const SUBTYPE()). This pointer remains valid until any modifying operation on the container, or until another reference is taken from this container through an iterator (some containers may reduce theses constraints, for example a list). The iterator shall not be an end of the container.
* IT\_REF(it\_obj) --> &subobj: Same as IT\_CREF, but return a modifiable pointer to the object referenced by the iterator.
* IT\_INSERT(obj, it\_obj, subobj): Insert 'subobj' after 'it\_obj' in the container 'obj' and update it\_obj to point to the inserted object (as per IT\_NEXT semantics). All other iterators of the same container become invalidated. If 'it\_obj' is an end of the container, it inserts the object as the first one.
* IT\_REMOVE(obj, it\_obj): Remove it\_obj from the container 'obj' (clearing the associated object) and update it\_obj to point to the next object (as per IT\_NEXT semantics). As it modifies the container, all other iterators of the same container become invalidated. it\_obj shall not be an end of the container.
* SPLICE\_BACK(objd, objs, it\_obj): Move the object of the container 'objs' referenced by the iterator 'it\_obj' to the container 'objd'. Where it is moved is container dependent (it is recommended however to be like the PUSH method). Afterward 'it\_obj' references the next item in 'containerSrc' (as per IT\_NEXT semantics). 'it\_obj' shall not be an end of the container.
* SPLICE\_AT(objd, id\_objd, objs, it\_objs): Move the object referenced by the iterator 'it\_objs' from the container 'objs' just after the object referenced by the iterator 'it\_objd' in the container 'objd'. If 'it\_objd' references an end of the container, it is inserted as the first item of the container (See operator 'IT\_INSERT'). Afterward 'it\_objs' references the next item in the container 'objs', and 'it\_objd' references the moved item in the container 'objd'. 'it\_objs' shall not be an end of the container.
* OUT\_STR(FILE* f, obj): Output 'obj' as a custom formatted string into the FILE stream 'f'. Format is container dependent, but is human readable.
* IN\_STR(obj, FILE* f) --> bool: Set 'obj' to the value associated to the string representation of the object in the FILE stream 'f'. Return true in case of success (in that case the stream 'f' has been advanced to the end of the parsing of the object), false otherwise (in that case, the stream 'f' is in an undetermined position but is likely where the parsing fails). It ensures that an object which is output in a FILE through OUT\_STR, and an object which is read from this FILE through IN\_STR are considered as equal.
* GET\_STR(string\_t str, obj, bool append): Set 'str' to a string representation of the object 'obj'. Append to the string if 'append' is true, set it otherwise. This requires the module m-string.
* PARSE\_STR(obj, const char *str, const char **endp) --> bool: Set 'obj' to the value associated to the string representation of the object in the char stream 'str'. Return true in case of success (in that case if endp is not NULL, it points to the end of the parsing of the object), false otherwise (in that case, if endp is not NULL, it points to an undetermined position but likely to be where the parsing fails). It ensures that an object which is output in a string through GET\_STR, and an object which is read from this string through GET\_STR are considered as equal.
* OUT\_SERIAL(m\_serial\_write\_t *serial, obj) --> m\_serial\_return\_code\_t : Output 'obj' into the configurable serialization stream 'serial' (See #[m-serial-json.h](#m-serial-json) for details and example). Return M\_SERIAL\_OK\_DONE in case of success, or M\_SERIAL\_FAIL otherwise .
* IN\_SERIAL(obj, m\_serial\_read\_t *serial) --> m\_serial\_return\_code\_t: Set 'obj' to its representation from the configurable serialization stream 'serial' (See #[m-serial-json.h](#m-serial-json) for details and example). M\_SERIAL\_OK\_DONE in case of success (in that case the stream 'serial' has been advanced up to the complete parsing of the object), or M\_SERIAL\_FAIL otherwise (in that case, the stream 'serial' is in an undetermined position but usually around the next characters after the first failure).
* UPDATE(dest, src): Update the object 'dest' with the object 'src'. What it does exactly is container dependent. It can either SET or ADD to the node the new 'src' (default is SET).
* GET\_STR(string\_t str, obj, bool append): Set 'str' to a formatted string representation of the object 'obj'. Append to the string if 'append' is true, set it otherwise. This operator requires the module m-string.
* PARSE\_STR(obj, const char *str, const char **endp) --> bool: Set 'obj' to the value associated to the formatted string representation of the object in the char stream 'str'. Return true in case of success (in that case if endp is not NULL, it points to the end of the parsing of the object), false otherwise (in that case, if endp is not NULL, it points to an undetermined position but likely to be where the parsing fails). It ensures that an object which is output in a string through GET\_STR, and an object which is read from this string through GET\_STR are considered as equal.
* OUT\_SERIAL(m\_serial\_write\_t *serial, obj) --> m\_serial\_return\_code\_t : Output 'obj' into the configurable serialization stream 'serial' (See #[m-serial-json.h](#m-serial-json) for details and example) as per the serial object semantics. Return M\_SERIAL\_OK\_DONE in case of success, or M\_SERIAL\_FAIL otherwise .
* IN\_SERIAL(obj, m\_serial\_read\_t *serial) --> m\_serial\_return\_code\_t: Set 'obj' to its representation from the configurable serialization stream 'serial' (See #[m-serial-json.h](#m-serial-json) for details and example) as per the serial object semantics. M\_SERIAL\_OK\_DONE in case of success (in that case the stream 'serial' has been advanced up to the complete parsing of the object), or M\_SERIAL\_FAIL otherwise (in that case, the stream 'serial' is in an undetermined position but usually around the next characters after the first failure).
* UPDATE(objd, objs): Update the object 'objd' with the object 'objs'. What it does exactly is container dependent. It can either SET or ADD (default is SET).
* OOR\_SET(obj, int\_value): Some containers want to store some information within the uninitialized objects (for example Open Addressing Hash Table). This method stores the integer value 'int\_value' into an uninitialized object 'obj'. It shall be able to differentiate between uninitialized object and initialized object (How is type dependent). The way to store this information is fully object dependent. In general, you use out-of-range value for detecting such values. The object remains uninitialized but sets to of out-of-range value (OOR). int\_value can be 0 or 1.
* OOR\_EQUAL(obj, int\_value): This method compares the object 'obj' (initialized or uninitialized) to the out-of-range value (OOR) representation associated to 'int\_value' and returns true if both objects are equal, false otherwise. See OOR\_SET.
* REVERSE(container) : Reverse the order of the items in the container.
* SEPARATOR() --> character: Return the character used to separate items for I/O methods (default is ',')
* EXT\_ALGO(name, container oplist, object oplist): Define additional algorithms functions specialized for the containers (for internal use only by m-algo).
* LIMITS() --> ( numbers ): Return internal properties of a container (for internal use only by m-algo).
* REVERSE(obj) : Reverse the order of the items in the container 'obj'.
* SEPARATOR() --> character: Return the character used to separate items for I/O methods (default is ',') (for internal use only).
* EXT\_ALGO(name, container oplist, object oplist): Define additional algorithms functions specialized for the containers (for internal use only).
* LIMITS() --> ( numbers ): Return internal properties of a container (for internal use only).
The operator names listed above shall not be defined as macro.
More operators are expected.