SyntaxFactory to create syntax nodes, tokens, and trivia programmatically. This allows you to build Lua code from scratch or construct nodes for tree transformations.
Understanding SyntaxFactory
SyntaxFactory is a static class that provides factory methods for creating all syntax elements:
using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Syntax;
Creating Simple Tokens
Start with the building blocks - tokens:// Create keyword tokens
var localKeyword = SyntaxFactory.Token(SyntaxKind.LocalKeyword);
var functionKeyword = SyntaxFactory.Token(SyntaxKind.FunctionKeyword);
// Create punctuation
var equals = SyntaxFactory.Token(SyntaxKind.EqualsToken);
var comma = SyntaxFactory.Token(SyntaxKind.CommaToken);
Creating Identifiers
Identifiers require special methods:// Create an identifier token
var identifier = SyntaxFactory.Identifier("myVariable");
// Create an identifier name (a node)
var identifierName = SyntaxFactory.IdentifierName("myVariable");
Creating Literals
For literal values:// Numeric literals
var numberToken = SyntaxFactory.Literal(42);
var floatToken = SyntaxFactory.Literal(3.14);
// String literals
var stringToken = SyntaxFactory.Literal("hello");
// Boolean literals (keywords)
var trueToken = SyntaxFactory.Token(SyntaxKind.TrueKeyword);
var falseToken = SyntaxFactory.Token(SyntaxKind.FalseKeyword);
Building Expressions
Literal Expressions
// Create a numeric literal expression
var numberExpr = SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(10)
);
// Create a string literal expression
var stringExpr = SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("hello")
);
Binary Expressions
// Create: x + 10
var addition = SyntaxFactory.BinaryExpression(
SyntaxKind.AddExpression,
SyntaxFactory.IdentifierName("x"),
SyntaxFactory.Token(SyntaxKind.PlusToken),
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(10)
)
);
Function Calls
// Create: print("hello")
var printCall = SyntaxFactory.FunctionCallExpression(
SyntaxFactory.IdentifierName("print"),
SyntaxFactory.FunctionArgumentList(
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("hello")
)
})
)
);
Member Access
// Create: string.format
var memberAccess = SyntaxFactory.MemberAccessExpression(
SyntaxFactory.IdentifierName("string"),
SyntaxFactory.Token(SyntaxKind.DotToken),
SyntaxFactory.IdentifierName("format")
);
// Create: string.format("x = %d", x)
var formatCall = SyntaxFactory.FunctionCallExpression(
memberAccess,
SyntaxFactory.FunctionArgumentList(
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.StringLiteralExpression,
SyntaxFactory.Literal("x = %d")
),
SyntaxFactory.IdentifierName("x")
})
)
);
Building Statements
Local Variable Declaration
// Create: local x = 10
var localDecl = SyntaxFactory.LocalVariableDeclarationStatement(
SyntaxFactory.SeparatedList(new[] {
SyntaxFactory.LocalDeclarationName(
SyntaxFactory.IdentifierName("x")
)
}),
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(10)
)
})
);
Assignment Statement
// Create: x = 20
var assignment = SyntaxFactory.AssignmentStatement(
SyntaxFactory.SeparatedList<PrefixExpressionSyntax>(new[] {
SyntaxFactory.IdentifierName("x")
}),
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(20)
)
})
);
Function Declaration
// Create: function add(a, b) return a + b end
var functionDecl = SyntaxFactory.FunctionDeclarationStatement(
SyntaxFactory.Token(SyntaxKind.FunctionKeyword),
SyntaxFactory.FunctionName(SyntaxFactory.IdentifierName("add")),
SyntaxFactory.ParameterList(
SyntaxFactory.SeparatedList<ParameterSyntax>(new[] {
SyntaxFactory.NamedParameter(SyntaxFactory.Identifier("a")),
SyntaxFactory.NamedParameter(SyntaxFactory.Identifier("b"))
})
),
SyntaxFactory.StatementList(
SyntaxFactory.SingletonList<StatementSyntax>(
SyntaxFactory.ReturnStatement(
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.BinaryExpression(
SyntaxKind.AddExpression,
SyntaxFactory.IdentifierName("a"),
SyntaxFactory.Token(SyntaxKind.PlusToken),
SyntaxFactory.IdentifierName("b")
)
})
)
)
),
SyntaxFactory.Token(SyntaxKind.EndKeyword)
);
Working with Lists
Loretta usesSyntaxList and SeparatedSyntaxList for collections:
SyntaxList
For lists without separators (like statement lists):// Create a list of statements
var statements = SyntaxFactory.List(new StatementSyntax[] {
localDecl,
assignment
});
// Or use SingletonList for a single item
var singleStatement = SyntaxFactory.SingletonList<StatementSyntax>(localDecl);
SeparatedSyntaxList
For lists with separators (like argument lists):// Create a separated list
var arguments = SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(1)
),
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(2)
)
});
// Separators (commas) are added automatically
Formatting Generated Code
Code created with SyntaxFactory has no whitespace by default. UseNormalizeWhitespace to add formatting:
// Without normalization
var ugly = localDecl.ToFullString();
// Output: "localx=10"
// With normalization
var pretty = localDecl.NormalizeWhitespace().ToFullString();
// Output: "local x = 10"
Custom Formatting
Control formatting with options:// Use tabs instead of spaces
var formatted = localDecl.NormalizeWhitespace(
indentation: "\t",
eol: "\n"
);
Adding Trivia
Add comments, whitespace, and line breaks:// Create trivia
var comment = SyntaxFactory.Comment("-- This is a comment");
var newLine = SyntaxFactory.EndOfLine("\n");
var space = SyntaxFactory.Whitespace(" ");
// Add to tokens
var tokenWithComment = SyntaxFactory.Token(
SyntaxFactory.TriviaList(comment, newLine),
SyntaxKind.LocalKeyword,
SyntaxFactory.TriviaList(space)
);
// Add to nodes
var nodeWithTrivia = localDecl
.WithLeadingTrivia(comment, newLine)
.WithTrailingTrivia(newLine);
ToFullString vs ToString
Two methods convert nodes to text:var node = localDecl
.WithLeadingTrivia(SyntaxFactory.Comment("-- comment"))
.WithTrailingTrivia(SyntaxFactory.EndOfLine("\n"));
// ToString() excludes trivia
var withoutTrivia = node.ToString();
// Output: "local x = 10"
// ToFullString() includes trivia
var withTrivia = node.ToFullString();
// Output: "-- comment\nlocal x = 10\n"
Always use
ToFullString() when generating complete code files. Use ToString() when you only need the node’s text without surrounding whitespace or comments.Complete Example: Generating a Lua Module
Here’s a complete example that generates a Lua module:using Loretta.CodeAnalysis.Lua;
using Loretta.CodeAnalysis.Lua.Syntax;
public static class ModuleGenerator
{
public static string GenerateModule(string moduleName,
IEnumerable<(string name, int value)> constants)
{
var statements = new List<StatementSyntax>();
// Create: local M = {}
statements.Add(
SyntaxFactory.LocalVariableDeclarationStatement(
SyntaxFactory.SeparatedList(new[] {
SyntaxFactory.LocalDeclarationName(
SyntaxFactory.IdentifierName("M")
)
}),
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.TableConstructorExpression(
SyntaxFactory.Token(SyntaxKind.OpenBraceToken),
default,
SyntaxFactory.Token(SyntaxKind.CloseBraceToken)
)
})
)
);
// Add constants: M.NAME = value
foreach (var (name, value) in constants)
{
statements.Add(
SyntaxFactory.AssignmentStatement(
SyntaxFactory.SeparatedList<PrefixExpressionSyntax>(new[] {
SyntaxFactory.MemberAccessExpression(
SyntaxFactory.IdentifierName("M"),
SyntaxFactory.Token(SyntaxKind.DotToken),
SyntaxFactory.IdentifierName(name)
)
}),
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(value)
)
})
)
);
}
// Create: return M
statements.Add(
SyntaxFactory.ReturnStatement(
SyntaxFactory.SeparatedList<ExpressionSyntax>(new[] {
SyntaxFactory.IdentifierName("M")
})
)
);
// Create compilation unit
var compilationUnit = SyntaxFactory.CompilationUnit(
SyntaxFactory.StatementList(SyntaxFactory.List(statements)),
SyntaxFactory.Token(SyntaxKind.EndOfFileToken)
);
// Normalize and return
return compilationUnit.NormalizeWhitespace().ToFullString();
}
}
// Usage
var code = ModuleGenerator.GenerateModule(
"Constants",
new[] {
("VERSION", 1),
("MAX_SIZE", 100),
("TIMEOUT", 30)
}
);
Console.WriteLine(code);
local M = {}
M.VERSION = 1
M.MAX_SIZE = 100
M.TIMEOUT = 30
return M
Builder Pattern
For complex node construction, consider using a builder pattern:public class LocalVariableBuilder
{
private readonly List<string> _names = new();
private readonly List<ExpressionSyntax> _values = new();
public LocalVariableBuilder AddVariable(string name, ExpressionSyntax? value = null)
{
_names.Add(name);
if (value != null)
_values.Add(value);
return this;
}
public LocalVariableDeclarationStatementSyntax Build()
{
var names = SyntaxFactory.SeparatedList(
_names.Select(n => SyntaxFactory.LocalDeclarationName(
SyntaxFactory.IdentifierName(n)
))
);
var values = _values.Count > 0
? SyntaxFactory.SeparatedList(_values)
: default;
return SyntaxFactory.LocalVariableDeclarationStatement(names, values);
}
}
// Usage
var decl = new LocalVariableBuilder()
.AddVariable("x", SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(10)
))
.AddVariable("y", SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(20)
))
.Build();
Next Steps
- Learn how to transform existing trees with rewriters
- Understand error handling in generated code
- Use walkers to analyze generated trees