/*! @page extend Extending the library The standard Vars objects give scope for defining many different types of data structure, but these representations may not be the most efficient in terms of access time, memory conservation or ease-of-use. When these factors matter, you can always use your own defined structures and store pointers to them inside Vars objects. However, a disadvantage of this is that pointers to your own structures aren't recognized by Vars, and so are not printed by v_print(), or traversed by v_traverse(), or destroyed by v_destroy(), and so on. To deal with this, you can extend the Vars library by defining your data structure as a new Vars object type, using some special functions. The following sections describe how to do that. - @subpage anatomy - @subpage creating - @subpage destroy - @subpage copy - @subpage iteration - @subpage textio - @subpage binio - @subpage traverse - @subpage printing - @subpage template . */ /*! @page anatomy Anatomy of an object type A type in Vars consists of a structure and a set of functions for manipulating that structure. A structure which is a Vars object is recognized as such by having a header structure as its first entry. This structure is declared as being of type vheader. For example, suppose you wanted to extend Vars to recognize complex numbers. A suitable declaration would be: @include ext1.c The next stage in defining a type is to decide what functions are required to manipulate it. Three functions are always needed: declaration, creation and destruction functions. */ /*! @page creating Creating a new object type In order for Vars to recognize a new type, it must be declared. You should provide this as a separate function, which is also called the first time the creation function is used. For example, here is the part of the complex number extension that creates new complex numbers: @include ext2.c The variable @c vcomplex_type is a `type variable', which holds the declaration of the new type. It is initialized by the call to v_create(). Note that this is only done once, on the first call to @c complex_declare(). v_create() takes two arguments -- the name of the type, and a (preferably short) code, which must not be the same as the code of any other variable. The standard types have codes which are just the uppercase first letter of their type names. The calls to @c v_*_func() declare functions to perform various tasks on complex variables -- more on those later. In complex_create(), the complex type is declared and the header variable @c hdr is created (again, only once) by v_header() based on the variable type. This is added to each newly-created variable. */ /*! @page printing Printing objects An object's print function is for displaying the its contents in a compact readable format. It's useful (mainly for debugging) but not essential. The print function for the complex type is as follows: @include ext3.c There are several support functions to help with formatting: - The functions v_print_start() and v_print_finish() must be called at the beginning and end of printing. - The functions v_push_indent() and v_pop_indent() can be used to increase or decrease the indent level respectively, in order to make the output look nice. - The function v_print_type() prints a header line according to the variable type name, using the `type variable'. A newline is printed afterwards. - The function v_indent() prints out indentation up to the current indent level. This should be called before each piece of output following a newline. . */ /*! @page textio Text I/O on objects Text I/O is a part of object @ref persist "persistence", and is done via freeze/thaw functions. Freezing data is relatively easy -- you just write the data out (with indentation to make it look nice). Thawing the data out is more tricky. Essentially, the input is split into tokens which are parsed. The freezing and thawing functions for the complex type are as follows: @include ext4.c Reading variable data from a file has an added complication when using new types. The type @e must be declared before the read is performed, or Vars will not know how to read the data. For the standard types, this is done automatically. But @c complex_declare() must be called before reading. */ /*! @page iteration Iteration over objects Some types of object act as containers for other objects or data. For these types, it makes sense to be able to @ref iterate "iterate" over their contents. To implement iteration for an object type, you must declare a function which returns the next entry in the object, via v_next_func(). The current iteration state of an object is stored in a @ref viter structure. The next-entry function you write accepts a pointer to this structure. It has the following fields: - @c object -- The object being iterated over (read-only). - @c count -- Index of the current iteration (counting from 0) (read-only). - @c ipos, @c ppos -- Integer and pointer iteration positions, initialized to zero and @c NULL respectively (read-write). - @c ival, @c fval, @c dval, @c sval, @c pval -- Arrays of iteration values of different types: integer, float, double, string, pointer (read-write). . Your function should use the iteration positions in some way to refer to the current state of the iteration inside the object. It should update the positions and set one or more iteration values to represent the current entry. Finally, it should return whether the iteration continues. For the complex number example, iteration is not really required. But for illustration, here's a function that iterates over a complex number, returns both parts of the number, and then stops. @include ext9.c The function only needs to use the @c ipos iteration variable to keep track of things, and sets the @c dval[0] array entry as the iteration value. To make code easier to read, you should also supply one or more convenience macros to access the appropriate iteration values. In the example above, you could write something like this: @verbatim #define complex_iter_val(iter) iter.dval[0] @endverbatim Note that the macro uses dot notation, not @c ->, since in the calling function the iteration variable will be a structure. */ /*! @page binio Binary I/O on objects Reading and writing binary data is a part of object @ref persist "persistence", and is simple compared to text data. Any references to other Vars variables in the structure should be written using their appropriate write function. References to generic pointers should be written using v_write(). You can use the standard @c fread(3) and @c fwrite(3) functions to read and write your data, or alternatively you can use low-level Vars functions (such as v_read_double()) to do it. The advantage of using the Vars functions is that they perform byte-swapping if it is required. The reading and writing functions for the complex type are as follows: @include ext5.c As with text I/O, you must declare any new types before reading their data from a file (e.g., @c complex_declare() must be called before reading complex numbers). When data is stored to file with v_write(), the type code is written to indicate what type of data is stored. That's why it's preferable to have a short type code -- it saves on data file size. */ /*! @page copy Copying objects A copy function should be provided to make a copy of an object, and be declared by calling v_copy_func(). Note that a copy should be completely independent -- it shouldn't share any memory with the original object. This is required so that the v_copy() function works correctly. Here's the function for the complex number example: @include ext8.c */ /*! @page traverse Traversing objects A type should provide a traversal function to visit any pointers which are internal to its structure, by calling v_traverse_func() in a similar manner to v_print_func() and v_destroy_func(). The function should call v_traverse() on all the pointers that have not been allocated as part of the type (e.g. if you have a scalar as part of your structure, you would not traverse it, but you @e would traverse its pointer value, if any). In the complex number example, there are no such pointers, so the traversal function is very simple: @include ext6.c First of all, the user-defined function is called on the variable. Next, if v_traverse_seen() returns true, nothing else needs to be done -- the variable has already been traversed. If not, v_push_traverse() is called with the variable as an argument. This flags @c c as visited. Then any pointers contained in the data structure are traversed (although in this example, there aren't any). Finally, the traverse level is decreased using v_pop_traverse(). Note that the user-defined function @e must be called before v_push_traverse() -- otherwise, if v_traverse_seen() is called by the user function, it will always return true. If the user function or the traverse function return non-zero, then traversal aborts and so does this function. Otherwise it returns zero (continue traversal). */ /*! @page destroy Destroying objects The destruction function is the companion to the creation function, and should be the function referred to in the call to v_destroy_func(). Here is the function for the complex number example: @include ext7.c The destruction function should only destroy what was allocated by the creation function -- recursive destruction is automatically done by v_destroy() (provided the traversal function has been declared properly). If a destruction function called by v_destroy() calls v_destroy() itself, this will have no effect. */ /*! @page template Code templates To help with generation of new object types, here are templates of the body and header files required. To use them, you must decide on a name for your new type, and a corresponding type code (preferably short). Then replace @c vartype and @c varcode globally in the templates, preserving case. Here's the header file. @include vars-vartype.h Here's the corresponding body. @include vars-vartype.c */ /* * Local Variables: * mode: c * End: */