Lua 5.3 virtual machine implemented in pure Python. No C dependencies.
Full pipeline: Lua source → lexer → compiler → bytecode → VM.
python -m pylua hello.luafrom pylua import run_string
print(run_string('print("hello")'))
# hellofrom pylua import run_string, LuaVM, LuaError
# arithmetic
run_string('print(2 ^ 10)') # 1024.0
run_string('print(7 // 2, 7 % 2)') # 3 1
# string
run_string('print("hello" .. " " .. "world")') # hello world
run_string('print(string.format("%05d", 42))') # 00042
run_string('print(string.rep("ab", 3, "-"))') # ab-ab-abfrom pylua import run_string
run_string('''
local t = {10, 20, 30, x="hello"}
for i, v in ipairs(t) do
print(i, v)
end
''')
-- 1 10
-- 2 20
-- 3 30
run_string('''
local t = {5, 3, 1, 4, 2}
table.sort(t)
print(table.concat(t, ", "))
''')
-- 1, 2, 3, 4, 5from pylua import run_string
run_string('''
local function counter(start)
local n = start
return function()
n = n + 1
return n
end
end
local c = counter(0)
print(c(), c(), c()) -- 1 2 3
''')from pylua import run_string
run_string('''
local Vec = {}
Vec.__index = Vec
function Vec.new(x, y)
return setmetatable({x=x, y=y}, Vec)
end
function Vec:len()
return math.sqrt(self.x^2 + self.y^2)
end
Vec.__add = function(a, b)
return Vec.new(a.x + b.x, a.y + b.y)
end
Vec.__tostring = function(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end
local a = Vec.new(3, 4)
local b = Vec.new(1, 2)
print(tostring(a + b)) -- (4, 6)
print(a:len()) -- 5.0
''')from pylua import run_string
run_string('''
local function fib()
local a, b = 0, 1
while true do
coroutine.yield(a)
a, b = b, a + b
end
end
local co = coroutine.wrap(fib)
local out = {}
for i = 1, 10 do out[i] = co() end
print(table.concat(out, " "))
''')
-- 0 1 1 2 3 5 8 13 21 34from pylua import run_string
run_string('''
local s = "2023-12-25"
local y, m, d = string.match(s, "(%d+)-(%d+)-(%d+)")
print(y, m, d) -- 2023 12 25
local words = {}
for w in string.gmatch("hello world foo bar", "%a+") do
words[#words+1] = w
end
print(table.concat(words, ", ")) -- hello, world, foo, bar
print(string.gsub("hello world", "(%w+)", string.upper))
-- HELLO WORLD 2
''')from pylua import run_string
run_string('''
local ok, err = pcall(function()
error("something went wrong")
end)
print(ok, err)
-- false input:3: something went wrong
local ok2, msg = xpcall(
function() error("oops") end,
function(e) return "caught: " .. e end
)
print(ok2, msg)
-- false caught: input:9: oops
''')from pylua import LuaVM, LuaError
vm = LuaVM()
# state persists across calls
vm.run_string('x = 42')
vm.run_string('print(x + 1)') # 43
# read Lua globals from Python
print(vm.globals.rawget("x")) # 42
# error handling
try:
vm.run_string('error("boom")')
except LuaError as e:
print(f"caught: {e}")from pylua import run_file
output = run_file("script.lua")
print(output)Types: nil, boolean, integer, float, string, table, function, coroutine
Operators: + - * / // % ^ & | ~ << >> == ~= < > <= >= and or not .. #
Control: if/elseif/else, while, repeat/until, for (numeric & generic), break, goto/label
Functions: closures, upvalues, varargs (...), multiple returns, tail calls
Tables: array + hash, constructors {}, metatables (20 metamethods)
Stdlib: string, table, math, io, os, coroutine
Pattern matching: find, match, gmatch, gsub with full Lua pattern syntax
Coroutines: create, resume, yield, wrap, status
pytest tests/ -v57 tests: 34 Lua files verified against lua53 reference output + 23 Python API tests.
pylua/
__init__.py Public API: LuaVM, run_string, run_file
__main__.py CLI: python -m pylua file.lua
__type.py Types, opcodes, constants
__lexer.py Lexer
__compiler.py Lua source → bytecode
__dump.py Bytecode writer
__undump.py Bytecode reader
__vm.py Virtual machine
__stdlib.py Standard library
tests/
lua/ 34 Lua test cases
__test_compiler.py
__test_api.py