Skip to content

Commit

Permalink
Merge branch 'main' into cfg-loop
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisKXu committed Jul 13, 2024
2 parents 8818ba2 + 884aeac commit 9d33100
Show file tree
Hide file tree
Showing 80 changed files with 1,613 additions and 1,330 deletions.
3 changes: 1 addition & 2 deletions samples/Fibonacci.Loop/Fibonacci.Loop.tdl
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ int fibonacci(int i) {
let a = 1;
let b = 1;

while i > 2 {
--i
while i-- > 2 {
const c = a + b;
a = b;
b = c;
Expand Down
2 changes: 1 addition & 1 deletion samples/Fibonacci.Loop/Fibonacci.Loop.tdlproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
</Project>
252 changes: 83 additions & 169 deletions src/Todl.Compiler.SourceGenerators/BoundNodeFactorySourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,80 +1,69 @@
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.Text;

namespace Todl.Compiler.SourceGenerators;

[Generator]
public sealed class BoundNodeFactorySourceGenerator : IIncrementalGenerator
internal sealed class BoundNodeFactorySourceGenerator : IIncrementalGenerator
{
private const string BoundNodeFactoryNamespace = "Todl.Compiler.CodeAnalysis.Binding.BoundTree";
private const string BoundNodeFactoryClassName = "BoundNodeFactory";
private const string BoundNodeNamespace = "Todl.Compiler.CodeAnalysis.Binding";
private const string BoundNodeTypeName = $"{BoundNodeNamespace}.BoundNode";
private const string BoundNodeFactoryGeneratedSourceFileName = $"{BoundNodeFactoryClassName}_generated.cs";

private static readonly SyntaxList<UsingDirectiveSyntax> defaultNamespaces = List(new List<string>()
{
"System",
"System.CodeDom.Compiler",
"System.Collections.Generic",
"System.Reflection",
"Todl.Compiler.CodeAnalysis.Symbols",
"Todl.Compiler.CodeAnalysis.Syntax",
"Todl.Compiler.Diagnostics"
}.Select(n => UsingDirective(ParseName(n))));

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var boundNodeClasses = context.SyntaxProvider.CreateSyntaxProvider(
static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax classDeclarationSyntax,
Transform)
.Where(m => m is not null);

context.RegisterSourceOutput(
context.CompilationProvider.Combine(boundNodeClasses.Collect()),
static (context, values) => Generate(context, values.Left, values.Right));
}

static ClassDeclarationSyntax Transform(GeneratorSyntaxContext context, CancellationToken cancellationToken)
{
var semanticModel = context.SemanticModel;
var symbolInfo = semanticModel.GetDeclaredSymbol(context.Node) as INamedTypeSymbol;
var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: $"{BoundNodeFactoryNamespace}.BoundNodeAttribute",
predicate: static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
transform: static (context, _) => new BoundNodeMetadata(context));

if (symbolInfo.Name.Contains("Bound") && !symbolInfo.IsAbstract)
{
return context.Node as ClassDeclarationSyntax;
}

return null;
context.RegisterSourceOutput(pipeline, GenerateBoundNodeFactoryMethods);
}

static void Generate(
SourceProductionContext context,
Compilation compilation,
IReadOnlyList<ClassDeclarationSyntax> boundNodeClasses)
static void GenerateBoundNodeFactoryMethods(SourceProductionContext context, BoundNodeMetadata boundNodeMetadata)
{
try
{

var compilationUnit = CompilationUnit()
.WithUsings(defaultNamespaces)
.WithMembers(List(
new MemberDeclarationSyntax[]
var className = boundNodeMetadata.ClassName;

var sourceText = SourceText.From($$"""
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using Todl.Compiler.CodeAnalysis.Symbols;
using Todl.Compiler.CodeAnalysis.Syntax;
using Todl.Compiler.Diagnostics;
namespace {{BoundNodeFactoryNamespace}};
internal sealed partial class {{BoundNodeFactoryClassName}}
{
[GeneratedCode("{{nameof(BoundNodeFactorySourceGenerator)}}", "1.0.0.0")]
internal static {{className}} Create{{className}}(
SyntaxNode syntaxNode,
{{boundNodeMetadata.WriteParameters()}}
DiagnosticBag.Builder diagnosticBuilder = null)
{
FileScopedNamespaceDeclaration(ParseName(BoundNodeNamespace)),
WriteBoundNodeFactory(compilation, boundNodeClasses)
}))
.NormalizeWhitespace();
diagnosticBuilder ??= new();
{{boundNodeMetadata.WriteDiagnostics()}}
context.AddSource(BoundNodeFactoryGeneratedSourceFileName, compilationUnit.GetText(Encoding.UTF8));
return new {{className}}()
{
SyntaxNode = syntaxNode,
{{boundNodeMetadata.WriteInitializers()}}
DiagnosticBuilder = diagnosticBuilder
};
}
}
""", Encoding.UTF8);

context.AddSource($"{BoundNodeFactoryClassName}.{boundNodeMetadata.ClassName}.g.cs", sourceText);
}
catch (Exception ex)
{
Expand All @@ -92,128 +81,53 @@ static void Generate(
}
}

static ClassDeclarationSyntax WriteBoundNodeFactory(Compilation compilation, IReadOnlyList<ClassDeclarationSyntax> boundNodeClasses)
private sealed class BoundNodeMetadata
{
var attributes = AttributeList(
SingletonSeparatedList(
Attribute(ParseName(nameof(GeneratedCodeAttribute)))
.WithArgumentList(
AttributeArgumentList(
SeparatedList<AttributeArgumentSyntax>(
new SyntaxNodeOrToken[]
{
AttributeArgument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal(typeof(BoundNodeFactorySourceGenerator).FullName))),
Token(SyntaxKind.CommaToken),
AttributeArgument(
LiteralExpression(
SyntaxKind.StringLiteralExpression,
Literal("1.0.0.0")))
})))));

var boundNodeType = compilation.GetTypeByMetadataName(BoundNodeTypeName);
var members = boundNodeClasses
.Select(s => compilation.GetSemanticModel(s.SyntaxTree).GetDeclaredSymbol(s))
.Where(t => t.IsDerivedFrom(boundNodeType))
.Select(t => WriteCreateMethod(t, boundNodeType));

return ClassDeclaration(BoundNodeFactoryClassName)
.WithMembers(List(members))
.WithAttributeLists(SingletonList(attributes))
.WithModifiers(
TokenList(
new[] { Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword) }));
}
private const string BoundNodeTypeName = $"{BoundNodeFactoryNamespace}.BoundNode";

static MemberDeclarationSyntax WriteCreateMethod(INamedTypeSymbol returnType, INamedTypeSymbol boundNodeType)
{
var properties = returnType
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => !p.IsReadOnly);
private readonly GeneratorAttributeSyntaxContext context;

public string ClassName => context.TargetSymbol.Name;

public IEnumerable<(string Name, IPropertySymbol Property)> Properties { get; }

var parameters = new List<ParameterSyntax>()
public BoundNodeMetadata(GeneratorAttributeSyntaxContext context)
{
Parameter(Identifier("syntaxNode")).WithType(ParseTypeName("SyntaxNode"))
};
this.context = context;

parameters.AddRange(properties.Select(p =>
Parameter(Identifier(p.CamelCasedName()))
.WithType(ParseTypeName(p.GetPropertyTypeName()))));
var boundNodeClass = context.TargetSymbol as INamedTypeSymbol;

parameters.Add(Parameter(Identifier("diagnosticBuilder"))
.WithType(ParseTypeName("DiagnosticBag.Builder"))
.WithDefault(EqualsValueClause(
LiteralExpression(SyntaxKind.NullLiteralExpression))));
Properties = boundNodeClass
.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => !p.IsReadOnly)
.Select(p => (p.GetPropertyTypeName(), p));
}

var statements = new List<StatementSyntax>()
{
// equivalent to "diagnosticBuilder ??= new();"
ExpressionStatement(
AssignmentExpression(
SyntaxKind.CoalesceAssignmentExpression,
IdentifierName("diagnosticBuilder"),
ImplicitObjectCreationExpression()))
};

statements.AddRange(
properties
.Where(p => p.Type.IsDerivedFrom(boundNodeType))
.Select(p => ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("diagnosticBuilder"),
IdentifierName("Add")))
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(IdentifierName(p.CamelCasedName()))))))));

statements.AddRange(
properties
.Where(p => p.Type is INamedTypeSymbol t
&& t.IsGenericType
&& t.TypeArguments.Any(t => t.IsDerivedFrom(boundNodeType)))
.Select(p => ExpressionStatement(
InvocationExpression(
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("diagnosticBuilder"),
IdentifierName("AddRange")))
.WithArgumentList(ArgumentList(SingletonSeparatedList(
Argument(IdentifierName(p.CamelCasedName()))))))));

var initializerAssignmentExpressions = new List<ExpressionSyntax>()
public string WriteParameters()
=> string.Join("\n", Properties.Select(p => $"{p.Name} {p.Property.CamelCasedName()},"));

public string WriteDiagnostics()
{
AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName("SyntaxNode"),
IdentifierName("syntaxNode")),

AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName("DiagnosticBuilder"),
IdentifierName("diagnosticBuilder"))
};

initializerAssignmentExpressions.AddRange(
properties.Select(p => AssignmentExpression(
SyntaxKind.SimpleAssignmentExpression,
IdentifierName(p.Name),
IdentifierName(p.CamelCasedName()))));

statements.Add(ReturnStatement(
ImplicitObjectCreationExpression()
.WithInitializer(InitializerExpression(
SyntaxKind.ObjectInitializerExpression,
SeparatedList(initializerAssignmentExpressions)))));

return MethodDeclaration(ParseTypeName(returnType.Name), $"Create{returnType.Name}")
.WithModifiers(
TokenList(
new[] { Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword) }))
.WithParameterList(ParameterList(SeparatedList(parameters)))
.WithBody(Block(List(statements)));
var statements = new List<string>();
var boundNodeType = context.SemanticModel.Compilation.GetTypeByMetadataName(BoundNodeTypeName);

statements.AddRange(
Properties
.Where(p => p.Property.Type.IsDerivedFrom(boundNodeType))
.Select(p => $"diagnosticBuilder.Add({p.Property.CamelCasedName()});"));

statements.AddRange(
Properties
.Where(p => p.Property.Type is INamedTypeSymbol t
&& t.IsGenericType
&& t.TypeArguments.Any(t => t.IsDerivedFrom(boundNodeType)))
.Select(p => $"diagnosticBuilder.AddRange({p.Property.CamelCasedName()});"));

return string.Join("\n", statements);
}

public string WriteInitializers()
=> string.Join("\n", Properties.Select(p => $"{p.Property.Name} = {p.Property.CamelCasedName()},"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Todl.Compiler.SourceGenerators;

[Generator]
internal sealed class BoundTreeVisitorSourceGenerator : IIncrementalGenerator
{
private const string BoundTreeVisitorNamespace = "Todl.Compiler.CodeAnalysis.Binding.BoundTree";
private const string BoundTreeVisitorClassName = "BoundTreeVisitor";

public void Initialize(IncrementalGeneratorInitializationContext context)
{
var pipeline = context.SyntaxProvider.ForAttributeWithMetadataName(
fullyQualifiedMetadataName: $"{BoundTreeVisitorNamespace}.BoundNodeAttribute",
predicate: static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
transform: static (context, _) => context.TargetSymbol);

context.RegisterSourceOutput(pipeline, GenerateBoundTreeVisitorMethods);
}

static void GenerateBoundTreeVisitorMethods(SourceProductionContext context, ISymbol symbol)
{
try
{
var className = symbol.Name;
var camelCaseClassName = symbol.CamelCasedName();

var sourceText = SourceText.From($$"""
namespace {{BoundTreeVisitorNamespace}};
internal abstract partial class {{BoundTreeVisitorClassName}}
{
[System.CodeDom.Compiler.GeneratedCode("{{nameof(BoundTreeVisitorSourceGenerator)}}", "1.0.0.0")]
public virtual BoundNode Visit{{className}}({{className}} {{camelCaseClassName}}) => DefaultVisit({{camelCaseClassName}});
}
""", Encoding.UTF8);

context.AddSource($"{BoundTreeVisitorClassName}.{className}.g.cs", sourceText);
}
catch (Exception ex)
{
context.ReportDiagnostic(
Diagnostic.Create(
descriptor: new DiagnosticDescriptor(
id: "TODL000",
title: "Error occurred while generating BoundTreeVisitor",
messageFormat: ex.Message + ex.StackTrace,
category: typeof(BoundTreeVisitorSourceGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true,
description: ex.Message + ex.StackTrace),
location: null));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- this is a workaround for omnisharp (and by extension, vscode) to work -->
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using FluentAssertions;
using Todl.Compiler.CodeAnalysis.Binding;
using Todl.Compiler.CodeAnalysis.Binding.BoundTree;
using Todl.Compiler.CodeAnalysis.Symbols;
using Xunit;

Expand Down
Loading

0 comments on commit 9d33100

Please sign in to comment.