3. Compilation

When you construct a Lace engine, you give it a compilation callback set. That set is used to call you back when Lace encounters something it needs help compiling. The structure of it is:

{ _lace = {
   loader = function(compcontext, nametoload) ... end,
   commands = {
      ... = function(compcontext, words...) ... end,
   },
   controltype = {
      ... = function(compcontext, type, words...) ... end,
   }
} }

Anything outside of the _lace entry in the context is considered fair game structure-wise and can be used by the functions called back to acquire internal pointers etc. Note however that the compilation context will not be passed around during execution so if you need to remember some of it in order to function properly, then it's up to you. Also note that anything not defined above in the _lace example is considered "private" to Lace and should not be touched by non-Lace code.

In addition, Lace will maintain a source entry in the _lace table with the lexed source which is being compiled and, if we're compiling an included source, a parent entry with the compilation context of the parent. The toplevel field is the compilation context of the top level compilation. If parent is nil, then toplevel will equal compcontext. Lace also maintains a linenr entry with the currently-being-compiled line number, so that commands and control types can use that in error reports if necessary.

If loader is absent then the include statement refuses to include anything mandatory. If it is present but returns nil when called then the include statement fails any mandatory includes which do so.

Otherwise the loader is expected to return the 'real' name of the source and the content of it. This allows for symbolic lookups.

If Lace encounters a command it does not recognise then it will call context._lace.commands[cmdname] passing in the words representing the line in question. It's up to that function to compile the line or else to return an error.

If Lace encounters a control type during a define command which it does not understand, then it calls the context._lace.controltype[name] function passing in all the remaining arguments. The control type function is expected to return a compiled set for the define or else an error.

To start a Lace engine compiling a ruleset, simply do (pseudocode):

rules, err = lace.compiler.compile(compcontext, sourcename[, sourcecontent])

If sourcecontent is not given, Lace will use the loader in the compcontext to load the source.

If rules is nil, then err is a Lua error. If rules is false, then err is a formatted error from compilation Otherwise, rules should be a table containing the ruleset.

Internally, once compiled, Lace rulesets are a table of tables. Each rule entry has a reference to its source and line number. It then has a function pointer for executing this rule, and a set of arguments to give the rule. Lace automatically passes the execution context as the first argument to the rule. Sub-included rulesets are simply one of the arguments to the function used to run the rule.

Loader

When Lace wishes to load an entry, it calls the loader function. This is to allow rulesets to be loadable from arbitrary locations such as files on disk, HTTP URLs, random bits of memory or even out of version control repositories directly.

The loader function is given the compilation context and the name of the source to load. Note that while it has the compilation context, the loader function must be sensitive to the case of the initial load. Under that circumstance, the source information in the compilation context will be unavailable. The loader function is required to fit the following pseudocode definition:

realname, content = loader(compcontext, nametoload)

If realname is not a string then content is expected to be an "internal" error message (see below) which will be augmented with the calling source position etc and rendered into an error to return to the caller of lace.compiler.compile().

If realname is a string then it is taken to be the real name of the loaded content (at worst you should return nametoload here) and content is a string representing the contents of that file.

Once it has been loaded by the loader function, Lace will compile that sub-ruleset before continuing with the current ruleset.

Commands

When Lace wishes to compile a command for which it has no internal definition, it will call the command function provided in the compilation context. If no such command function is found, it will produce an error and stop the compilation.

The command functions must fit the following pseudocode definition:

cmdtab, msg = command_func(compcontext, words...)

If cmdtab is not a table, msg should be an "internal" error message (see below) which will be augmented with the calling source position etc and rendered into an error to return to the caller of lace.compiler.compile().

If cmdtab is a table, it is taken to be the compiled table representing the command to run at ruleset execution time. It should have the form:

{ fn = exec_function, args = {...} }

Lace will automatically augment that with the source information which led to the compiled rule for use later.

The exec_function is expected to fit the following pseudocode definition:

result, msg = exec_function(exec_context, unpack(args))

See execution for notes on how these exec_function functions are meant to behave.

Control Types

When Lace is compiling a definition rule with a control type it has not got internally, Lace will call the controltype function associated with it (or report an error if no such control type is found).

The control type functions must fit the following pseudocode definition:

ctrltab, msg = controltype_func(compcontext, type, words...)

If ctrltab is not a table, msg should be an "internal" error message (see below) which will be augmented with the calling source position etc and rendered into an error to return to the caller of lace.compiler.compile().

If ctrltab is a table, it is taken to be the compiled table representing the control type to run at ruleset execution time. It should have the form:

{ fn = ct_function, args = {...} }

The ct_function is expected to fit the following pseudocode definition:

result, msg = ct_function(exec_context, unpack(args))

See execution for notes on how these ct_function functions are meant to behave.

Compiler internal errors

Error messages during compilation are generated by calling:

return lace.error.error("my message", { x, y })

Where the table is a list of numeric indices of the words which caused the error. If words is empty (or nil) then the error is considered to be the entire line.

Lace will use this information to construct meaningful long error messages which point at the words in question. Such as:

Unknown command name: 'go_fish'
myruleset :: 6
go_fish "I have no bananas"
^^^^^^^

In the case of control type compilation, the words will automatically be offset by the appropriate number to account for the define words. This means you should always 1-index from your arguments where index 1 is the control type word index.

The same kind of situation occurs during execution.

generated by LDoc 1.4.6 Last updated 2022-07-22 12:53:02