diff options
Diffstat (limited to 'doc/compilation.mdwn')
-rw-r--r-- | doc/compilation.mdwn | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/doc/compilation.mdwn b/doc/compilation.mdwn new file mode 100644 index 0000000..37876db --- /dev/null +++ b/doc/compilation.mdwn @@ -0,0 +1,190 @@ +Lace - The nitty-gritty of the compilation process +================================================== + +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]]. |