What is the REPL?
The REPL is an interactive shell that:- Reads user input (Lua code)
- Evaluates the code
- Prints the result
- Loops back for more input
- Testing Lua code snippets
- Experimenting with APIs
- Debugging scripts interactively
- Learning Lua syntax
- Prototyping game logic
Using the CLI REPL
Starting the REPL
Run the SolarSharp CLI without arguments:# Start interactive REPL
solarsharp
# Run a specific script file
solarsharp myscript.lua
# Execute a command
solarsharp -X "help"
# Show help
solarsharp -H
REPL Interface
When you start the REPL, you’ll see:SolarSharp 2.0.0.0 [Lua 5.2]
Running on .NET ...
Type Lua code to execute it or type !help to see help on commands.
Welcome.
>
Basic Usage
> print("Hello, SolarSharp!")
Hello, SolarSharp!
> x = 5
> y = 10
> x + y
15
> function greet(name)
>> return "Hello, " .. name
>> end
> greet("World")
Hello, World
Return Value Shortcuts
Use= prefix for expression evaluation (like standard Lua):
> = 2 + 2
4
> = math.sin(math.pi / 2)
1.0
> = { 1, 2, 3, 4, 5 }
table: 0x...
REPL Commands
Commands start with!:
> !help
# Shows available commands
> !exit
# Exits the REPL
> !run myfile.lua
# Runs a Lua file
Implementing Custom REPL
You can easily create your own REPL usingReplInterpreter:
Basic Custom REPL
using System;
using SolarSharp.Interpreter;
using SolarSharp.Interpreter.DataTypes;
using SolarSharp.Interpreter.Errors;
using SolarSharp.Interpreter.REPL;
public class SimpleREPL
{
public static void Main()
{
Script script = new Script();
ReplInterpreter repl = new ReplInterpreter(script)
{
HandleClassicExprsSyntax = true // Enable '=' prefix
};
Console.WriteLine("Simple Lua REPL - Type 'exit' to quit\n");
while (true)
{
// Show prompt
Console.Write(repl.ClassicPrompt + " ");
// Read input
string input = Console.ReadLine();
// Check for exit
if (input == "exit") break;
try
{
// Evaluate
LuaValue result = repl.Evaluate(input);
// Print result (if not null and not void)
if (result != null && result.Type != DataType.Void)
{
Console.WriteLine(result);
}
}
catch (InterpreterException ex)
{
// Handle errors
Console.WriteLine($"Error: {ex.DecoratedMessage ?? ex.Message}");
}
}
}
}
Advanced REPL with Features
using System;
using System.Collections.Generic;
using SolarSharp.Interpreter;
using SolarSharp.Interpreter.DataTypes;
using SolarSharp.Interpreter.Errors;
using SolarSharp.Interpreter.REPL;
using SolarSharp.Interpreter.Modules;
public class AdvancedREPL
{
private Script script;
private ReplInterpreter repl;
private List<string> history;
public AdvancedREPL()
{
// Initialize with all modules
script = new Script(CoreModules.Preset_Complete);
// Set up debug output
script.Options.DebugPrint = s => Console.WriteLine(s);
// Create REPL
repl = new ReplInterpreter(script)
{
HandleClassicExprsSyntax = true
};
history = new List<string>();
// Register custom functions
RegisterCustomFunctions();
}
private void RegisterCustomFunctions()
{
// Add a function to make .NET types available
script.Globals["import"] = (Func<string, LuaValue>)ImportType;
// Add history access
script.Globals["history"] = (Action)ShowHistory;
}
private LuaValue ImportType(string typeName)
{
var type = Type.GetType(typeName);
if (type == null)
{
Console.WriteLine($"Type '{typeName}' not found.");
return LuaValue.Nil;
}
return UserData.CreateStatic(type);
}
private void ShowHistory()
{
Console.WriteLine("\n=== Command History ===");
for (int i = 0; i < history.Count; i++)
{
Console.WriteLine($"{i + 1}: {history[i]}");
}
Console.WriteLine();
}
public void Run()
{
PrintBanner();
while (true)
{
try
{
// Show appropriate prompt
Console.Write(repl.ClassicPrompt + " ");
string input = Console.ReadLine();
// Handle commands
if (input != null && input.StartsWith("!"))
{
if (HandleCommand(input[1..]))
break; // Exit if command returns true
continue;
}
// Add to history
if (!string.IsNullOrWhiteSpace(input))
{
history.Add(input);
}
// Evaluate
LuaValue result = repl.Evaluate(input);
// Print result
if (result != null && result.Type != DataType.Void)
{
Console.WriteLine(FormatResult(result));
}
}
catch (SyntaxErrorException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Syntax Error: {ex.DecoratedMessage ?? ex.Message}");
Console.ResetColor();
}
catch (ScriptRuntimeException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Runtime Error: {ex.DecoratedMessage ?? ex.Message}");
Console.ResetColor();
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"Error: {ex.Message}");
Console.ResetColor();
}
}
}
private bool HandleCommand(string cmd)
{
string[] parts = cmd.Split(' ', 2);
string command = parts[0].ToLower();
string args = parts.Length > 1 ? parts[1] : "";
switch (command)
{
case "exit":
case "quit":
return true;
case "help":
ShowHelp();
break;
case "clear":
Console.Clear();
PrintBanner();
break;
case "history":
ShowHistory();
break;
case "load":
LoadScript(args);
break;
case "save":
SaveHistory(args);
break;
default:
Console.WriteLine($"Unknown command: {command}");
Console.WriteLine("Type !help for available commands.");
break;
}
return false;
}
private void PrintBanner()
{
Console.WriteLine(Script.GetBanner("Console"));
Console.WriteLine();
Console.WriteLine("Enhanced Lua REPL");
Console.WriteLine("Type Lua code or !help for commands\n");
}
private void ShowHelp()
{
Console.WriteLine("\n=== Available Commands ===");
Console.WriteLine("!help - Show this help");
Console.WriteLine("!exit - Exit the REPL");
Console.WriteLine("!clear - Clear screen");
Console.WriteLine("!history - Show command history");
Console.WriteLine("!load FILE - Load and execute a Lua file");
Console.WriteLine("!save FILE - Save command history");
Console.WriteLine();
Console.WriteLine("=== Special Functions ===");
Console.WriteLine("import(typename) - Import a .NET type");
Console.WriteLine("history() - Show history from Lua");
Console.WriteLine();
}
private void LoadScript(string filename)
{
if (string.IsNullOrWhiteSpace(filename))
{
Console.WriteLine("Usage: !load <filename>");
return;
}
try
{
script.DoFile(filename);
Console.WriteLine($"Loaded: {filename}");
}
catch (Exception ex)
{
Console.WriteLine($"Error loading file: {ex.Message}");
}
}
private void SaveHistory(string filename)
{
if (string.IsNullOrWhiteSpace(filename))
{
Console.WriteLine("Usage: !save <filename>");
return;
}
try
{
System.IO.File.WriteAllLines(filename, history);
Console.WriteLine($"History saved to: {filename}");
}
catch (Exception ex)
{
Console.WriteLine($"Error saving history: {ex.Message}");
}
}
private string FormatResult(LuaValue value)
{
return value.Type switch
{
DataType.Number => value.Number.ToString(),
DataType.String => value.String,
DataType.Boolean => value.Boolean.ToString().ToLower(),
DataType.Table => FormatTable(value.Table),
_ => value.ToString()
};
}
private string FormatTable(Table table)
{
// Simple table formatting
if (table.Length == 0)
return "{}";
var items = new List<string>();
foreach (var pair in table.Pairs)
{
items.Add($"{pair.Key} = {pair.Value}");
}
return "{ " + string.Join(", ", items) + " }";
}
}
// Usage
class Program
{
static void Main()
{
var repl = new AdvancedREPL();
repl.Run();
}
}
ReplInterpreter API
Key Properties
ReplInterpreter repl = new ReplInterpreter(script);
// Enable '=' prefix for expressions
repl.HandleClassicExprsSyntax = true;
// Check if there's pending input (multiline)
bool pending = repl.HasPendingCommand;
// Get current pending command
string current = repl.CurrentPendingCommand;
// Get appropriate prompt (> or >>)
string prompt = repl.ClassicPrompt;
Evaluate Method
// Returns:
// - LuaValue result if code is complete and executes
// - null if more input is needed
// - Throws exception on error
LuaValue result = repl.Evaluate(input);
if (result == null)
{
// Need more input (incomplete code)
Console.Write(">> ");
}
else if (result.Type != DataType.Void)
{
// Show result
Console.WriteLine(result);
}
Multiline Input Handling
The REPL automatically handles multiline input:> function factorial(n)
>> if n <= 1 then
>> return 1
>> else
>> return n * factorial(n - 1)
>> end
>> end
> factorial(5)
120
while (true)
{
Console.Write(repl.ClassicPrompt + " ");
string input = Console.ReadLine();
try
{
LuaValue result = repl.Evaluate(input);
if (result == null)
{
// More input needed - loop continues with >> prompt
continue;
}
if (result.Type != DataType.Void)
{
Console.WriteLine(result);
}
}
catch (SyntaxErrorException ex)
{
// Only show error if not expecting more input
if (!ex.IsPrematureStreamTermination)
{
Console.WriteLine($"Syntax error: {ex.DecoratedMessage}");
}
}
}
Unity Integration
Create an in-game debug console:using UnityEngine;
using SolarSharp.Interpreter;
using SolarSharp.Interpreter.REPL;
using SolarSharp.Interpreter.Errors;
public class UnityREPL : MonoBehaviour
{
private Script script;
private ReplInterpreter repl;
private string input = "";
private string output = "";
private Vector2 scrollPos;
private bool showConsole = false;
void Start()
{
script = new Script();
// Expose Unity objects
script.Globals["GameObject"] = typeof(GameObject);
script.Globals["Debug"] = typeof(Debug);
repl = new ReplInterpreter(script)
{
HandleClassicExprsSyntax = true
};
script.Options.DebugPrint = s => output += s + "\n";
}
void Update()
{
// Toggle console with tilde key
if (Input.GetKeyDown(KeyCode.BackQuote))
{
showConsole = !showConsole;
}
}
void OnGUI()
{
if (!showConsole) return;
GUILayout.BeginArea(new Rect(10, 10, Screen.width - 20, Screen.height / 2));
// Output area
scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Height(300));
GUILayout.Label(output);
GUILayout.EndScrollView();
// Input area
GUILayout.BeginHorizontal();
GUILayout.Label(repl.ClassicPrompt);
input = GUILayout.TextField(input, GUILayout.Width(Screen.width - 100));
if (GUILayout.Button("Run") || (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Return))
{
ExecuteInput();
}
GUILayout.EndHorizontal();
if (GUILayout.Button("Clear"))
{
output = "";
}
GUILayout.EndArea();
}
void ExecuteInput()
{
if (string.IsNullOrWhiteSpace(input)) return;
output += repl.ClassicPrompt + " " + input + "\n";
try
{
var result = repl.Evaluate(input);
if (result != null && result.Type != DataType.Void)
{
output += result + "\n";
}
input = "";
}
catch (InterpreterException ex)
{
output += $"Error: {ex.DecoratedMessage ?? ex.Message}\n";
input = "";
}
// Scroll to bottom
scrollPos.y = float.MaxValue;
}
}
Best Practices
1. Handle Incomplete Input
CheckIsPrematureStreamTermination for multiline support:
catch (SyntaxErrorException ex)
{
if (ex.IsPrematureStreamTermination)
{
// Continue reading input
return null;
}
else
{
// Real syntax error
throw;
}
}
2. Provide Context
Expose useful objects and functions:script.Globals["help"] = (Action)ShowHelp;
script.Globals["clear"] = (Action)Console.Clear;
script.Globals["version"] = Script.VERSION;
3. Save History
Implement command history for better UX:List<string> history = new List<string>();
int historyIndex = -1;
// On up arrow: show previous command
// On down arrow: show next command
4. Graceful Error Handling
Don’t let errors crash the REPL:try
{
repl.Evaluate(input);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
// Continue REPL
}
Next Steps
- Explore Error Handling for better error messages
- Learn about Unity Integration for game consoles
- Check the API Reference for more details