run method
Implementation
Future<void> run() async {
// Disable flushing in the Lua print function to avoid stream conflicts
LuaLikeConfig().flushAfterPrint = false;
final history = ReplHistory();
// Load history from file first
try {
history.loadFromFile();
if (debugMode) {
print('Loaded ${history.length} commands from history');
}
} catch (e) {
if (debugMode) {
print('Error loading history: $e');
}
}
// Create a console for terminal control with scrolling support
final console = Console.scrolling(recordBlanks: false);
// Set up virtual devices for REPL I/O
final stdinDevice = VirtualIODevice();
final stdoutDevice = ConsoleOutputDevice(console);
IOLib.defaultInput = createLuaFile(stdinDevice);
IOLib.defaultOutput = createLuaFile(stdoutDevice);
// Custom print function that writes to our buffer instead of stdout
void customPrint(String message) {
stdoutDevice.write('$message\n');
}
// Print welcome message
customPrint(
'LuaLike $lualikeVersion (Lua $luaCompatVersion compatible) AST mode',
);
customPrint('Type "exit" to quit');
// REPL loop
bool running = true;
String? multilineBuffer;
while (running) {
try {
// Clear the output buffer before each command
multilineBuffer = null;
// Get custom prompt from _PROMPT global variable
final promptValue = bridge.getGlobal('_PROMPT');
String basePrompt = '> ';
if (promptValue != null && promptValue is String) {
basePrompt = promptValue;
}
// Determine prompt based on whether we're in multiline mode
String prompt = multilineBuffer != null ? '>> ' : basePrompt;
// Read input line using console.readLine which supports history navigation
console.write(prompt);
final line = console.readLine(
cancelOnBreak: true,
callback: (text, lastPressed) {
if (lastPressed.isControl) {
if (lastPressed.controlChar == ControlCharacter.ctrlC) {
running = false;
}
}
},
);
// Check for exit or EOF
if (line == null ||
(multilineBuffer == null && line.toLowerCase() == 'exit')) {
running = false;
continue;
}
// Write input to the virtual stdin device
stdinDevice.write('$line\n');
// Add to history if not empty
if (line.isNotEmpty) {
history.add(line);
}
// Handle multiline input
String codeToExecute;
if (multilineBuffer != null) {
// We're in multiline mode
if (line.trim().isEmpty) {
// Empty line ends multiline input
codeToExecute = multilineBuffer;
multilineBuffer = null;
} else {
// Add to multiline buffer
multilineBuffer += '\n$line';
continue;
}
} else {
// Check if this might be the start of multiline input
if (line.trim().endsWith('{') ||
line.trim().endsWith('(') ||
line.trim().endsWith('do') ||
line.trim().endsWith('then') ||
line.trim().endsWith('else') ||
line.trim().endsWith('function') ||
line.trim().endsWith('=')) {
multilineBuffer = line;
continue;
}
codeToExecute = line;
}
// Execute the code
Object? result;
try {
result = await bridge.execute(codeToExecute);
} on ReturnException catch (e) {
result = e.value;
} catch (e) {
if (e.toString().contains('unexpected symbol near') ||
e.toString().contains('syntax error')) {
// This might be an incomplete statement, try multiline mode
if (multilineBuffer == null) {
multilineBuffer = codeToExecute;
continue;
} else {
// If we're already in multiline mode and still have syntax errors,
// show the error and reset
String errorMsg = e.toString();
if (errorMsg.startsWith('Exception: ')) {
errorMsg = errorMsg.substring('Exception: '.length);
}
customPrint('stdin:1: $errorMsg');
multilineBuffer = null;
continue;
}
} else {
// Other errors
String errorMsg = e.toString();
if (errorMsg.startsWith('Exception: ')) {
errorMsg = errorMsg.substring('Exception: '.length);
}
customPrint('stdin:1: $errorMsg');
continue;
}
}
// Print the result
customPrint('= ${_formatValue(result)}');
} catch (e, stack) {
customPrint('Error: $e');
if (debugMode) {
customPrint('Stack trace: $stack');
}
// Reset multiline buffer on error
multilineBuffer = null;
}
}
// Save history to file before exiting
try {
history.saveToFile();
} catch (e) {
if (debugMode) {
customPrint('Error saving history: $e');
}
}
customPrint('\nGoodbye!');
}