Click here to Skip to main content
Click here to Skip to main content

NeoLua (Lua for the .net dynamic language runtime)

By , 24 Mar 2014
Rate this:
Please Sign up or sign in to vote.

Introduction

NeoLua is an implementation of the Lua language. Currently, the implementation is on the level of Lua 5.2 (http://www.lua.org/manual/5.2/manual.html). The goal is to follow the reference of the C-Lua implementation and combine this with full .NET Framework support. That means, it should be easy to call .NET functions from Lua and it should be easy access variables and functions from a .net language (e.g. C#, VB.NET, ...).

NeoLua is implemented in C# and uses the Dynamic Language Runtime.

Background

The idea of NeoLua was born because I needed Lua as a scripting language in a service application. In the first release I used LuaInterface. But it turned out that it is really hard to use LuaInterface as a bridge between .NET and C-Lua correctly.

I got a lot of memory leaks! During debugging I discovered that I did not de-reference the Lua variables correctly and learned that to do this right is really hard.

Principles

What NeoLua is useful for

  • Outsource the logic of your application into scripts
  • Structuring of logic
  • Build a dynamic configuration system, with functions and variables
  • As a formula parser
  • ...

So, this could be reliable partner for your compiled .NET application or engine (e.g. Game Engines).

What I did not have in mind

  • Compiling libraries
  • Standalone applications

Advantages of NeoLua

  • Dynamic access between Lua script and and the host application/.NET framework and vice-versa.
  • NeoLua is based on the DLR. So you get compiled code that is collectable and well-optimized.
  • It is compatible with the .NET world (e.g. C#, VB.NET, IronPython, ...).
  • Full and easy access to the .NET framework or your own libraries (with no stub code).
  • A .NET Framework Garbage Collector that is well-tested and very fast.
  • Pure IL (x86,x64 support)

Drawbacks of NeoLua

  • Handling of upvalues is not possible. .NET uses closures.
  • Debugging is more complicated, because we have to deal with machine code, but NeoLua supports stack traces.
  • It is not 100% compatible to Lua.

Drawbacks of bridges to C-lua, that are solved with NeoLua

  • You have two memory managers and so you have to marshal every data between these two worlds. That takes time and there are a lot pitfalls to get memory leaks.
  • C-Lua interprets its own bytecode. The code is not compiled to machine code.

Hello World

To get started with NeoLua is very easy. At first, you have to add a reference to the Neo.Lua-Assembly. There are two ways to get NeoLua. First download it from neolua.codeplex.com or download the zip-file from this site. The second way is to use the nuget package.

Next, create an instance of the Neo.IronLua.Lua (called Lua-Script-Engine) and get a fresh environment to execute Lua code.

Here is an example that prints "Hello World!" in the debug output:

using Neo.IronLua;

namespace Test
{
  public static class Program
  {
    public static void Main(string[] args)
    {
      using (Lua l = new Lua())
      {
        var g = l.CreateEnvironment();
        g.DoChunk("print('Hello World!');", "test.lua");
      }
    }
  }
}

Running Lua-Snippets

To run a small snippet, just execute it via DoChunk. Scripts can have parameters, like in example a and b. Every script can return values. The return value of scripts are always an array of objects, because in Lua it is possible to return more than one result.

using (Lua l = new Lua())
{
    var g = l.CreateEnvironment();

    var r = g.DoChunk("return a + b", "test.lua",
      new KeyValuePair<string, object>("a", 2),
      new KeyValuePair<string, object>("b", 4));

    Console.WriteLine(r[0]);
}

Because NeoLua is a dynamic language, there is also a way to do it dynamically.

using (Lua l = new Lua())
{
    dynamic g = l.CreateEnvironment();
    dynamic r = g.dochunk("return a + b", "test.lua", "a", 2 "b", 4);
    Console.WriteLine(r[0]);
}

Values and Types

NeoLua is not a dynamically typed language, it just looks like it is. Variables always have a type (at least System.Object).

NeoLua supports all CLR types. If there is type conversion necessary, it is done automatically. Dynamic types are also supported.

local a = "5"; -- a string is assigned to a local variable of the type object
local b = {}; -- object assigned with an empty table 
b.c = a + 8; -- the variable "a" is converted into an integer and assigned to the dynamic member of a table

Working with Globals

The environment is a special Lua table. Set or get the variables on the environment and they are accessible in the script-code as global variables.

using (Lua l = new Lua())
{
    var g = l.CreateEnvironment();
    dynamic dg = g;
    
    dg.a = 2; // dynamic way to set a variable
    g["b"] = 4; // second way to access variable
    g.DoChunk("c = a + b", "test.lua");

    Console.WriteLine(dg.c);
    Console.WriteLine(g["c"]);
}

Working with LuaTables

The implementation of NeoLua supports the class LuaTable. This class is used by the script-code as is. There is no communication between Lua and C#.

Member

using (Lua l = new Lua())
{
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();
  dg.t = new LuaTable();
  dg.t.a = 2;
  dg.t.b = 4;
  dg.dochunk("t.c = t.a + t.b", "test.lua");
  Console.WriteLine(dg.t.c);
}

Index access

using (Lua l = new Lua())
{
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();
  dg.t = new LuaTable();
  dg.t[0] = 2;
  dg.t[1] = 4;
  dg.dochunk("t[2] = t[0] + t[1]", "test.lua");
  Console.WriteLine(dg.t[2]);
}

Functions

Defining a function feels natural. It is basically a delegate that is assigned to a member.
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();
  dg.myadd = new Func<int, int, int>((a, b) => a + b); // define a new function in c#
  dg.dochunk("function Add(a, b) return myadd(a, b) end;", "test.lua"); // define a new function in lua that calls the C# function

  Console.WriteLine((int)dg.Add(2, 4)); //call the Lua function

  var f = (Func<object,>)dg.Add;// get the Lua function
  Console.WriteLine(f(2, 4).ToInt32());
}
NeoLua supports to add type information to the function signature.
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();

  dg.myadd = new Func<int, int, int>((a, b) => a + b);

  dg.dochunk("function Add(a : int, b : int) : int return myadd(a, b) end;", "test.lua");

  Console.WriteLine((int)dg.Add(2, 4));

  var f = (Func<int,>)dg.Add;
  Console.WriteLine(f(2, 4));
}

Methods (host application)

The next example defines three members.

a , b are members, they are holding a ordinary integer. And add is holding a function, but this definition is not a method. Because if you try to call the function like a method in e.g. C#, it will throw a NullReferenceException. You must always pass the table as a first parameter to it. To declare a real method, you have to call the explicit method DefineMethod. Lua does not care about the difference, but C# or VB.NET do.
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();

  dg.t = new LuaTable();
  dg.t.a = 2;
  dg.t.b = 4;
  
  dg.t.add = new Func<dynamic, int>(self => 
    {
      return self.a + self.b;
    });

  ((LuaTable)dg.t).DefineMethod("add2", (Delegate)dg.t.add);

  Console.WriteLine(dg.dochunk("return t:add()", "test.lua")[0]);
  Console.WriteLine(dg.dochunk("return t:add2()", "test.lua")[0]);
  Console.WriteLine(dg.t.add(dg.t));
  Console.WriteLine(dg.t.add2());
}

Methods (Lua)

  • add is a normal function, created from a delegate.
  • add1 is declared as a function.
  • add2 is a method, that is created from a delegate. Be aware that a delegate definition doesn't know anything about the concept of methods, so you have to declare the self parameter.
  • add3 shows a method declaration.
using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();
  LuaResult r = dg.dochunk("t = { a = 2, b = 4 };" +
    "t.add = function(self)" +
    "  return self.a + self.b;" +
    "end;" +
    "function t.add1(self)" +
    "  return self.a + self.b;" +
    "end;" +
    "t:add2 = function (self)" +
    "  return self.a + self.b;" +
    "end;" +
    "function t:add3()" +
    "  return self.a + self.b;" +
    "end;" +
    "return t:add(), t:add2(), t:add3(), t.add(t), t.add2(t), t.add3(t);", 
    "test.lua");
  Console.WriteLine(r[0]);
  Console.WriteLine(r[1]);
  Console.WriteLine(r[2]);
  Console.WriteLine(r[3]);
  Console.WriteLine(r[4]);
  Console.WriteLine(r[5]);
  Console.WriteLine(dg.t.add(dg.t)[0]);
  Console.WriteLine(dg.t.add2()[0]);
  Console.WriteLine(dg.t.add3()[0]);
}

Classes/Objects

To create a class you have to write a function that creates a new object. The function is by definition the class and the result of the function is the object.

using (Lua l = new Lua())
{
  dynamic dg = l.CreateEnvironment();

  dg.dochunk("function classA()" +
    "  local c = { sum = 0 };" +
    "  function c:add(a)" +
    "    self.sum = self.sum + a;" +
    "  end;" +
    "  return c;" +
    "end;", "classA.lua");

  dynamic o = dg.classA()[0];
  o.add(1);
  o.add(2);
  o.add(3);
  Console.WriteLine(o.sum);
}

Compiled Code

Use a script multiple times. Pre-compile the script-code with CompileChunk and run it on different environments. That saves compile time and memory.

using (Lua l = new Lua())
{
    LuaChunk c = l.CompileChunk("return a;", "test.lua", false);

    var g1 = l.CreateEnvironment();
    var g2 = l.CreateEnvironment();

    g1["a"] = 2;
    g2["a"] = 4;

    Console.WriteLine((int)(g1.DoChunk(c)[0]) + (int)(g2.DoChunk(c)[0]));
}

Lambda support

A delegate, that is generated by this function, has no debug information and no environment. It is not allowed to use global variables or lua functions/libraries in the code. Only the clr-package is useable.

using (Lua l = new Lua())
{
  var f = l.CreateLambda<Func<double, double>>("f", "return clr.System.Math:Abs(x) * 2", "x");
  Console.WriteLine("f({0}) = {1}", 2, f(2));
  Console.WriteLine("f({0}) = {1}", 2, f(-2));
}

.NET

To access the .NET Framework classes, there is a package that is called clr. This package references namespaces and classes.

This example shows how to call the static String.Concat function:

function a() return 'Hello ', 'World', '!'; end; return clr.System.String:Concat(a());

This example shows how to use the StringBuilder:

local sys = clr.System; -- using the namespace System
local sb = sys.Text.StringBuilder();
sb:Append('Hello'):Append(' World!');
return sb:ToString();

The function call on the type invokes the constructor of a class to instantiate a new object.

Complex Example

This example reads Lua script files and executes them once. The example shows how to compile a script and catch exceptions and how to retrieve the stack trace. The stack trace can only be retrieved if the script is compiled with debug information. To turn this switch on set the second parameter of CompileChunk to true.

Turning on debug information has two draw-backs. Firstly the compile process is slower, and secondly the memory usage of the script code increases.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace Neo.IronLua
{
  public static class Program
  {
    public static void Main(string[] args)
    {
      using (Lua l = new Lua())
      {
        // create an environment that is associated with the Lua scripts
        dynamic g = l.CreateEnvironment();

        // register new functions
        g.print = new Action<object[]>(Print);
        g.read = new Func<string,>(Read);

        foreach (string c in args)
        {
          using (LuaChunk chunk = l.CompileChunk(c, true)) // compile the script with debug informations which is needed for a complete stack trace
            try
            {
              object[] r = g.dochunk(chunk); // execute the chunk
              if (r != null && r.Length > 0)
              {
                Console.WriteLine(new string('=', 79));
                for (int i = 0; i < r.Length; i++)
                  Console.WriteLine("[{0}] = {1}", i, r[i]);
              }
            }
            catch (TargetInvocationException e)
            {
              Console.ForegroundColor = ConsoleColor.DarkRed;
              Console.WriteLine("Expception: {0}", e.InnerException.Message);
              LuaExceptionData d = LuaExceptionData.GetData(e.InnerException); // get stack trace
              Console.WriteLine("StackTrace: {0}", d.GetStackTrace(0, false));
              Console.ForegroundColor = ConsoleColor.Gray;
            }
        }
      }
    } // Main

    private static void Print(object[] texts)
    {
      foreach (object o in texts)
        Console.Write(o);
      Console.WriteLine();
    } // proc Print

    private static string Read(string sLabel)
    {
      Console.Write(sLabel);
      Console.Write(": ");
      return Console.ReadLine();
    } // func Read
  }
}

The Lua-script is as follows:

local a, b = tonumber(read("a")), tonumber(read("b"));

function PrintResult(o, op)
    print(o .. ' = ' .. a .. op .. b);
end;

PrintResult(a + b, " + ");
PrintResult(a - b, " - ");
PrintResult(a * b, " * ");
PrintResult(a / b, " / ");

Performance

I compared the performance to the LuaInterface project.

This table shows the results depending on if we cleared the caches. Caches are built during compile and runtime.

Cleared reflection cache:

Empty               : LuaInterface    0,9 ms    NeoLua    0,4 ms   2,023
Sum                 : LuaInterface    0,0 ms    NeoLua    1,3 ms   0,015
Sum_strict          : LuaInterface    0,0 ms    NeoLua    0,1 ms   0,091
Sum_echo            : LuaInterface    2,1 ms    NeoLua    2,7 ms   0,800
String              : LuaInterface    1,5 ms    NeoLua    1,1 ms   1,404
String_echo         : LuaInterface    4,1 ms    NeoLua    2,8 ms   1,486
Delegate            : LuaInterface    2,0 ms    NeoLua    1,6 ms   1,290
StringBuilder       : LuaInterface    3,1 ms    NeoLua    4,1 ms   0,745

None cleared reflection cache:

Empty               : LuaInterface    1,7 ms    NeoLua    1,0 ms   1,700
Sum                 : LuaInterface    1,0 ms    NeoLua    1,5 ms   0,664
Sum_strict          : LuaInterface    1,0 ms    NeoLua    1,0 ms   1,000
Sum_echo            : LuaInterface    3,9 ms    NeoLua    3,0 ms   1,328
String              : LuaInterface    2,9 ms    NeoLua    2,0 ms   1,443
String_echo         : LuaInterface    7,2 ms    NeoLua    3,3 ms   2,187
Delegate            : LuaInterface    3,7 ms    NeoLua    2,0 ms   1,808
StringBuilder       : LuaInterface    5,1 ms    NeoLua    4,3 ms   1,199

The next table shows the results with pre-compiled scripts.

Cleared reflection cache:

Empty               : LuaInterface    0,0 ms    NeoLua    0,0 ms     NaN
Sum                 : LuaInterface    0,0 ms    NeoLua    0,0 ms   0,667
Sum_strict          : LuaInterface    0,0 ms    NeoLua    0,0 ms     NaN
Sum_echo            : LuaInterface    1,6 ms    NeoLua    0,1 ms  11,286
String              : LuaInterface    1,2 ms    NeoLua    0,3 ms   3,933
String_echo         : LuaInterface    3,5 ms    NeoLua    0,4 ms   9,132
Delegate            : LuaInterface    1,4 ms    NeoLua    0,1 ms  22,833
StringBuilder       : LuaInterface    2,4 ms    NeoLua    0,1 ms  29,500

None cleared reflection cache:

Empty               : LuaInterface    0,0 ms    NeoLua    0,0 ms     NaN
Sum                 : LuaInterface    0,0 ms    NeoLua    0,1 ms   0,429
Sum_strict          : LuaInterface    0,0 ms    NeoLua    0,0 ms     NaN
Sum_echo            : LuaInterface    2,7 ms    NeoLua    0,3 ms   8,469
String              : LuaInterface    1,8 ms    NeoLua    0,4 ms   4,209
String_echo         : LuaInterface    5,9 ms    NeoLua    0,8 ms   7,603
Delegate            : LuaInterface    2,7 ms    NeoLua    0,2 ms  15,647
StringBuilder       : LuaInterface    4,1 ms    NeoLua    0,2 ms  20,650

Parts that are not implemented

Not all Lua functionality is implemented yet, and some parts may be never be implemented. However, most parts of the Lua reference work just fine.

Things thay might be implemented in the future:

  • Hex representation for doubles
  • Metatables
  • Missing runtime parts
  • Debugging (hard)

Things that most likely will never be implemented:

  • Working with upvalues
  • Lua-Byte-Code support

Resources

NeoLua is hosted on CodePlex: neolua.codeplex.com. There you can find the source, documentation and releases.

Changes

0.8.2:

  • Add: coroutine package
  • Add: io package

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

About the Author

neolithos
Software Developer
Germany Germany
No Biography provided

Comments and Discussions

 
QuestionDoString with Nlua, Is it possible? PinprofessionalPapyRef27-Mar-14 5:44 
AnswerRe: DoString with Nlua, Is it possible? Pinmemberneolithos27-Mar-14 6:36 
GeneralRe: DoString with Nlua, Is it possible? PinprofessionalPapyRef27-Mar-14 7:22 
GeneralRe: DoString with Nlua, Is it possible? Pinmemberneolithos27-Mar-14 7:35 
GeneralRe: DoString with Nlua, Is it possible? PinprofessionalPapyRef27-Mar-14 7:44 
GeneralMy vote of 5 PinprofessionalPapyRef26-Mar-14 22:59 
GeneralRe: My vote of 5 Pinmemberneolithos27-Mar-14 4:18 
QuestionMake the source code available to SpeedTest program? PinprofessionalPapyRef26-Mar-14 21:57 
AnswerRe: Make the source code available to SpeedTest program? Pinmemberneolithos27-Mar-14 4:21 
QuestionPossible to load compiled LUA Script? PinmemberPapyRef23-Mar-14 1:10 
AnswerRe: Possible to load compiled LUA Script? Pinmemberneolithos24-Mar-14 0:08 
GeneralRe: Possible to load compiled LUA Script? PinmemberPapyRef24-Mar-14 3:29 
GeneralRe: Possible to load compiled LUA Script? Pinmemberneolithos24-Mar-14 5:48 
QuestionNeoLua vs Nlua, why it is easier to use NeoLua than NLua? PinmemberPapyRef23-Mar-14 0:15 
AnswerRe: NeoLua vs Nlua, why it is easier to use NeoLua than NLua? Pinmemberneolithos24-Mar-14 1:07 
Generalmy vote of 5+ [modified] PinmemberSouthmountain7-Mar-14 4:55 
GeneralRe: my vote of 5+ Pinmemberneolithos7-Mar-14 5:38 
GeneralVery interesting PinmemberWrangly4-Nov-13 12:07 
GeneralRe: Very interesting Pinmemberneolithos5-Nov-13 4:16 
QuestionHow's the performance? PinmemberFatCatProgrammer28-Oct-13 4:08 
AnswerRe: How's the performance? Pinmemberneolithos28-Oct-13 10:03 
AnswerRe: How's the performance? Pinmemberneolithos2-Nov-13 12:26 
GeneralRe: How's the performance? PinmemberFatCatProgrammer3-Nov-13 3:49 
GeneralRe: How's the performance? [modified] Pinmemberneolithos4-Nov-13 3:26 
GeneralMy vote of 5 Pinmemberarchitecton27-Oct-13 4:43 
GeneralMy Vote Of 5 PinprofessionalBrisingr Aerowing25-Oct-13 15:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140421.2 | Last Updated 24 Mar 2014
Article Copyright 2013 by neolithos
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid