run method
Parses args and invokes Command.run on the chosen command.
This always returns a Future in case the command is asynchronous. The
Future will throw a UsageException if args was invalid.
Implementation
@override
Future<void> run(Iterable<String> args) async {
// artisanal.run(args) handles global setup and catches UsageException.
// However, it expects subcommands. Since Lua uses flags for logic,
// we need to handle the parsed results if no command matches.
// Setup renderer etc by calling artisanal's run logic via super.run
// But super.run will likely throw if we have no commands.
// Let's use artisanal's public properties to mimic its behavior.
final argResults = argParser.parse(args);
if (argResults['help'] as bool) {
printUsage();
return;
}
if (argResults['version'] as bool) {
final versionCmd = VersionCommand();
await versionCmd.run();
if (args.length == 1) return;
}
final restArgs = argResults.rest;
final config = LuaLikeConfig();
final useIr = argResults['ir'] as bool;
final useLuaBytecode = argResults['lua-bytecode'] as bool;
final useAst = argResults['ast'] as bool;
// Precompiled chunks (detected by official Lua header bytes, not extension)
// must use the bytecode VM so ScriptCommand can load+run without IR/SSA.
final autoUseLuaBytecode =
restArgs.isNotEmpty &&
_looksLikeTrackedLuaBytecodeScript(restArgs.first);
if (useLuaBytecode || autoUseLuaBytecode) {
config.defaultEngineMode = EngineMode.luaBytecode;
} else if (useIr || !useAst) {
config.defaultEngineMode = EngineMode.ir;
} else {
config.defaultEngineMode = EngineMode.ast;
}
config.dumpIr = argResults['dump-ir'] as bool;
config.emitLlvm = argResults['emit-llvm'] as bool;
config.emitDart = argResults['emit-dart'] as bool;
config.foldEnabled = argResults['fold'] as bool;
// Handle --disassemble (print bytecode listing, no execution)
if (argResults['disassemble'] as bool) {
if (restArgs.isEmpty) {
stderr.writeln('Error: --disassemble requires a script file argument.');
exit(1);
}
final scriptPath = restArgs.first;
final bytes = File(scriptPath).readAsBytesSync();
try {
// Official header → parse as binary chunk
if (bytes.isNotEmpty && bytes.first == 0x1B) {
final chunk = const LuaBytecodeParser().parse(bytes);
print(const LuaBytecodeDisassembler().render(chunk));
} else {
final source = _decodeSource(bytes);
final program = parse(source, url: scriptPath);
final rawBc = argResults['raw'] as bool;
final artifact =
CompilePipeline(
config: CompilePipelineConfig.luaBytecodeOptimized(
enableBytecodePeephole: !rawBc,
),
).compile(program)
as LuaBytecodeArtifact;
print(const LuaBytecodeDisassembler().render(artifact.chunk));
}
} catch (e, st) {
stderr.writeln('Disassembly failed: $e');
stderr.writeln(st);
exit(1);
}
exit(0);
}
// Handle --compile (compile-only, no execution)
if (argResults['compile'] as bool) {
if (restArgs.isEmpty) {
stderr.writeln('Error: --compile requires a script file argument.');
exit(1);
}
final scriptPath = restArgs.first;
// No standard extension for binary chunks — caller must name the file.
final outputPath = argResults['output'] as String?;
if (outputPath == null || outputPath.isEmpty) {
stderr.writeln(
'Error: --compile requires --output / -o <path> '
'(no default extension; binary is detected by header).',
);
exit(1);
}
_compileToBytecode(
scriptPath,
outputPath,
dartOutputPath: argResults['dart-output'] as String?,
preserveDebug: argResults['preserve-debug'] as bool,
);
return;
}
// Handle --check-env (verify native compilation toolchain)
if (argResults['check-env'] as bool) {
stderr.writeln('Checking native compilation environment...');
try {
final r = await Process.run('dart', [
'run',
'tool/compile_llvm.dart',
'--help',
]);
if (r.exitCode == 0) {
stderr.writeln(' compile_llvm.dart: OK (${r.exitCode})');
}
} catch (_) {
stderr.writeln(' compile_llvm.dart: not found in tool/');
}
for (final tool in ['zig', 'llc', 'clang']) {
try {
final r = await Process.run('which', [tool]);
stdout.writeln(' $tool: ${r.stdout.toString().trim()}');
} catch (_) {
stderr.writeln(' $tool: not found');
}
}
exit(0);
}
// Handle --native (LLVM native compilation)
if (argResults['native'] as bool) {
if (restArgs.isEmpty) {
stderr.writeln('Error: --native requires a script file argument.');
exit(1);
}
final scriptPath = restArgs.first;
if (!File(scriptPath).existsSync()) {
stderr.writeln('File not found: $scriptPath');
exit(1);
}
final outputPath = argResults['output'] as String?;
if (outputPath == null || outputPath.isEmpty) {
stderr.writeln('Error: --native requires --output / -o <path>');
exit(1);
}
await checkEnvironment();
final out = await compileLuaToNative(
scriptPath: scriptPath,
outputPath: outputPath,
);
stderr.writeln('Done: $out');
stderr.writeln('Run: $out');
exit(0);
}
BaseCommand.resetBridge();
final emitDocsFormat = argResults['emit-docs'] as String?;
if (emitDocsFormat != null) {
await _emitDocs(
format: emitDocsFormat,
output: argResults['emit-docs-output'] as String?,
);
return;
}
// Handle debug mode
if (argResults['debug'] as bool) {
debugMode = true;
io.writeln('Debug mode enabled');
}
// Set up logging
final logLevel = argResults['level'] as String?;
final logCategories =
(argResults['category'] as List<String>?) ?? const <String>[];
ctx.Level? cliLevel;
if (logLevel != null && logLevel.isNotEmpty) {
cliLevel = parseLogLevel(logLevel) ?? ctx.Level.warning;
}
setLualikeLogging(
enabled: debugMode,
level: cliLevel,
categories: logCategories.isEmpty ? null : logCategories,
);
// Handle special case where no arguments provided
if (args.isEmpty) {
if (stdin.hasTerminal) {
// Terminal mode: show version and enter interactive mode
final versionCmd = VersionCommand();
await versionCmd.run();
final interactiveCmd = InteractiveCommandWrapper(debugMode: debugMode);
await interactiveCmd.run();
} else {
// Non-terminal mode: read from stdin
final stdinCmd = StdinCommand([], args.toList());
await stdinCmd.run();
}
return;
}
// Handle special case for single '-' argument (stdin)
if (args.length == 1 && args.first == '-') {
final stdinCmd = StdinCommand([], args.toList());
await stdinCmd.run();
return;
}
// Handle LUA_INIT using BaseCommand functionality
final baseCmd = _LuaInitCommand();
await baseCmd.handleLuaInit();
// Handle require files (-l)
final requireFiles = argResults['require'] as List<String>;
for (final file in requireFiles) {
final requireCmd = RequireCommand(file, args.toList());
await requireCmd.run();
}
// Handle execute strings (-e)
final executeStrings = argResults['execute'] as List<String>;
for (final code in executeStrings) {
final executeCmd = ExecuteCommand(code, args.toList());
await executeCmd.run();
}
// Handle --lua-test (runs a Lua test suite file with standard env)
final luaTest = argResults['lua-test'] as String?;
if (luaTest != null) {
final testFile = luaTest.endsWith('.lua') ? luaTest : '$luaTest.lua';
final testPath = 'luascripts/test/$testFile';
final initCode =
'_port = true; _soft = true; '
"package.path = 'luascripts/test/?.lua;luascripts/test/?/init.lua;?.lua;;'; "
"dofile('$testPath')";
final executeCmd = ExecuteCommand(initCode, args.toList());
await executeCmd.run();
}
// Handle script file and arguments
if (restArgs.isNotEmpty) {
final scriptPath = restArgs.first;
final scriptArgs = restArgs.skip(1).toList();
final scriptCmd = ScriptCommand(scriptPath, scriptArgs, args.toList());
await scriptCmd.run();
}
// Handle interactive mode (-i)
if (argResults['interactive'] as bool) {
final interactiveCmd = InteractiveCommandWrapper(debugMode: debugMode);
await interactiveCmd.run();
}
}