Let’s start with Lua
The current state of things is that the lightweight but mighty Lua has become ubiquitous in the world of computing as a scripting and configuration component. Lua’s strategy of “mechanism, not policies” allows the language to be extremely malleable. It’s worth watching Roberto Ierusalimschy’s talks on the evolution of Lua [1. Video 1, more slides, Video 2, more Roberto].
The latest adoptions of Lua are for example the Redis’ EVAL Query with Lua scripting, and Wikipedia’s template automation using user-written Lua scripts.
dynamiclua
There have been several attempts to bring Lua into the world of .NET, and one of the most recent efforts by Niklas Rother, dynamiclua, combines the previous achievements with the dynamic
type of .NET 4.0, and lays path for an easily reusable business logic scripting component, which is now even available via nuget.
After adding dynamiclua as a reference, here’s your first business logic:
using (dynamic lua = new DynamicLua.DynamicLua()) {
Console.WriteLine(lua("return 2+2"));
}
note the IDisposable
usage for freeing native resources.
Discovering the features
Static configuration
As an author of a configurable component,
In order to reduce my support effort,
I want the users of my component to write their static configuration themselves
While for that user story JSON or XML would suffice, we’re on the Lua track, hence this is how it could work:
// comes from some user input or file
string user_static_config = @"
config = {
url = 'https://github.com/nrother/dynamiclua'
}
";
lua(user_static_config);
string url = lua.config.url;
Console.WriteLine(url);
… it does look like JSON, but it isn’t. Here, one can observe the harmony resulting from the use of the dynamic
keyword to interact with a dynamic language from a statically typed one. DynamicLua can leverage reflection capabilities of both languages to create an “automagical” interop, compared sometimes tedious writing of bindings for a language, such as native C++.
The power of Lua at the user’s fingertips
As an author of a customizable business software,
In order to reduce my own support effort,
And create a new business of customizations,
I want the administrators of my software to write their business logic scripts themselves
Many systems grow out of static configurability, and business logic definition and evaluation in some programming language becomes inevitable. Whether this is a good thing or a bad one for one’s business [2. User scripts are software, and thus might never terminate] is not in the scope of this article, as it is assumed, we want to make our software customizable via scripting [3. I won’t repeat the meme about great power here].
The static configuration entries can be dynamically evaluated at script run time. Various bindings may be used in the script, as i.e. the math
standard library here:
lua("config.answer = nil or math.floor(131.94689145078/math.pi)");
int answer = (int)lua.config.answer;
Console.WriteLine(answer);
Using Lua as an expression evaluator
Lua supports multiple return values, which makes certain scenarios fun:
dynamic result = lua("return 1, 'world'");
Console.WriteLine("{0} {1}", result[0], result[1]);
Binding .NET code
Dynamiclua does not have an explicit class binding syntax [4. as is the case with many C++ bindings i.e. LuaBridge], but it doesn’t matter much, as functions can be bound, and thus, constructors. Via reflection all public [5. In my opinion, access to privates should throw…] members are bound. The domain code is therefore very easy to bind. Take an example:
class Example
{
int answer = 42;
public int Answer { get { return answer; } }
public int Add(int a, int b)
{
return a + b;
}
}
The binding is quite trivial:
lua.NewExample = new Func<Example>(() => new Example());
// Lua functions may be called via dynamiclua
Example my_example = lua.NewExample();
Console.WriteLine(my_example.Answer);
lua(@"
local example = NewExample()
print(example.Answer)
print(example:Add(2,3))
");
Minimal sandboxing
As an author of a scripting component,
In order to mitigate the risk of damage by malicious user-supplied code,
I want the script executing environment to be sandboxed.
Sandboxing is a very complex issue and perhaps only approximations of an undefined ideal sandbox can be made [6. see wiki: SandBoxes and Daniel Silverstone’s talk at Lua Workshop 2013 on his approach]. A sane attitude towards this security-related issue should go along the lines of trusting nobody, even oneself, and even that distrust should not be enough. Depending on the posed security requirements, the logic execution component might even be physically separated from your business software, i.e. running on another machine or in another process. But that is perhaps another concern and should be independent of your script execution component.
Since Lua is malleable, one can configure or manipulate the Lua virtual machine to make certain actions unlikely, such as accessing the file system. This can be achieved by setting the bound names to nil
as in [7. More …]:
lua("import = nil");
lua("require = nil");
new Action(() => lua("import 'System'"))
.ShouldThrow<Exception>();
In this little spec you can also observe what should happen (non-specifically) if there’s a Lua syntax or a runtime error. You can also see how to import assemblies into the Lua state.
Here’s another quote from the test:
lua.import("System");
Func<int> func = () => (int)lua("return Console.BufferHeight")[0];
new Action(() => func())
.ShouldNotThrow();
func().Should().Be(Console.BufferHeight);
Without further ado
Some things should perhaps be further investigated:
- Is dynamiclua prepared for the Mono runtime on *x platforms?
- Is it possible to crash the process uncontrollably, even while catching exceptions?
Fix crash on finalization
Lua can appear simple enough, such that, given a syntactic constraint, even non-programmers could write business logic scripts and configurations. However, there is a company experimenting with alternative ways to define business logic using different approaches to a logic-definition UI. It’s worth checking out: LogicEditor.
Other language and runtime candidates will hopefully be investigated within the next posts of the quest: IronRuby and Boo. The readers of this blog entry are welcome to criticize, point out omitted or wrong information, and interact freely.
Code
https://github.com/d-led/BusinessLogicScripting