Ponet
Ponet is a C# code generation library wrapping the Roslyn API.
For more information see ponet.aenea.dev.
Disclaimers
Ponet is an immature library. Therefore, there are many inconveniences as follows.
- Unable to construct the intended SyntaxTree.
- API is unstable.
If you are interested in Ponet and use it, I hope you can give us some feedback to help us develop it in the future.
Example
Hello World
Here's a Ponet code to generate Hello World
class.
string message = "Hello World!";
MethodDeclarationSyntax mainMethod = MethodBuilder.Of("Main")
.Modifier(Modifier.Static)
.Returns(TypeName.Void)
.CodeBlock(CodeBlockBuilder.Of()
.Add($"Console.WriteLine({message:S});")
.Build())
.ToSyntaxNode();
CompilationUnitSyntax compilationUnitSyntax = CsharpFileBuilder.Of("HelloWorld")
.Using("System")
.Member(
TypeBuilder.Of("Hello").Method(mainMethod)
.ToClassSyntaxNode()
).ToSyntaxNode();
// Write to File.
compilationUnitSyntax.Save("./HelloWorld.cs");
Generated HelloWorld.cs
contains:
using System;
namespace HelloWorld
{
class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}
}
Immutable Class
An example of an ImmutableClass for typical code generation is:
/*
* Prepare properties which are public and Getter-only.
*/
PropertyDeclarationSyntax[] properties = new[]
{
PropertyBuilder.Of(TypeName.String, "FirstName"),
PropertyBuilder.Of(TypeName.String, "LastName"),
PropertyBuilder.Of(TypeName.Int, "Age")
}.Select(builder => builder.Modifier(Modifier.Public).Getter().ToSyntaxNode()).ToArray();
/*
* Prepare constructor to initialize properties.
*/
MethodBuilder constructorBuilder = MethodBuilder.Of("Person").Modifier(Modifier.Public);
CodeBlockBuilder constructorBody = CodeBlockBuilder.Of();
foreach (PropertyDeclarationSyntax property in properties)
{
// add constructor parameter from PropertyDeclarationSyntax
// Format Nlc extracts an lowercamelized identifier (lc means 'lower camel')
constructorBuilder.Parameter(property.Type, $"{property:Nlc}");
// add expression to initialize property
// Format N extracts an identifier
constructorBody.Add($"{property:N} = {property:Nlc};");
}
ConstructorDeclarationSyntax constructor =
constructorBuilder.CodeBlock(constructorBody.Build()).ToConstructorNode();
/*
* Finish creating the class
*/
ClassDeclarationSyntax personClass = TypeBuilder.Of("Person").Modifier(Modifier.Public)
.AddMember(constructor)
.Properties(properties)
.ToClassSyntaxNode();
personClass
represents
public class Person
{
public Person(string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
public string FirstName { get; }
public string LastName { get; }
public int Age { get; }
}
Usage
Types(Class, Interface, Enum, Struct)
Ponet provides TypeBuilder for building Class, Interface, Enum and Struct. Complete the construction of the syntax tree by calling methods for each type.
Class
Class is built using TypeBuilder#ToClassSyntaxNode
. See Example above for the actual code.
Interface
Use TypeBuilder#ToInterfaceSyntaxNode
to build the Interface.
MethodDeclarationSyntax methodDeclarationSyntax = MethodBuilder.Of("Execute")
.Parameter(TypeName.String, "text")
.Returns(TypeName.Void)
.ToSyntaxNode();
InterfaceDeclarationSyntax interfaceDeclarationSyntax = TypeBuilder.Of("ISomeService")
.Modifier(Modifier.Public)
.AddMember(methodDeclarationSyntax)
.ToInterfaceSyntaxNode();
represents.
public interface ISomeService
{
void Execute(string text);
}
Enum
Use TypeBuilder#ToEnumSyntaxNode
to build the Enum.
EnumDeclarationSyntax enumDeclarationSyntax = TypeBuilder.Of("Lang")
.Modifier(Modifier.Public)
.EnumSymbol("Csharp")
.EnumSymbol("Fsharp")
.EnumSymbol("Java")
.EnumSymbol("Kotlin")
.ToEnumSyntaxNode();
represents.
public enum Lang
{
Csharp,
Fsharp,
Java,
Kotlin
}
Struct
// Constructor
ConstructorDeclarationSyntax constructor = MethodBuilder.Of("SomeStruct")
.Parameter(TypeName.Int, "x")
.Parameter(TypeName.Int, "y")
.CodeBlock(
CodeBlockBuilder.Of()
.Add("X = x;")
.Add("Y = y;")
.Build())
.ToConstructorNode();
// Struct
StructDeclarationSyntax structDeclarationSyntax = TypeBuilder.Of("SomeStruct")
.AddMember(constructor)
.AddMember(PropertyBuilder.Of(TypeName.Int, "X").Modifier(Modifier.Public).Getter().ToSyntaxNode())
.AddMember(PropertyBuilder.Of(TypeName.Int, "Y").Modifier(Modifier.Public).Getter().ToSyntaxNode())
.ToStructSyntaxNode()
structDeclarationSyntax
represents
struct SomeStruct
{
SomeStruct(int x, int y)
{
X = x;
Y = y;
}
public int X { get; }
public int Y { get; }
}
Convert to String, Save to File
Provides extension methods for CompilationUnitSyntax to support syntax tree stringization and saving to a file.
CompilationUnitSyntax compilationUnitSyntax = ...;
// To String
string code = compilationUnitSyntax.toCode();
// Write to File
compilationUnitSyntax.Save("./generatedCode");
Custom StringFormat Specifier
When building a SyntaxNode, you often want to refer to the types and identifiers in the SyntaxNode already built.
To do this, Ponet provides its own format specifiers that can be used with interpolation.
- the N, NUC, Nlc format specifier
- result: identifier(method name, parameter name)
- support: BaseTypeDeclarationSyntax, ParameterSyntax, VariableDeclaratorSyntax, etc
- the T, TUC, Tlc
- result: Type Name
- support: FieldDeclarationSyntax, PropertyDeclarationSyntax, ParameterSyntax, etc
- the S
- result: escaped string
- support: string only
UC stands for UpperCamel and lc for _lowerCamel. this changes the naming convention for the result of an same single-character format specifier.
The input and result combinations are shown in the following table.
Format Specifier | from | to |
---|---|---|
UC | UpperCamel | UpperCamel |
UC | lowerCamel | LowerCamel |
UC | UPPER_UNDERSCORE | UpperUnderscore |
UC | _lowerCamel | LowerCamel |
lc | UpperCamel | upperCamel |
lc | lowerCamel | lowerCamel |
lc | UPPER_UNDERSCORE | upperUnderscore |
lc | _lowerCamel | lowerCamel |