Lace - Execution of rulesets
============================

Once compiled, a ruleset is essentially a sequence of functions to call on the
execution context.  The simplest execution context is an empty table.  If Lace
is going to store anything it will use a `_lace` prefix as with [[compilation]]
contexts.  As with compilation, the caller is not permitted to put anything
inside `_lace` nor to rely on its layout.

A few important functions make up the execution engine.  The top level
function is simply:

    result, msg = lace.engine.run(ruleset, exec_context)

This will run `ruleset` with the given execution context and return
a simple result.

If `result` is `nil`, then `msg` is a long-form string error explaining
what went wrong.  It represents a Lua error being caught and as such
you may not want to report it to your users.

If `result` is `false`, then `msg` is a long-form string error
explaining that something returned an error during execution which it
would be reasonable to report to users under most circumstances.

If `result` is `"allow"`, then `msg` is an optional string saying why
the ruleset resulted in an allow.  Ditto for `"deny"`.  Essentially any
string might be a reason.  This is covered below in Commands.

Commands
========

When a command is being run, it is called as:

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

where `args` are the arguments returned during the compilation of the command.

If the function throws an error, that will be caught and processed by
the execution engine.

If `result` is falsehood (`nil`, `false`) then the command is considered to
have failed for some reason and `msg` contains an "internal" error
message to report to the user.  This aborts the execution of the
ruleset.

If `result` is `true`, then the command successfully ran, and execution
continues at the next rule.

If `result` is a string, then the command returned a result.  This
ceases execution of the ruleset and the result and message (which must
be a string explanation) are returned to the caller.  Typically such
results would be "allow" or "deny" although there's nothing forcing
that to be the case.

Control Types
=============

When a control type function is being run, it is called as:

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

where `args` are the arguments returned when the definition was compiled.

If the function throws an error, it will be caught and processed by
the execution engine.

If `result` is `nil` then msg is an "internal" error, execution will be
stopped and the issue reported to the caller.

If `result` is `false`, the control call failed and returned falsehood.
Anything else and the control call succeeded and returns truth.

Control type functions are called at the point of test, not at the
point of definition.  Control type results are *NOT* cached.  It is up
to the called functions to perform any caching/memoising of results as
needed to ensure suitably performant behaviour.

Helper functions
================

Since sometimes when writing command functions, you need to know if a given
define rule passes, Lace provides a function to do this.  It is bound up in the
behaviour of Lace's internal `define` command and as such, you should treat it
as a black box.

    result, msg = lace.engine.test(exec_context, name)

This, via the magic of the execution context calls through to the
appropriate control type functions, returning their results directly.

This means that it can throw an error in the case of a Lua error,
otherwise it returns the two values as detailed above.

