lualike 0.2.2
lualike: ^0.2.2 copied to clipboard
A Lua-like scripting language for Dart, designed to be easy to embed and extend. It provides a simple REPL, supports Lua-like syntax, and allows for seamless integration with Dart code.
LuaLike #
LuaLike is an embeddable Lua-like runtime and tooling package for Dart.
It includes a high-level bridge for running scripts from Dart, AST parsing APIs, low-level parser utilities, and the same standard-library registration surface used by the built-in string, table, math, and debug libraries.
What this package exposes #
package:lualike/lualike.dartMain entrypoint for embedding LuaLike, running code, selecting an engine, parsing source into ASTs, and working with values and errors.package:lualike/parsers.dartLower-level parsers for Lua format strings, binary pack formats, string parsing helpers, and Lua patterns.package:lualike/library_builder.dartPublic extension surface for registering libraries and builder-style native APIs from Dart.
Install #
dependencies:
lualike: ^0.0.1-alpha.2
Then run:
dart pub get
Quick start #
import 'package:lualike/lualike.dart';
Future<void> main() async {
final lua = LuaLike();
lua.expose('greet', (List<Object?> args) {
final name = args.isEmpty ? 'world' : Value.wrap(args.first).unwrap();
return Value('Hello, $name!');
});
final result = await lua.execute('''
local total = 0
for i = 1, 4 do
total = total + i
end
return greet(total)
''');
print((result as Value).unwrap());
}
Features #
Dart ↔ Lua interop #
Expose Dart functions and call them from Lua, or call Lua functions from Dart. Share complex data structures like maps, lists, and tables between the two languages.
lualike.expose('getCurrentTime', () => DateTime.now().toString());
lualike.expose('pow', (num x, num y) => x * y);
await lualike.execute('''
print("2^8 =", pow(2, 8))
print("Time:", getCurrentTime())
''');
See example/interop_example.dart.
Lua tables as Dart maps #
Lua tables are exposed as Dart Map objects, so you can read, write, and pass them back and forth seamlessly.
// Send a config map to Lua
lua.setGlobal('config', {
'debug': true,
'maxRetries': 3,
});
// Read tables back from Lua
final summary = lua.getGlobal('summary')?.unwrap() as Map;
print(summary['playerLevel']);
This works for complex nested structures too — see example/dart_library_example.dart.
Error handling with pcall / xpcall #
Both synchronous and asynchronous Dart functions work with Lua's pcall and xpcall error handling, including nested protected calls.
await bridge.execute('''
local status, result = pcall(function()
local inner, err = pcall(function()
error("inner error")
end)
return inner
end)
''');
See example/error_handling_example.dart.
Virtual file system and modules #
Register virtual files so Lua's require can load them without a physical filesystem:
lua.fileManager.registerVirtualFile('mathutils.lua', '''
local M = {}
function M.factorial(n)
if n <= 1 then return 1 end
return n * M.factorial(n - 1)
end
return M
''');
await lua.execute('local m = require("mathutils"); print(m.factorial(5))');
See moduleExample() in example/dart_library_example.dart.
Custom I/O backend #
For web platforms or testing, the io library's physical file backend can be
replaced with an in-memory adapter. The web/main.dart demo
does this with IOLib.fileSystemProvider.setIODeviceFactory(). Check the
FileSystemProvider and
InMemoryIODevice source for the pattern.
AST parsing #
Parse Lua source into an AST tree without executing it, then traverse or transform the tree.
final program = parse('''
local answer = 42
return answer
''', url: 'example.lua');
final expression = parseExpression('a + b * c');
print(program.statements.length);
print(expression.runtimeType);
See example/lualike_example.dart.
Choose an engine #
LuaLike currently ships three execution modes:
EngineMode.astParses source and runs it directly with the AST interpreter.EngineMode.luaBytecodeEmits Lua-compatible bytecode and runs it through the bytecode VM. It currently passes the Lua compatibility suite, but it is still slower thanEngineMode.ast.EngineMode.irRuns the experimental IR pipeline.
You can select an engine per call:
import 'package:lualike/lualike.dart';
Future<void> main() async {
final astResult = await executeCode('return 20 + 22');
final bytecodeResult = await executeCode(
'return 20 + 22',
mode: EngineMode.luaBytecode,
);
print((astResult as Value).unwrap());
print((bytecodeResult as Value).unwrap());
}
Or set a process-wide default:
import 'package:lualike/lualike.dart';
void main() {
LuaLikeConfig().defaultEngineMode = EngineMode.luaBytecode;
}
Parse without executing #
If you only need the syntax tree, use the exported parsing helpers instead of the runtime bridge:
import 'package:lualike/lualike.dart';
void main() {
final program = parse('''
local answer = 42
return answer
''', url: 'example.lua');
final expression = parseExpression('a + b * c');
print(program.statements.length);
print(expression.runtimeType);
print(luaChunkId('example.lua'));
}
Use package:lualike/parsers.dart when you want the reusable parser implementations behind helpers such as string.format, string.pack, or Lua pattern handling.
Extend LuaLike from Dart #
The simplest extension point is LuaLike.expose():
import 'package:lualike/lualike.dart';
Future<void> main() async {
final lua = LuaLike();
lua.expose('double', (List<Object?> args) {
final value = Value.wrap(args.first).unwrap() as num;
return Value(value * 2);
});
final result = await lua.execute('return double(21)');
print((result as Value).unwrap());
}
For reusable namespaced libraries, use package:lualike/library_builder.dart:
import 'package:lualike/library_builder.dart';
import 'package:lualike/lualike.dart';
class GreetingLibrary extends Library {
@override
String get name => 'greeting';
@override
void registerFunctions(LibraryRegistrationContext context) {
final builder = BuiltinFunctionBuilder(context);
context.define('hello', builder.create((args) {
final who = args.isEmpty ? 'world' : Value.wrap(args.first).unwrap();
return Value('hello, $who');
}));
// Inline documentation for IDE completion and doc generation:
context.describe('hello', FunctionDoc(
summary: 'Returns a greeting.',
params: [DocParam('who', 'string', 'Name to greet.', optional: true)],
returns: 'string',
category: 'greeting',
));
}
}
Future<void> main() async {
final lua = LuaLike();
lua.register(GreetingLibrary()); // shorthand for register + initialize
final result = await lua.execute('return greeting.hello("LuaLike")');
print((result as Value).unwrap());
}
This is the same registration path used by the built-in libraries in the repository. Add inline FunctionDoc metadata via context.describe() to power IDE completions and documentation generation (see doc/lsp.md).
Generate table schema docs from Dart annotations #
Annotate your Dart classes with @TableSchema() / @SchemaField() and use the table_schema builder to auto-generate TableDoc constants — no manual FieldDoc boilerplate.
1. Annotate a class
import 'package:lualike/annotations.dart';
@TableSchema(description: 'Metadata table every plugin must export.')
class PluginManifest {
@SchemaField(description: 'Unique plugin identifier.', required: true)
final String id;
@SchemaField(description: 'Semantic version string.', required: true)
final String version;
@SchemaField(
description: 'Runtime capabilities required.',
type: 'string[]',
defaultValue: [],
)
final List<String> capabilities;
}
Type inference is automatic — the List<String> above maps to array unless you pass an explicit type: override. Supported: String → string, int → integer, double/num → number, bool → boolean, List → array, Map → table.
2. Configure build.yaml
targets:
$default:
builders:
lualike|table_schema:
enabled: true
generate_for:
- "lib/**_schema.dart"
3. Run the builder
dart run build_runner build
This produces a .table_schema.g.dart file next to each matching source file:
// GENERATED CODE — DO NOT MODIFY BY HAND.
final pluginManifest = TableDoc(
name: 'PluginManifest',
description: 'Metadata table every plugin must export.',
fields: [
FieldDoc(key: 'id', type: 'string',
description: 'Unique plugin identifier.', required: true),
FieldDoc(key: 'version', type: 'string',
description: 'Semantic version string.', required: true),
FieldDoc(key: 'capabilities', type: 'string[]',
description: 'Runtime capabilities required.',
defaultValue: [], required: false),
],
);
4. Register in your library
class MyLibrary extends Library {
@override
String get name => 'my_library';
@override
void registerFunctions(LibraryRegistrationContext context) {
context.describeTable('PluginManifest', pluginManifest);
}
}
The annotated fields, types, defaults, and constraints are all included in the generated output and appear in both LuaLS annotations and JSON manifests. See example/builder_demo for a complete walkthrough covering annotations, functions, ValueClass, constants, and build_runner automation.
Examples #
Run any example directly:
dart run example/interop_example.dart
dart run example/error_handling_example.dart
dart run example/dart_library_example.dart
dart run example/lualike_example.dart
| File | Demonstrates |
|---|---|
| interop_example.dart | Exposing Dart functions, calling Lua from Dart, sharing data |
| error_handling_example.dart | pcall, xpcall, async error handling, nested protected calls |
| dart_library_example.dart | Full-featured: basic usage, value exchange, tables, modules, config, custom functions |
| lualike_example.dart | AST parsing: method syntax, varargs, complex source snippets |
| builder_demo | Build runner integration, @TableSchema annotations, generated docs, and library registration |
LSP support for your scripts #
lualike generates LuaLS-compatible annotation stubs so your editor can provide completion, hover docs, and signature help for lualike scripts — including custom libraries you register from Dart.
Built-in stdlib only #
dart run bin/main.dart --emit-docs luals --emit-docs-output annotations.lua
Your own libraries #
Create tool/generate_metadata.dart:
import 'package:lualike/lualike.dart';
import 'package:lualike/docs.dart';
import 'package:your_project/your_project.dart';
Future<void> main() async {
final lua = LuaLike();
lua.vm.libraryRegistry.register(MyGameLibrary());
await generateMetadata(
lua,
outputDir: 'doc/api',
formats: {MetadataFormat.luals},
);
}
Then point your LuaLS workspace library at the generated file. See
doc/lsp.md for editor-specific configuration (Neovim, VS Code,
.luarc.json).
Guides and reference material #
Repository guides:
- Embedding LuaLike in Dart
- Value handling
- Error handling
- Metatables and metamethods
- Writing builtin functions
- Standard library architecture
- Builder-style library pattern
Examples and source:
Notes on public API stability #
The supported API surface is the set of symbols exported by package:lualike/lualike.dart, package:lualike/parsers.dart, and package:lualike/library_builder.dart.
The lib/src/ tree is still visible in the repository for people who want to study or borrow implementations, but those files should be treated as internal details unless they are re-exported through one of the public libraries above.