Skip to content

Commit

Permalink
Union type generator
Browse files Browse the repository at this point in the history
  • Loading branch information
louthy committed Nov 8, 2019
1 parent c8cb004 commit 35470a0
Show file tree
Hide file tree
Showing 4 changed files with 306 additions and 0 deletions.
23 changes: 23 additions & 0 deletions LanguageExt.CodeGen/CodeGenUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,27 @@ public static SyntaxToken MakeFirstCharUpper(SyntaxToken identifier)
return SyntaxFactory.Identifier(id2);
}

public static string MakeFirstCharUpper(string identifier)
{
var id = identifier;
var id2 = $"{Char.ToUpper(id[0])}{id.Substring(1)}";
return id2;
}

public static SyntaxToken MakeFirstCharLower(SyntaxToken identifier)
{
var id = identifier.ToString();
var id2 = $"{Char.ToLower(id[0])}{id.Substring(1)}";
return SyntaxFactory.Identifier(id2);
}

public static string MakeFirstCharLower(string identifier)
{
var id = identifier;
var id2 = $"{Char.ToLower(id[0])}{id.Substring(1)}";
return id2;
}

static bool FirstCharIsUpper(string name) =>
name.Length > 0 && Char.IsUpper(name[0]);

Expand Down Expand Up @@ -509,12 +523,21 @@ public static MemberAccessExpressionSyntax PreludeMember(SimpleNameSyntax name)
IdentifierName("Prelude")),
name);

public static HashSet<string> MethodNames(SyntaxList<MemberDeclarationSyntax> list) =>
new HashSet<string>(
list.Select(MethodName)
.Where(m => !String.IsNullOrWhiteSpace(m))
.Distinct());

public static HashSet<string> MemberNames(SyntaxList<MemberDeclarationSyntax> list) =>
new HashSet<string>(
list.Select(MemberName)
.Where(m => !String.IsNullOrWhiteSpace(m))
.Distinct());

static string MethodName(MemberDeclarationSyntax decl) =>
decl is MethodDeclarationSyntax m ? m.Identifier.Text : "";

static string MemberName(MemberDeclarationSyntax decl) =>
decl is PropertyDeclarationSyntax p ? p.Identifier.Text
: decl is MethodDeclarationSyntax m ? m.Identifier.Text
Expand Down
19 changes: 19 additions & 0 deletions LanguageExt.CodeGen/UnionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using CodeGeneration.Roslyn;
using LanguageExt.CodeGen;

namespace LanguageExt
{
/// <summary>
/// Union attribute
/// </summary>
[AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = true)]
[CodeGenerationAttribute(typeof(UnionGenerator))]
[Conditional("CodeGeneration")]
public class UnionAttribute : Attribute
{
}
}
257 changes: 257 additions & 0 deletions LanguageExt.CodeGen/UnionGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CodeGeneration.Roslyn;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.CSharp;
using System.ComponentModel;

namespace LanguageExt.CodeGen
{
public class UnionGenerator : ICodeGenerator
{
public UnionGenerator(AttributeData attributeData)
{
}

public Task<SyntaxList<MemberDeclarationSyntax>> GenerateAsync(
TransformationContext context,
IProgress<Diagnostic> progress,
CancellationToken cancellationToken)
{
if (context.ProcessingNode is InterfaceDeclarationSyntax applyTo)
{
var cases = applyTo.Members
.Where(m => m is MethodDeclarationSyntax)
.Select(m => m as MethodDeclarationSyntax)
.Select(m => MakeCaseClass(applyTo, m))
.ToList();

var staticCtorClass = MakeStaticConstructorClass(applyTo);

return Task.FromResult(SyntaxFactory.List<MemberDeclarationSyntax>().AddRange(cases).Add(staticCtorClass));
}
else
{
return Task.FromResult(SyntaxFactory.List<MemberDeclarationSyntax>());
}
}

ClassDeclarationSyntax MakeStaticConstructorClass(InterfaceDeclarationSyntax applyTo)
{
var @class = ClassDeclaration(applyTo.Identifier)
.WithModifiers(
TokenList(new[] { Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.PartialKeyword) }));

var returnType = ParseTypeName($"{applyTo.Identifier}{applyTo.TypeParameterList}");

var cases = applyTo.Members
.Where(m => m is MethodDeclarationSyntax)
.Select(m => m as MethodDeclarationSyntax)
.Select(m => MakeCaseCtorFunction(applyTo, returnType, m))
.ToList();

return @class.WithMembers(List(cases));
}

MemberDeclarationSyntax MakeCaseCtorFunction(InterfaceDeclarationSyntax applyTo, TypeSyntax returnType, MethodDeclarationSyntax method)
{
var typeParamList = applyTo.TypeParameterList;
if (method.TypeParameterList != null)
{
typeParamList = typeParamList.AddParameters(method.TypeParameterList.Parameters.ToArray());
}

var thisType = ParseTypeName($"{method.Identifier.Text}{typeParamList}");

var args = CodeGenUtil.Interleave(
method.ParameterList
.Parameters
.Select(p => (SyntaxNodeOrToken)Argument(IdentifierName(p.Identifier.Text)))
.ToArray(),
Token(SyntaxKind.CommaToken));

var @case = MethodDeclaration(returnType, method.Identifier)
.WithModifiers(
TokenList(
new[]{
Token(SyntaxKind.PublicKeyword),
Token(SyntaxKind.StaticKeyword)}))
.WithParameterList(method.ParameterList)
.WithExpressionBody(
ArrowExpressionClause(
ObjectCreationExpression(thisType)
.WithArgumentList(
ArgumentList(
SeparatedList<ArgumentSyntax>(args)))))
.WithSemicolonToken(
Token(SyntaxKind.SemicolonToken));

if(typeParamList != null)
{
@case = @case.WithTypeParameterList(typeParamList);
}
return @case;
}

static ClassDeclarationSyntax MakeCaseClass(InterfaceDeclarationSyntax applyTo, MethodDeclarationSyntax method)
{
var modifiers = SyntaxFactory.TokenList(
Enumerable.Concat(
applyTo.Modifiers.Where(t => !t.IsKind(SyntaxKind.PartialKeyword)).AsEnumerable(),
new[] { SyntaxFactory.Token(SyntaxKind.PartialKeyword) }));


var @class = ClassDeclaration(method.Identifier.Text).WithModifiers(modifiers);

var typeParamList = applyTo.TypeParameterList;
if(method.TypeParameterList != null)
{
typeParamList = typeParamList.AddParameters(method.TypeParameterList.Parameters.ToArray());
}

if (typeParamList != null)
{
@class = @class.WithTypeParameterList(typeParamList);
}

if (applyTo.ConstraintClauses != null)
{
@class = @class.WithConstraintClauses(applyTo.ConstraintClauses);
}

var returnType = ParseTypeName($"{applyTo.Identifier}{applyTo.TypeParameterList}");
var thisType = ParseTypeName($"{method.Identifier.Text}{typeParamList}");
var thisRecordType = ParseTypeName($"LanguageExt.Record<{method.Identifier.Text}{typeParamList}>");

var ctor = MakeConstructor(@class, returnType, method);

var fields = method.ParameterList
.Parameters
.Select(p => MakeField(returnType, p))
.ToList();

var impl = applyTo.Members
.Where(m => m is MethodDeclarationSyntax)
.Select(m => m as MethodDeclarationSyntax)
.Select(m => MakeExplicitInterfaceImpl(returnType, m))
.ToList();

fields.AddRange(impl);
fields.Add(ctor);

@class = @class.WithMembers(List(fields));

@class = @class.WithBaseList(
BaseList(
SeparatedList<BaseTypeSyntax>(
new SyntaxNodeOrToken[]{
SimpleBaseType(thisRecordType),
Token(SyntaxKind.CommaToken),
SimpleBaseType(returnType)
})));

return @class;
}

static MemberDeclarationSyntax MakeExplicitInterfaceImpl(TypeSyntax returnType, MethodDeclarationSyntax @case)
{
var method = MethodDeclaration(@case.ReturnType, @case.Identifier)
.WithExplicitInterfaceSpecifier(
ExplicitInterfaceSpecifier(ParseName(returnType.ToString())))
.WithParameterList(@case.ParameterList)
.WithExpressionBody(
ArrowExpressionClause(
ThrowExpression(
ObjectCreationExpression(
QualifiedName(
IdentifierName("System"),
IdentifierName("NotSupportedException")))
.WithArgumentList(ArgumentList()))))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken));

if(@case.TypeParameterList != null)
{
method = method.WithTypeParameterList(@case.TypeParameterList);
}
return method;
}

static MemberDeclarationSyntax MakeConstructor(ClassDeclarationSyntax @class, TypeSyntax returnType, MethodDeclarationSyntax method)
{
var assignments = method.ParameterList
.Parameters
.Select(p =>
ExpressionStatement(
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(CodeGenUtil.MakeFirstCharUpper(p.Identifier.Text)),
IdentifierName(p.Identifier.Text))));

return ConstructorDeclaration(Identifier(method.Identifier.Text))
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword)))
.WithParameterList(method.ParameterList)
.WithBody(Block(List(assignments)));
}

static MemberDeclarationSyntax MakeField(TypeSyntax returnType, ParameterSyntax p)
{
var fieldName = CodeGenUtil.MakeFirstCharUpper(p.Identifier.Text);

var field = FieldDeclaration(
VariableDeclaration(p.Type)
.WithVariables(SingletonSeparatedList(VariableDeclarator(Identifier(fieldName)))))
.WithModifiers(
TokenList(
new[]{
Token(SyntaxKind.PublicKeyword),
Token(SyntaxKind.ReadOnlyKeyword)}))
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)); ;

return field;
}
}
}

//public interface Option<A>
//{
// Option<A> Some(A value);
// Option<A> None();
//}

//public class Some<A> : Option<A>, Record<Some<A>>
//{
// public readonly A Value;
// public Some(A value)
// {
// Value = value;
// }

// [EditorBrowsable(EditorBrowsableState.Never)]
// Option<A> Option<A>.None() => new None<A>();
// [EditorBrowsable(EditorBrowsableState.Never)]
// Option<A> Option<A>.Some(A value) => new Some<A>(value);
//}

//public class None<A> : Option<A>, Record<None<A>>
//{
// public None()
// {
// }

// [EditorBrowsable(EditorBrowsableState.Never)]
// Option<A> Option<A>.None() => new None<A>();
// [EditorBrowsable(EditorBrowsableState.Never)]
// Option<A> Option<A>.Some(A value) => new Some<A>(value);
//}

//public static partial class Option
//{
// public static Option<A> Some<A>(A value) => new Some<A>(value);
// public static Option<A> None<A>() => new None<A>();
//}
7 changes: 7 additions & 0 deletions Samples/TestBed/TestCodeGen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ public TestWith4(string @new, string @class, string @static, string @while)
While = @while;
}
}

[Union]
public interface Maybe<A>
{
Maybe<A> Just(A value);
Maybe<A> Nothing();
}
}


0 comments on commit 35470a0

Please sign in to comment.