diff --git a/Fluid.Benchmarks/BaseBenchmarks.cs b/Fluid.Benchmarks/BaseBenchmarks.cs
index 4c3b00bb..0ee74f07 100644
--- a/Fluid.Benchmarks/BaseBenchmarks.cs
+++ b/Fluid.Benchmarks/BaseBenchmarks.cs
@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.IO;
-using System.Reflection;
namespace Fluid.Benchmarks
{
diff --git a/Fluid.Generator/Fluid.Generator.csproj b/Fluid.Generator/Fluid.Generator.csproj
new file mode 100644
index 00000000..de77a15b
--- /dev/null
+++ b/Fluid.Generator/Fluid.Generator.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netstandard2.0
+ enable
+ enable
+ latest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Fluid.Generator/LiquidGenerator.cs b/Fluid.Generator/LiquidGenerator.cs
new file mode 100644
index 00000000..0f9fbc53
--- /dev/null
+++ b/Fluid.Generator/LiquidGenerator.cs
@@ -0,0 +1,216 @@
+using Fluid;
+using Fluid.Compilation;
+using Fluid.Parser;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using System.Diagnostics;
+using System.Reflection;
+using System.Text;
+
+#nullable enable
+
+[Generator]
+public class LiquidGenerator : ISourceGenerator
+{
+ public void Execute(GeneratorExecutionContext context)
+ {
+ Debug.WriteLine("Execute code generator");
+
+#if DEBUG
+ //if (!Debugger.IsAttached)
+ //{
+ // Debugger.Launch();
+ //}
+#endif
+
+ var receiver = context.SyntaxReceiver as LiquidRenderReceiver;
+ if (receiver is null) return;
+
+ StringBuilder sb = new();
+ sb.AppendLine($@"// Source Generated at {DateTimeOffset.Now:R}
+using System;
+using System.Buffers;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using Fluid;
+using Fluid.Values;
+
+public class LiquidTemplates
+{{
+");
+ sb.AppendLine("// Seeking for additional files...");
+ foreach (var file in context.AdditionalFiles)
+ {
+ sb.AppendLine($"// Processing '{file.Path}'");
+
+ var isLiquidTemplate = string.Equals(
+ context.AnalyzerConfigOptions.GetOptions(file).TryGetAdditionalFileMetadataValue("IsLiquidTemplate"),
+ "true",
+ StringComparison.OrdinalIgnoreCase
+ );
+
+ if (!isLiquidTemplate)
+ continue;
+
+ var content = file.GetText(context.CancellationToken)?.ToString();
+
+ if (string.IsNullOrWhiteSpace(content))
+ continue;
+
+ ProcessFile(context, file.Path, content!, receiver, sb);
+ }
+
+ sb.AppendLine(@"
+} // end
+");
+ context.AddSource("LiquidTemplates", sb.ToString());
+ }
+
+ private static void ProcessFile(in GeneratorExecutionContext context, string filePath, string content, LiquidRenderReceiver? receiver, StringBuilder builder)
+ {
+ // Generate class name from file name
+ var templateName = SanitizeIdentifier(Path.GetFileNameWithoutExtension(filePath));
+
+ // Always output non-specific writer
+ builder.AppendLine(@$"
+ public static void Render{templateName}(T model, TextWriter writer)
+ {{
+ // Emitted as an initial call site for the template,
+ // when actually called a specific call site for the exact model will be additionally be emitted.
+ throw new NotImplementedException();
+ }}
+");
+
+ List? invocations = null;
+ if (receiver?.Invocations?.TryGetValue(templateName, out invocations) ?? false)
+ {
+ Debug.Assert(invocations != null);
+
+ foreach (var invocation in invocations!)
+ {
+ var arguments = invocation.ArgumentList.Arguments;
+ if (arguments.Count != 2) continue;
+
+ var semanticModel = context.Compilation.GetSemanticModel(invocation.SyntaxTree);
+ var modelType = semanticModel.GetTypeInfo(arguments[0].Expression).Type;
+
+ var parser = new FluidParser();
+
+ //var template = parser.Parse(content) as FluidTemplate;
+
+ //var compiler = new AstCompiler();
+
+ //compiler.RenderTemplate(modelType!, templateName, template!, builder);
+ }
+ }
+ }
+
+ private static string SanitizeIdentifier(string symbolName)
+ {
+ if (string.IsNullOrWhiteSpace(symbolName)) return string.Empty;
+
+ var sb = new StringBuilder(symbolName.Length);
+ if (!char.IsLetter(symbolName[0]))
+ {
+ // Must start with a letter or an underscore
+ sb.Append('_');
+ }
+
+ var capitalize = true;
+ foreach (var ch in symbolName)
+ {
+ if (!char.IsLetterOrDigit(ch))
+ {
+ capitalize = true;
+ continue;
+ }
+
+ sb.Append(capitalize ? char.ToUpper(ch) : ch);
+ capitalize = false;
+ }
+
+ return sb.ToString();
+ }
+
+ public void Initialize(GeneratorInitializationContext context)
+ => context.RegisterForSyntaxNotifications(() => new LiquidRenderReceiver());
+
+ class LiquidRenderReceiver : ISyntaxReceiver
+ {
+ public Dictionary>? Invocations { get; private set; }
+
+ public void OnVisitSyntaxNode(SyntaxNode node)
+ {
+ if (node.IsKind(SyntaxKind.InvocationExpression) &&
+ node is InvocationExpressionSyntax invocation)
+ {
+ var expression = invocation.Expression;
+ if (expression is MemberAccessExpressionSyntax member)
+ {
+ var isLiquid = false;
+ string? template = null;
+ if (member.IsKind(SyntaxKind.SimpleMemberAccessExpression))
+ {
+ foreach (SyntaxNode child in expression.ChildNodes())
+ {
+ if (!isLiquid)
+ {
+ if (child is IdentifierNameSyntax classIdent)
+ {
+ var valueText = classIdent.Identifier.ValueText;
+ // Console.Error.WriteLine(valueText);
+ if (classIdent.Identifier.ValueText == "LiquidTemplates")
+ {
+ isLiquid = true;
+ continue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ if (child is IdentifierNameSyntax methodIdent)
+ {
+ var valueText = methodIdent.Identifier.ValueText;
+ if (valueText.IndexOf("Render", StringComparison.Ordinal) == 0)
+ {
+ template = valueText.Substring("Render".Length);
+ }
+ break;
+ }
+ }
+
+ if (isLiquid && template is not null)
+ {
+ if ((Invocations ??= new()).TryGetValue(template, out var list))
+ {
+ list.Add(invocation);
+ }
+ else
+ {
+ Invocations.Add(template, new() { invocation });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+internal static class SourceGeneratorExtensions
+{
+ public static string? TryGetValue(this AnalyzerConfigOptions options, string key) =>
+ options.TryGetValue(key, out var value) ? value : null;
+
+ public static string? TryGetAdditionalFileMetadataValue(this AnalyzerConfigOptions options, string propertyName) =>
+ options.TryGetValue($"build_metadata.AdditionalFiles.{propertyName}");
+}
\ No newline at end of file
diff --git a/Fluid.MvcViewEngine/Fluid.MvcViewEngine.csproj b/Fluid.MvcViewEngine/Fluid.MvcViewEngine.csproj
index d6b2bb0a..a62198e8 100644
--- a/Fluid.MvcViewEngine/Fluid.MvcViewEngine.csproj
+++ b/Fluid.MvcViewEngine/Fluid.MvcViewEngine.csproj
@@ -1,7 +1,7 @@
- netcoreapp3.1;net5.0;net6.0;net7.0
+ netcoreapp3.1
latest
logo_64x64.png
true
diff --git a/Fluid.SandBox/Fluid.SandBox.csproj b/Fluid.SandBox/Fluid.SandBox.csproj
new file mode 100644
index 00000000..6f6b8bd0
--- /dev/null
+++ b/Fluid.SandBox/Fluid.SandBox.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net7.0
+ enable
+ enable
+ 11
+
+
+
+
+
+
+
diff --git a/Fluid.SandBox/Program.cs b/Fluid.SandBox/Program.cs
new file mode 100644
index 00000000..2455b573
--- /dev/null
+++ b/Fluid.SandBox/Program.cs
@@ -0,0 +1,161 @@
+using Fluid;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Encodings.Web;
+
+var source = @"
+{%- for f in fortunes -%}
+ | {{ f.Id }} | {{ f.Message }} |
+{%- endfor -%}
+
";
+
+var templates = new Dictionary();
+
+var sw = Stopwatch.StartNew();
+
+var parser = new FluidParser();
+
+var sb = new StringBuilder(2048);
+var writer = new StringWriter(sb);
+
+var fortunes = new Fortune[] {
+ new (0, "Additional fortune added at request time."),
+ new (1, "fortune: No such file or directory"),
+ new (2, "A computer scientist is someone who fixes things that aren't broken."),
+ new (3, "After enough decimal places, nobody gives a damn."),
+ new (4, "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1"),
+ new (5, "A computer program does what you tell it to do, not what you want it to do."),
+ new (6, "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen"),
+ new (7, "Any program that runs right is obsolete."),
+ new (8, "A list is only as strong as its weakest link. — Donald Knuth"),
+ new (9, "Feature: A bug with seniority."),
+ new (10, "Computers make very fast, very accurate mistakes."),
+ new (11, "