A self-contained Lua interpreter written in C# with zero external dependencies. I built this for a Timberborn mod i'm working on. I originally tried using MoonSharp but kept running into DLL loading issues at runtime, so i just wrote my own.
This project is inspired by Expression 2, a lua scripting intrepertor for the Valve Source Game Engine. Famously found in the Wiremod mod for Garrysmod.
Super simple, (hopefully) drops straight into any C# project.
I needed to embed a Lua runtime inside a Unity game mod. every DLL-based solution (MoonSharp etc) caused runtime errors. so instead of fighting the DLL loader i just wrote a basic one from scratch — lexer → parser → AST → evaluator.
if you're in a similar situation (Unity mod, game plugin, embedded scripting in a restricted environment) this might save you the same headache.
- variables — local and global
- if / elseif / else
- while, repeat / until
- numeric for
for i = 1, 10, 2 do - generic for
pairsandipairs - functions and closures
- tables (array and hash style)
- multiple return values
- string concatenation with
.. - varargs
...
stdlib included:
print,tostring,tonumber,type,pairs,ipairs,unpack,select,error,assertmath— floor, ceil, abs, sqrt, sin, cos, exp, log, max, min, pow, fmod, random, pistring— len, sub, upper, lower, rep, reverse, find, formattable— insert, remove, concat, sort
var lua = new MiniLua(msg => Console.WriteLine(msg));
string output = lua.Execute(@"
for i = 1, 5 do
print('hello ' .. i)
end
");-- List all linked buildings
print("Linked: " .. chip.count())
for i, alias in ipairs(chip.list()) do
local b = chip.get(alias)
if b ~= nil then
print(i .. ". [" .. alias .. "] " .. b.name())
end
end
-- Check water storage contents
local storage = chip.get("water storage")
if storage == nil then
print("No building linked as 'water storage'")
else
local contents = storage.getContents()
if contents == nil then
print("Could not read contents")
else
local water = contents["Water"] or 0
print("Water: " .. water)
if water > 50 then
print("Status: GOOD")
else
print("Status: BAD")
end
end
endyou can expose C# functions or values to Lua before running a script:
var lua = new MiniLua();
// inject a simple value
lua.SetGlobal("playerName", LuaValue.Of("Marvin"));
// inject a callable function
lua.SetGlobal("add", new LuaValue((LuaValue[] args) => {
double a = args[0].NumVal;
double b = args[1].NumVal;
return new[] { LuaValue.Of(a + b) };
}));
// inject a table
var api = new LuaTable();
api.Set("greet", new LuaValue((LuaValue[] args) => {
Console.WriteLine("hi " + args[0].StrVal);
return new LuaValue[0];
}));
lua.SetGlobal("myApi", new LuaValue(api));
lua.Execute(@"
print(playerName)
print(add(3, 4))
myApi.greet('world')
");try {
lua.Execute(scriptCode);
} catch (LuaError ex) {
Console.WriteLine("Lua error: " + ex.Message);
}Lexer - tokenizes the source into a flat token stream. handles strings, numbers, operators, keywords, comments.
Parser - recursive descent parser. produces a typed AST. Statements (IfStmt, WhileStmt, ForStmt, AssignStmt, LocalStmt, FuncDefStmt, ReturnStmt etc); Expressions (BinOpExpr, UnOpExpr, FieldExpr, IndexExpr, CallExpr, MethodCallExpr etc).
Interpreter - 'Walking the tree' evaluator. ExecBlock walks statements, Eval evaluates expressions. control flow (return, break) uses exception signals (ReturnSignal, BreakSignal) to unwind the call stack cleanly.
LuaTable - backed by a List<LuaValue> for sequential integer keys (array part) and a Dictionary<string, LuaValue> for string keys (hash part). standard # length semantics.
This is not a full Lua implementation. There are many things that are missing or not fully supported... For an in-game scripting interpretor it's more than enough. if you need the full language, I'd reccomending using MoonSharp as its probably much easier...
— marvinb16