Wiki

Clone wiki

glLoadGen / Style_Creation

The style system is designed to be extensible. But it is also somewhat complex. There are two concepts that are important: a style and a structure.

A structure is a Lua table that effectively defines the basic layout of where information goes in the generated files. It also defines which files are generated. In a structure, you can do things like loop over all the extensions and/or versions. The structure system is very flexible, allowing you to create virtually any kind of output you could imagine.

The structure defines the basic layout of everything, but it doesn't define the actual words and characters that get written. This is defined by the style. Styles and structures interact; structures can say things like, "Loop over all extensions and call this style function for each." The particular style function will decide what to do with the extension it is given. Because of this, different styles can share the same structure. pointer_c and pointer_cpp both use the style defined in StyleCommon, even though they do very different things.

For example, let's say you have part of a code generation system, where you need to iterate over each extension and create a function for it. In that function, you iterate over all of the function pointers within that extension, writing loading code for each one. The part of the structure that provides this would look like this:

{ type="ext-iter",
  { type="block", name="ExtensionFunctionDef(hFile, extName, spec, options),
    { type="func-iter",
      { type="write", name="LoadFunctionPtr(hFile, func, typemap, spec, options)", },
    },
  },
},

The first section has an ext-iter, which iterates all of the extensions the user asked for. It will perform all of the contents once for each extension. Within that is a user-written block that defines the beginning and end of the function. Inside of the block is a func-iter, which iterates over all of the functions inside of the current extension. So it's like a nested for-loop. Inside of the function iterator is a write statement that writes something for each function.

The stuff in the name tags represent (part of) the name of functions stored within the style. Exactly how this is interpreted depends on the particular statement. For example, the write statement above will call a function named //Write//LoadFunctionPtr, because the write statement always appends Write to its statements.

The block statement is a bit more complex, as it will call two separate functions: WriteBlockBeginExtensionFunctionDef and WriteBlockEndExtensionFunctionDef. It calls the first one before executing any of its containing statements, and it calls the second after executing its contents. Thus the block's contents are bound.

The style should have functions with the appropriate names; if it does not, a runtime error will occur, providing information that the function name couldn't be called.

The parameter list is actually quite special. Each parameter name has a very specific meaning and stores very specific data (which you must never modify). But different parameters are available at different times. For example, extName represents the name of the current extension. But there can only be a "current extension" when you are iterating over a group of extensions. Likewise, func is a function loaded from the appropriate spec file, but it is only available while iterating over a set of functions.

As with function names, if a parameter is specified in a place where it is not made available (such as providing func to ExtensionFunctionDef), a runtime error explaining the problem will occur.

This is a simple overview of the process. There is a rather lot more complexity than that.

In general, a style and a structure are created in tandem; if a structure is to be used in multiple styles, then the structure can be created first. But it's much easier to make part of a structure, implement enough style functions to use it, then expand on it.

There are two documents available to help you learn about making styles. The first is a step-by-step guide to making a new style and structure. The second is a reference manual for the structure system.

Responsibilities

Your style may create global definitions and so forth. In C/C++, definitions in one source file can come into conflict with definitions in another. Here are the rules that the system expects new styles to follow when creating such definitions:

  • The user should be able to use different specs with the same style and link them together in the same program without conflicts. Files generated like this should coexist. This effectively means that you need to make sure that your names are prefixed with something spec-specific. The spec table has functions to get an appropriate prefix; the spec.DeclPrefix() function is the general prefix for declaration/definitions of things that can conflict at link time.
  • The user should be able to supply a prefix with the -prefix option. The user should be able to generate two separate source/header files with the exact same options with the exception of the prefixes. That is, the user can supply the same specification, version, extensions list, etc. And both files should be able to be linked together into a single program entirely without incident. Both loaders should be able to co-exist peacefully; loading function pointers for one should not impact the loading of function pointers for another
    Note: if your style does static linking, such as for Linux or OSX, then the part about loading pointers for one not impacting the other can be ignored.
    This rule effectively means that you must prefix every non-static definition. Namespace scoping would also work, if you use the prefix as the namespace (or prefix the namespace with it).
    The prefix string is in options.prefix. Note that it will always be present; if you want to test for a user-defined prefix, test it against the empty string.

So decorate names that can conflict with the user-prefix and the spec's prefix. In C, you'll have to prefix the actual names.

Updated