Parsing Lua Code
Loretta provides a robust parser that converts Lua source code into syntax trees. The parser handles error recovery, allowing you to get a syntax tree even from invalid code.
Basic Parsing
The simplest way to parse Lua code is using LuaSyntaxTree.ParseText:
using Loretta.CodeAnalysis.Lua;
var code = "local x = 10";
var tree = LuaSyntaxTree.ParseText(code);
var root = tree.GetRoot();
Console.WriteLine(root.ToFullString());
ParseText Methods
LuaSyntaxTree.ParseText has two main overloads:
Parsing from String
public static SyntaxTree ParseText(
string text,
LuaParseOptions? options = null,
string path = "",
Encoding? encoding = null,
CancellationToken cancellationToken = default)
Example:
var code = "print('Hello, World!')";
var tree = LuaSyntaxTree.ParseText(
text: code,
options: new LuaParseOptions(LuaSyntaxOptions.Lua54),
path: "script.lua"
);
Parsing from SourceText
public static SyntaxTree ParseText(
SourceText text,
LuaParseOptions? options = null,
string path = "",
CancellationToken cancellationToken = default)
Example:
using Loretta.CodeAnalysis.Text;
var code = "local x = 10";
var sourceText = SourceText.From(code, Encoding.UTF8);
var tree = LuaSyntaxTree.ParseText(sourceText);
SourceText
SourceText is an abstraction over source code that provides efficient access to text content and line information. It’s particularly useful for:
- Large files: Efficient handling of large source files
- Encoding: Preserving encoding information
- Line mapping: Fast line/column lookups
- Incremental parsing: Reusing parsed trees when text changes
Creating SourceText
using Loretta.CodeAnalysis.Text;
// From string
var sourceText = SourceText.From("local x = 10");
// From string with encoding
var sourceTextWithEncoding = SourceText.From(
"local x = 10",
Encoding.UTF8
);
// From stream
using var stream = File.OpenRead("script.lua");
var sourceTextFromStream = SourceText.From(stream, Encoding.UTF8);
Working with SourceText
var sourceText = SourceText.From("local x = 10\nprint(x)");
// Get length
Console.WriteLine($"Length: {sourceText.Length}");
// Get a character at position
char ch = sourceText[6]; // 'x'
// Get line information
var lines = sourceText.Lines;
Console.WriteLine($"Line count: {lines.Count}");
// Get line and column from position
var linePosition = sourceText.Lines.GetLinePosition(6);
Console.WriteLine($"Position 6 is at line {linePosition.Line}, column {linePosition.Character}");
// Get text span for a line
var firstLine = sourceText.Lines[0];
var firstLineText = sourceText.ToString(firstLine.Span);
Console.WriteLine($"First line: {firstLineText}");
LuaParseOptions
LuaParseOptions controls how the parser interprets Lua syntax. It wraps LuaSyntaxOptions to specify which Lua version or dialect to parse.
public sealed class LuaParseOptions
{
public LuaSyntaxOptions SyntaxOptions { get; }
}
Creating Parse Options
using Loretta.CodeAnalysis.Lua;
// Use default options (accepts all syntax)
var defaultOptions = LuaParseOptions.Default;
// Create options for a specific Lua version
var lua54Options = new LuaParseOptions(LuaSyntaxOptions.Lua54);
var luauOptions = new LuaParseOptions(LuaSyntaxOptions.Luau);
var gmodOptions = new LuaParseOptions(LuaSyntaxOptions.GMod);
Changing Options
var options = new LuaParseOptions(LuaSyntaxOptions.Lua51);
// Create new options with different syntax options
var newOptions = options.WithSyntaxOptions(LuaSyntaxOptions.Lua54);
LuaSyntaxOptions and LuaParseOptions
LuaParseOptions contains LuaSyntaxOptions, which defines what language features are accepted:
var syntaxOptions = LuaSyntaxOptions.Lua54;
var parseOptions = new LuaParseOptions(syntaxOptions);
var tree = LuaSyntaxTree.ParseText(
"local x <const> = 10", // Lua 5.4 constant attribute
parseOptions
);
// The tree will parse without errors because Lua 5.4 supports attributes
var diagnostics = tree.GetDiagnostics();
Console.WriteLine($"Errors: {diagnostics.Count()}");
See Lua Versions for more details on syntax options.
Parsing with Different Lua Versions
Lua 5.1
var code = "local x = 10";
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.Lua51)
);
Lua 5.4 with Attributes
var code = "local x <const> = 10";
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.Lua54)
);
Luau (Roblox)
var code = @"
local x: number = 10
local y = if x > 5 then 'big' else 'small'
";
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.Luau)
);
GLua (Garry’s Mod)
var code = @"
// C-style comment
local x = 10
if x > 5 && x < 15 then
print('in range')
end
";
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.GMod)
);
Error Recovery
Loretta’s parser uses error recovery to produce a syntax tree even when the code has syntax errors. This is useful for tools like IDEs that need to analyze incomplete or invalid code.
// Code with syntax error (missing 'end')
var code = @"
local function test()
print('hello')
-- missing 'end'
";
var tree = LuaSyntaxTree.ParseText(code);
var root = tree.GetRoot();
// The tree is still created
Console.WriteLine($"Got root: {root != null}");
// But diagnostics will show the error
var diagnostics = tree.GetDiagnostics();
foreach (var diagnostic in diagnostics)
{
Console.WriteLine($"{diagnostic.Severity}: {diagnostic.GetMessage()}");
}
Even with syntax errors, you’ll get a complete syntax tree. The parser inserts “missing” tokens where necessary and reports diagnostics for the errors.
Incremental Parsing
When you modify a SourceText, Loretta can reuse parts of the existing syntax tree for better performance:
var originalText = SourceText.From("local x = 10\nprint(x)");
var tree = LuaSyntaxTree.ParseText(originalText);
// Make a small change to the text
var newText = originalText.WithChanges(new TextChange(
new TextSpan(10, 2), // Replace "10"
"20" // With "20"
));
// Parse with the new text - this will reuse parts of the old tree
var newTree = tree.WithChangedText(newText);
Complete Parsing Example
Here’s a complete example showing various parsing scenarios:
using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Syntax;
using Loretta.CodeAnalysis.Text;
using System.Text;
class ParsingExample
{
static void Main()
{
// Example 1: Simple parsing
SimpleParsingExample();
// Example 2: Parsing with options
ParsingWithOptionsExample();
// Example 3: Parsing with SourceText
ParsingWithSourceTextExample();
// Example 4: Error recovery
ErrorRecoveryExample();
}
static void SimpleParsingExample()
{
var code = "local x = 10";
var tree = LuaSyntaxTree.ParseText(code);
var root = tree.GetRoot();
Console.WriteLine("Parsed successfully!");
Console.WriteLine(root.ToFullString());
}
static void ParsingWithOptionsExample()
{
// Parse Luau code with type annotations
var code = "local x: number = 10";
var options = new LuaParseOptions(LuaSyntaxOptions.Luau);
var tree = LuaSyntaxTree.ParseText(code, options);
var diagnostics = tree.GetDiagnostics();
if (!diagnostics.Any())
{
Console.WriteLine("Luau code parsed without errors");
}
}
static void ParsingWithSourceTextExample()
{
var code = @"
local function greet(name)
print('Hello, ' .. name)
end
greet('World')
";
var sourceText = SourceText.From(code, Encoding.UTF8);
var tree = LuaSyntaxTree.ParseText(
sourceText,
new LuaParseOptions(LuaSyntaxOptions.Lua54),
"greet.lua"
);
Console.WriteLine($"File: {tree.FilePath}");
Console.WriteLine($"Lines: {sourceText.Lines.Count}");
}
static void ErrorRecoveryExample()
{
// Intentionally broken code
var code = @"
local function broken(
-- missing closing parenthesis and end
print('oops')
";
var tree = LuaSyntaxTree.ParseText(code);
var root = tree.GetRoot();
Console.WriteLine("Tree created despite errors:");
Console.WriteLine($"Root type: {root.GetType().Name}");
Console.WriteLine("\nDiagnostics:");
foreach (var diagnostic in tree.GetDiagnostics())
{
var span = diagnostic.Location.SourceSpan;
Console.WriteLine($" {diagnostic.Severity} at {span}: {diagnostic.GetMessage()}");
}
}
}
Best Practices
Use SourceText for File I/O
When reading from files, use SourceText.From with a stream to preserve encoding:
using var stream = File.OpenRead("script.lua");
var sourceText = SourceText.From(stream, Encoding.UTF8);
var tree = LuaSyntaxTree.ParseText(sourceText, path: "script.lua");
Specify the Correct Lua Version
Always specify parse options that match your target Lua version to get accurate diagnostics:
// For Lua 5.1 code
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.Lua51)
);
// For Luau/Roblox code
var tree = LuaSyntaxTree.ParseText(
code,
new LuaParseOptions(LuaSyntaxOptions.Luau)
);
Check Diagnostics After Parsing
Always check for diagnostics after parsing to handle syntax errors:
var tree = LuaSyntaxTree.ParseText(code);
var diagnostics = tree.GetDiagnostics().ToList();
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
{
Console.WriteLine("Parse errors found:");
foreach (var diagnostic in diagnostics)
{
Console.WriteLine($" {diagnostic.GetMessage()}");
}
}
See Also