chrisgve/luaswift
A lightweight Swift wrapper for Lua with optional extensions for scientific computing, data formats, and visualization.
Overview
LuaSwift embeds the Lua scripting language in iOS and macOS applications. The core wrapper is lightweight and dependency-free, bundling the complete Lua source for App Store compliance. Optional extensions add scientific computing, data formats, and visualization capabilities.
Requirements
- iOS 15.0+ / macOS 12.0+
- Swift 5.9+
- Xcode 15.0+
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ChrisGVE/LuaSwift.git", from: "1.12.3")
]Or in Xcode: File → Add Package Dependencies → Enter the repository URL.
Documentation
Full API reference and module documentation: docs/index.md
Quick links: Core API · Value Servers · Callbacks · Modules
Quick Start
Wrapper Only (No Modules)
import LuaSwift
// Create sandboxed engine (default: Lua 5.4)
let engine = try LuaEngine()
// Evaluate Lua code
let result = try engine.evaluate("return 1 + 2")
print(result.numberValue!) // 3.0
// Expose Swift data to Lua
class AppData: LuaValueServer {
let namespace = "App"
func resolve(path: [String]) -> LuaValue {
path.first == "version" ? .string("1.0") : .nil
}
}
engine.register(server: AppData())
let version = try engine.evaluate("return App.version")With Extension Modules
// Install all modules
try ModuleRegistry.install(in: engine)
// Extend standard library with LuaSwift enhancements
try engine.run("luaswift.extend_stdlib()")
// Use from Lua
try engine.run("""
-- Standard library extensions work seamlessly
local s = string.capitalize("hello world") -- "Hello world"
local keys = table.keys({a=1, b=2}) -- {"a", "b"}
-- Math submodules are available
local m = math.linalg.matrix({{1, 2}, {3, 4}})
local z = math.complex.new(3, 4)
local v = math.geo.vec3(1, 2, 3)
-- Data parsing
local data = json.decode('{"x": 1, "y": 2}')
-- Arrays (standalone module)
local a = array.zeros({3, 3})
""")Lua Version Selection
LuaSwift bundles Lua 5.1.5, 5.2.4, 5.3.6, 5.4.7, and 5.5.0. Default is 5.4.
LUASWIFT_LUA_VERSION=55 swift build # Lua 5.5
LUASWIFT_LUA_VERSION=51 swift build # Lua 5.1| Version | Env Value | Notes | |---------|-----------|-------| | 5.4.7 | 54 (default) | Recommended for new projects | | 5.5.0 | 55 | Latest, experimental features | | 5.3.6 | 53 | Integer subtype support | | 5.2.4 | 52 | Goto, bit32 library | | 5.1.5 | 51 | Maximum compatibility |
Module Reference
Standard Library Extensions
These modules extend Lua's built-in libraries. After calling luaswift.extend_stdlib(), their functions are available directly on string, table, and utf8.
| Module | Extends | Key Functions | |--------|---------|---------------| | stringx | string | capitalize, trim, split, join, startswith, endswith, replace | | tablex | table | keys, values, map, filter, reduce, merge, deepcopy | | utf8x | utf8 | sub, reverse, upper, lower, width (CJK-aware) | | compat | - | Lua version compatibility (bit32, unpack, loadstring) |
luaswift.extend_stdlib()
-- Now available on standard libraries
string.trim(" hello ") -- "hello"
table.keys({a=1, b=2}) -- {"a", "b"}
utf8.sub("日本語", 1, 2) -- "日本"Data Formats
| Module | Global | Description | |--------|--------|-------------| | json | json | JSON encode/decode with null handling | | yaml | yaml | YAML with multi-document support | | toml | toml | TOML configuration parsing |
Math (Unified Namespace)
After luaswift.extend_stdlib(), all math modules are available under the math namespace:
| Submodule | Access | Description | |-----------|--------|-------------| | Base extensions | math.* | Extended functions: sign, round, factorial, gamma | | Linear Algebra | math.linalg | Matrices, SVD, eigenvalues (BLAS/LAPACK) | | Complex Numbers | math.complex | Complex arithmetic and functions | | Geometry | math.geo | 2D/3D vectors, quaternions, transforms | | Special Functions | math.special | Bessel, gamma, erf, elliptic integrals | | Statistics | math.stats | Mean, median, variance, percentile | | Distributions | math.distributions | Normal, t, chi2, F, gamma, beta distributions | | Optimization | math.optimize | Minimization, root finding, curve fitting | | Integration | math.integrate | Numerical integration, ODE solvers | | Interpolation | math.interpolate | Splines, PCHIP, Akima | | Clustering | math.cluster | K-means, hierarchical, DBSCAN | | Spatial | math.spatial | KDTree, Voronoi, Delaunay | | Regression | math.regress | OLS, WLS, GLS, GLM, ARIMA | | Series | math.series | Taylor polynomials, summation, products | | Expressions | math.eval | Expression parsing, LaTeX support | | Constants | math.constants | Physical constants, unit conversions | | Number Theory | math.numtheory | Primes, factorization, totient |
luaswift.extend_stdlib()
-- All under math namespace
local m = math.linalg.matrix({{1, 2}, {3, 4}})
local z = math.complex.new(3, 4)
local v = math.geo.vec3(1, 2, 3)
local g = math.special.gamma(5) -- 24
local c = math.constants.c -- speed of light
local avg = math.stats.mean({1, 2, 3, 4, 5})Array (Standalone)
N-dimensional arrays with broadcasting and element-wise operations. Standalone module, not under math.
| Module | Global | Description | |--------|--------|-------------| | array | array | N-dimensional arrays with broadcasting |
local a = array.zeros({3, 3})
local b = array.linspace(0, 1, 100)
local c = a + 1 -- broadcastingVisualization
| Module | Global | Description | |--------|--------|-------------| | plot | plot | Retained-mode plotting (includes SVG generation) |
local fig = plot.figure()
local ax = fig:subplot(1, 1, 1)
ax:plot({1, 2, 3}, {1, 4, 9})
local svg_string = fig:render()Pattern Matching
| Module | Global | Description | |--------|--------|-------------| | regex | regex | ICU regular expressions |
Utilities
| Module | Global | Description | |--------|--------|-------------| | types | types | Type detection, conversion, and inspection | | debug | dbg | Structured logging with levels (debug, info, warn, error) | | serialize | serialize | Table serialization and deserialization | | svg | svg | Standalone SVG document generation |
File and Network Access
These modules require explicit installation and configuration.
| Module | Global | Description | |--------|--------|-------------| | iox | iox | Sandboxed file I/O within configured directories | | http | http | HTTP client for network requests |
// File I/O: Configure which directories Lua can access
IOModule.setAllowedDirectories([documentsPath, cachePath], for: engine)
try IOModule.install(in: engine)
// HTTP: Enable network requests
try HTTPModule.install(in: engine)The iox module restricts file operations to explicitly allowed directories—Lua scripts cannot access files outside these paths. This replaces Lua's standard io library (which is removed in sandboxed mode) with a secure alternative.
Optional Swift Package Dependencies
LuaSwift can optionally include companion Swift packages that provide enhanced implementations for specific module groups. These packages are developed as independent Swift libraries that can also be used directly without Lua.
Available Optional Packages
| Package | Environment Variable | Default | Description | |---------|---------------------|---------|-------------| | Yams | LUASWIFT_INCLUDE_YAMS | on | YAML encoding/decoding | | TOMLKit | LUASWIFT_INCLUDE_TOMLKIT | off | TOML encoding/decoding | | NumericSwift | LUASWIFT_INCLUDE_NUMERICSWIFT | off | Complex numbers, statistics, geometry, special functions | | ArraySwift | LUASWIFT_INCLUDE_ARRAYSWIFT | off | N-dimensional arrays with broadcasting | | PlotSwift | LUASWIFT_INCLUDE_PLOTSWIFT | off | Matplotlib-inspired plotting with SVG output |
Compile-Time Selection
By default only Yams is included; the other optional packages are off and must be enabled explicitly by setting their environment variable to 1 at build time. This keeps the default build lean and dependency-light.
# Include specific packages (off by default)
LUASWIFT_INCLUDE_TOMLKIT=1 swift build # Add TOMLKit (TOML)
LUASWIFT_INCLUDE_NUMERICSWIFT=1 swift build # Add NumericSwift (math.*)
LUASWIFT_INCLUDE_ARRAYSWIFT=1 swift build # Add ArraySwift (array)
LUASWIFT_INCLUDE_PLOTSWIFT=1 swift build # Add PlotSwift (plot/svg)
# Include several at once
LUASWIFT_INCLUDE_NUMERICSWIFT=1 LUASWIFT_INCLUDE_ARRAYSWIFT=1 swift build
# Full build (all optional packages)
LUASWIFT_INCLUDE_TOMLKIT=1 LUASWIFT_INCLUDE_NUMERICSWIFT=1 \
LUASWIFT_INCLUDE_ARRAYSWIFT=1 LUASWIFT_INCLUDE_PLOTSWIFT=1 swift build
# Yams is on by default; opt out with:
LUASWIFT_INCLUDE_YAMS=0 swift build # No Yams (YAML)What Each Package Provides
NumericSwift powers these Lua modules:
math.complex- Complex number arithmeticmath.geo- 2D/3D vectors and transformsmath.stats/math.distributions- Statistics and probability distributionsmath.special- Special mathematical functions (Bessel, gamma, erf, elliptic)math.numtheory- Number theory (primes, factorization)math.linalg- Linear algebra (BLAS/LAPACK via Accelerate)math.regress- Regression analysis (OLS, WLS, GLM, ARIMA)math.optimize- Minimization, root finding, curve fittingmath.integrate- Numerical integration, ODE solversmath.interpolate- Splines, PCHIP, Akima interpolationmath.cluster- K-means, hierarchical, DBSCANmath.spatial- KDTree, Voronoi, Delaunaymath.series- Taylor polynomials, summation
ArraySwift powers:
array- N-dimensional arrays with NumPy-like semantics
PlotSwift powers:
plot- Figure/axes plotting systemsvg- SVG document generation
When a package is excluded, its corresponding Lua modules are unavailable at runtime. Attempting to use them will result in a clear error message.
Xcode Integration
For Xcode projects, set environment variables in your scheme:
- Product → Scheme → Edit Scheme
- Select "Run" in the left sidebar
- Click "Arguments" tab
- Add environment variables under "Environment Variables"
Why Optional Dependencies?
- Binary size: Each package adds to the final binary; exclude what you don't need
- Build time: Fewer dependencies means faster builds
- Platform support: Some packages may have platform-specific requirements
- App Store size: iOS apps benefit from smaller binaries
Technical Note: SPM Optional Dependencies
Swift Package Manager does not have a native "optional dependency" feature like some package managers. LuaSwift uses environment variables at build time combined with conditional compilation (#if directives) to achieve optional dependencies.
For consumers who want fine-grained control, there are two approaches:
- Environment variables (current, Swift 5.9+): Set
LUASWIFT_INCLUDE_*=0before building - SPM Traits (Swift 6.1+): A newer feature that provides trait-based conditional dependencies
The environment variable approach is used for maximum compatibility. Future versions may adopt SPM Traits when Swift 6.1+ becomes the minimum supported version.
Known Limitations of Optional Dependencies
One optional dependency has an open issue affecting its opt-in build. Default builds (all flags off) are unaffected.
Thales (LUASWIFT_INCLUDE_THALES=1) — integration disabled (#18)
The Thales CAS integration is currently a no-op: the flag is forced off and the Thales-dependent code is compiled out. The upstream Thales package removed puiseuxSeries, residue, and convergenceRadius from its public API, which broke the LuaSwift bindings. The Thales source is preserved in the repository and SeriesModule retains its no-Thales fallbacks. The integration will be re-enabled once the upstream API stabilises.
Runtime Module Installation
At runtime, install only the modules you need:
let engine = try LuaEngine()
// Install specific modules
try JSONModule.install(in: engine)
try StringXModule.install(in: engine)
try MathXModule.install(in: engine)
// Or install all compiled modules
try ModuleRegistry.install(in: engine)Configuration
// Default: sandboxed, Lua 5.4, no memory limit
let engine = try LuaEngine()
// Custom configuration
let config = LuaEngineConfiguration(
sandboxed: true, // Remove dangerous functions
packagePath: "/path/to/lua", // Custom require() path
memoryLimit: 50_000_000, // 50 MB limit for Swift modules (array, linalg, etc.)
vmMemoryLimit: 64_000_000 // 64 MB ceiling on total Lua VM allocation
)
let engine = try LuaEngine(configuration: config)
// Unrestricted (use with caution)
let engine = try LuaEngine(configuration: .unrestricted)Sandboxing removes: os.execute, os.exit, io., debug., loadfile, dofile, load
Resource limits: memoryLimit bounds buffers allocated by Swift-backed modules, while vmMemoryLimit bounds total Lua VM allocation (strings, tables, etc.) via a custom allocator — a breach surfaces as LuaError.memoryError. The instruction limit (setInstructionLimit) is a CPU-bound control only: a single VM instruction calling a C function (such as string.rep('A', 1e9)) is not interrupted by it, so pair it with vmMemoryLimit when running untrusted code.
Cancellation: Call engine.requestCancellation() from any thread to abort a running script; the run throws LuaError.cancelled. Call engine.resetCancellation() after both .cancelled and .instructionLimitExceeded before running again.
Structured errors: Runtime errors during run() / evaluate() throw LuaError.runtimeFailure(LuaRuntimeFailure) with a stripped message, source line, full traceback, and a frames array. Coroutine errors (from resume) surface as the plain LuaError.runtimeError(String).
Debug hook: engine.setDebugHandler { event, inspector in … } installs a LINE/CALL/RET handler; engine.runDebug(…) activates it. The inspector gives read-only access to locals, upvalues, globals, and the call stack at each pause point. Use LuaDebugCommand to continue, step, or stop. See Core API — Debug Hook API for details. The inspector is unbounded by default — when debugging untrusted code, build with LUASWIFT_BOUNDED_INSPECTION=1 to cap per-table breadth and avoid a debugger-only memory-exhaustion DoS (details).
Introspection: Between runs, inspect live engine state without executing code — engine.registeredValueServerNames, registeredFunctionNames, installedModuleNames, and raw globals via globalNames(includingStandardLibrary:) / globalValue(_:) (no metamethods). See Core API — Engine Introspection.
Chunk names: engine.precompile(:chunkName:) and engine.run(:chunkName:) accept an optional chunkName that appears in error messages and tracebacks for easier debugging.
App Store Compliance
LuaSwift is designed to be App Store compliant per Apple's App Store Review Guidelines 2.5.2.
Core Compliance
- Bundled interpreter: Lua source compiled into app (no code download)
- No JIT: Standard interpreter only, not LuaJIT
- Sandboxing: Dangerous functions disabled by default
Sandbox Security
The default sandboxed mode provides defense-in-depth:
| Disabled | Reason | |----------|--------| | os.execute, os.exit | System command execution | | io. library | File system access | | debug. library | Runtime introspection | | loadfile, dofile, load | External code loading | | package.loadlib | Dynamic library loading | | require('io'), require('debug') | Library restoration bypass |
Security Warnings
packagePath Security: Never set
packagePathto a writable directory (Documents, Caches, tmp) when running user-provided scripts. This would allow downloaded Lua code to be executed viarequire(), violating App Store guidelines.
// SAFE: Read-only bundle resources
let config = LuaEngineConfiguration(
sandboxed: true,
packagePath: Bundle.main.resourcePath! + "/Lua"
)
// UNSAFE: Writable directory - don't do this with untrusted scripts
let config = LuaEngineConfiguration(
sandboxed: true,
packagePath: documentsDirectory // Dangerous!
)App Store Submission Checklist
Before submitting to the App Store, verify:
- [ ] Use sandboxed mode (default) for any user-provided scripts
- [ ] If using
packagePath, ensure it points only to read-only directories - [ ] Validate all inputs to Swift callbacks registered with Lua
- [ ] If using IOModule, restrict
allowedDirectoriesto app-controlled paths - [ ] Consider implementing script execution timeouts for untrusted code
- [ ] Don't combine HTTP + IO modules with
packagePathfor untrusted scripts
See the Sandboxing Guide for detailed security information.
License
Apache License 2.0. See LICENSE for details.
Lua is MIT licensed. See https://www.lua.org/license.html
Acknowledgments
LuaSwift's scientific computing modules are inspired by excellent open-source libraries:
- NumPy - N-dimensional array design and broadcasting semantics
- SciPy - Optimization, integration, interpolation, and special functions
- statsmodels - Statistical modeling and regression analysis
- matplotlib - Plotting API design
- Penlight - String and table utility patterns
Required Dependencies:
- Lua - The Lua programming language (bundled)
- swift-atomics - Lock-free flags for cancellation/debug coordination
- Yams - YAML parsing for Swift
- TOMLKit - TOML parsing for Swift
Optional Dependencies:
- NumericSwift - Complex numbers, statistics, geometry
- ArraySwift - N-dimensional arrays
- PlotSwift - Plotting and SVG generation
Package Metadata
Repository: chrisgve/luaswift
Default branch: main
README: README.md