Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

547 Prototype to make library types singleton instead of static on 1.0 #552

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 11 additions & 141 deletions Cql/CodeGeneration.NET/CSharpSourceCodeWriter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
/*
* Copyright (c) 2023, NCQA and contributors
* See the file CONTRIBUTORS for details.
*
*
* This file is licensed under the BSD 3-Clause license
* available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE
*/
Expand Down Expand Up @@ -103,7 +103,7 @@ public CSharpSourceCodeWriter(ILogger<CSharpSourceCodeWriter> log)
/// <param name="libraryNameToStream">A function that provides a <see cref="Stream"/> to write the source code given the name of the library being generated.</param>
/// <param name="closeStream">When <see langword="true"/>, <see cref="Stream"/>s provided by <paramref name="libraryNameToStream"/> will be closed when writing is done. Default value is <see langword="true"/></param>
/// <param name="writeFile">A function that determines whether the given library should be generated or not; default is <see langword="null" />. When <see langword="null" />, all libraries are written.</param>
///
///
public void Write(DefinitionDictionary<LambdaExpression> definitions,
IEnumerable<Type> tupleTypes,
DirectedGraph dependencyGraph,
Expand Down Expand Up @@ -198,111 +198,35 @@ private void writeClass(DefinitionDictionary<LambdaExpression> definitions,
writer.WriteLine(indentLevel, $"[CqlLibrary(\"{libraryAttribute}\", \"{versionAttribute}\")]");
var className = VariableNameGenerator.NormalizeIdentifier(libraryName);
if (PartialClass)
writer.WriteLine(indentLevel, $"partial class {className}");
writer.WriteLine(indentLevel, $"public partial class {className}");
else
writer.WriteLine(indentLevel, $"public class {className}");
writer.WriteLine(indentLevel, "{");
writer.WriteLine();
indentLevel += 1;
// Class
{
writer.WriteLine(indentLevel, $"public static {className} Instance {{ get; }} = new();");
writer.WriteLine();

writer.WriteLine(indentLevel, $"{AccessModifierString(ContextAccessModifier)} CqlContext context;");
writer.WriteLine();
writeCachedValues(definitions, libraryName, writer, indentLevel);

// Write constructor
writer.WriteLine(indentLevel, $"public {className}(CqlContext context)");
writer.WriteLine(indentLevel, "{");
{
indentLevel += 1;

writer.WriteLine(indentLevel, "this.context = context ?? throw new ArgumentNullException(\"context\");");
writer.WriteLine();

writeDependencies(dependencyGraph, libraryNameToClassName, libraryName, writer, indentLevel);
writer.WriteLine();
writeCachedValueNames(definitions, libraryName, writer, indentLevel);
indentLevel -= 1;
}
writer.WriteLine(indentLevel, "}");

WriteLibraryMembers(writer, dependencyGraph, libraryName, libraryNameToClassName!, indentLevel);
writeMemoizedInstanceMethods(definitions, libraryName, writer, indentLevel);
writeMethods(definitions, libraryName, writer, indentLevel);
indentLevel -= 1;
writer.WriteLine(indentLevel, "}");
}
}

private void writeMemoizedInstanceMethods(DefinitionDictionary<LambdaExpression> definitions, string libraryName, StreamWriter writer, int indentLevel)
private void writeMethods(DefinitionDictionary<LambdaExpression> definitions, string libraryName, StreamWriter writer, int indentLevel)
{
foreach (var kvp in definitions.DefinitionsForLibrary(libraryName))
{
foreach (var overload in kvp.Value)
{
definitions.TryGetTags(libraryName, kvp.Key, overload.Signature, out var tags);
WriteMemoizedInstanceMethod(libraryName, writer, indentLevel, kvp.Key, overload.T, tags);
writeMethod(libraryName, writer, indentLevel, kvp.Key, overload.T, tags);
writer.WriteLine();
}
}
}

private void writeCachedValueNames(DefinitionDictionary<LambdaExpression> definitions, string libraryName, StreamWriter writer, int indentLevel)
{
foreach (var kvp in definitions.DefinitionsForLibrary(libraryName))
{
foreach (var overload in kvp.Value)
{
if (isDefinition(overload.Item2))
{
var methodName = VariableNameGenerator.NormalizeIdentifier(kvp.Key);
var cachedValueName = DefinitionCacheKeyForMethod(methodName!);
var returnType = ExpressionConverter.PrettyTypeName(overload.Item2.ReturnType);
var privateMethodName = PrivateMethodNameFor(methodName!);
writer.WriteLine(indentLevel, $"{cachedValueName} = new Lazy<{returnType}>(this.{privateMethodName});");
}
}
}
}

private static void writeDependencies(DirectedGraph dependencyGraph, Func<string?, string?> libraryNameToClassName, string libraryName, StreamWriter writer, int indentLevel)
{
var node = dependencyGraph.Nodes[libraryName];
var requiredLibraries = node.ForwardEdges?
.Select(edge => edge.ToId)
.Except(new[] { dependencyGraph.EndNode.NodeId })
.Distinct();
foreach (var dependentLibrary in requiredLibraries!)
{
var typeName = libraryNameToClassName!(dependentLibrary);
var memberName = typeName;
writer.WriteLine(indentLevel, $"{memberName} = new {typeName}(context);");
}
}

private void writeCachedValues(DefinitionDictionary<LambdaExpression> definitions, string libraryName, StreamWriter writer, int indentLevel)
{
writer.WriteLine(indentLevel, "#region Cached values");
writer.WriteLine();
var accessModifier = AccessModifierString(DefinesAccessModifier);
foreach (var kvp in definitions.DefinitionsForLibrary(libraryName))
{
foreach (var overload in kvp.Value)
{
if (isDefinition(overload.T))
{
var methodName = VariableNameGenerator.NormalizeIdentifier(kvp.Key);
var cachedValueName = DefinitionCacheKeyForMethod(methodName!);
var returnType = ExpressionConverter.PrettyTypeName(overload.T.ReturnType);
writer.WriteLine(indentLevel, $"{accessModifier} Lazy<{returnType}> {cachedValueName};");
}
}
}
writer.WriteLine();
writer.WriteLine(indentLevel, "#endregion");
}

private void writeTupleTypes(IEnumerable<Type> tupleTypes, Func<string, Stream> libraryNameToStream, bool closeStream)
{
if (tupleTypes.Any())
Expand Down Expand Up @@ -346,36 +270,6 @@ private static bool isDefinition(LambdaExpression overload) =>
overload.Parameters.Count == 1
&& overload.Parameters[0].Type == typeof(CqlContext);

private void WriteLibraryMembers(TextWriter writer,
DirectedGraph dependencyGraph,
string libraryName,
Func<string, string> libraryNameToClassName,
int indent)
{
var node = dependencyGraph.Nodes[libraryName];
var requiredLibraries = node.ForwardEdges?
.Select(edge => edge.ToId)
.Except(new[] { dependencyGraph.EndNode.NodeId })
.Distinct();
if (requiredLibraries != null)
{

writer.WriteLine(indent, "#region Dependencies");
writer.WriteLine();

foreach (var dependentLibrary in requiredLibraries)
{
var typeName = libraryNameToClassName(dependentLibrary);
var memberName = typeName;
writer.WriteLine(indent, $"public {typeName} {memberName} {{ get; }}");
}

writer.WriteLine();
writer.WriteLine(indent, "#endregion");
writer.WriteLine();
}
}

private IList<DirectedGraphNode> DetermineBuildOrder(DirectedGraph minimalGraph)
{
var sorted = minimalGraph.TopologicalSort()
Expand All @@ -385,15 +279,7 @@ private IList<DirectedGraphNode> DetermineBuildOrder(DirectedGraph minimalGraph)
return sorted;
}

private string DefinitionCacheKeyForMethod(string methodName)
{
if (methodName[0] == '@')
return "__" + methodName.Substring(1);
else return "__" + methodName;
}
private string PrivateMethodNameFor(string methodName) => methodName + "_Value";

private void WriteMemoizedInstanceMethod(string libraryName, TextWriter writer, int indentLevel,
private void writeMethod(string libraryName, TextWriter writer, int indentLevel,
string cqlName,
LambdaExpression overload,
ILookup<string, string>? tags)
Expand All @@ -419,16 +305,6 @@ private void WriteMemoizedInstanceMethod(string libraryName, TextWriter writer,

if (isDef)
{
// Definitions, which are CQL expressions without parameter, can be memoized,
// so we generate a "generator" function (name ending in _Value) and a
// getter function, which just calls triggers the lazy to invoke this
// first _Value method.
var cachedValueName = DefinitionCacheKeyForMethod(methodName!);
var privateMethodName = PrivateMethodNameFor(methodName!);

var func = expressionConverter.ConvertTopLevelFunctionDefinition(indentLevel, overload, privateMethodName, "private");
writer.Write(func);
writer.WriteLine();
writer.WriteLine(indentLevel, $"[CqlDeclaration(\"{cqlName}\")]");
WriteTags(writer, indentLevel, tags);

Expand All @@ -447,14 +323,8 @@ private void WriteMemoizedInstanceMethod(string libraryName, TextWriter writer,
}
}

var lazyType = typeof(Lazy<>).MakeGenericType(visitedBody.Type);
var valueFunc =
Expression.Lambda(
Expression.MakeMemberAccess(
Expression.Parameter(lazyType, cachedValueName),
lazyType.GetMember("Value").Single()));

writer.Write(expressionConverter.ConvertTopLevelFunctionDefinition(indentLevel, valueFunc, methodName!, "public"));
var func = expressionConverter.ConvertTopLevelFunctionDefinition(indentLevel, overload, methodName!, "public");
writer.Write(func);
}
else
{
Expand Down
23 changes: 13 additions & 10 deletions Cql/CodeGeneration.NET/ExpressionConverter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
/*
* Copyright (c) 2023, NCQA and contributors
* See the file CONTRIBUTORS for details.
*
*
* This file is licensed under the BSD 3-Clause license
* available at https://raw.githubusercontent.com/FirelyTeam/cql-sdk/main/LICENSE
*/
Expand Down Expand Up @@ -66,11 +66,10 @@ private string convertDefinitionCallExpression(int indent, string leadingIndentS
var sb = new StringBuilder();
sb.Append(leadingIndentString);

var target = dce.LibraryName == LibraryName ? "this" :
VariableNameGenerator.NormalizeIdentifier(dce.LibraryName);
var target = dce.LibraryName == LibraryName ? "this" : $"{VariableNameGenerator.NormalizeIdentifier(dce.LibraryName)}.Instance";
var csFunctionName = VariableNameGenerator.NormalizeIdentifier(dce.DefinitionName);

sb.Append(CultureInfo.InvariantCulture, $"{target}.{csFunctionName}()");
sb.Append(CultureInfo.InvariantCulture, $"{target}.{csFunctionName}(context)");

return sb.ToString();
}
Expand All @@ -80,12 +79,11 @@ private string convertFunctionCallExpression(int indent, string leadingIndentStr
var sb = new StringBuilder();
sb.Append(leadingIndentString);

var target = fce.LibraryName == LibraryName ? "this" :
VariableNameGenerator.NormalizeIdentifier(fce.LibraryName);
var target = fce.LibraryName == LibraryName ? "this" : $"{VariableNameGenerator.NormalizeIdentifier(fce.LibraryName)}.Instance";
var csFunctionName = VariableNameGenerator.NormalizeIdentifier(fce.FunctionName);

sb.Append(CultureInfo.InvariantCulture, $"{target}.{csFunctionName}");
sb.Append(convertArguments(indent, fce.Arguments.Skip(1))); // skip cqlContext
sb.Append(convertArguments(indent, fce.Arguments));

return sb.ToString();
}
Expand Down Expand Up @@ -417,7 +415,12 @@ private string convertLambdaExpression(int indent, string leadingIndentString, L
var lambdaSb = new StringBuilder();
lambdaSb.Append(leadingIndentString);

var lambdaParameters = $"({string.Join(", ", lambda.Parameters.Select(p => $"{PrettyTypeName(p.Type)} {escapeKeywords(p.Name!)}"))})";
var parameters = lambda.Parameters.Select(p => $"{PrettyTypeName(p.Type)} {escapeKeywords(p.Name!)}").ToList();
// inserts the context parameter in the start of the lambda expression
if (indent == 1)
parameters.Insert(0, "CqlContext context");

var lambdaParameters = $"({string.Join(", ", parameters)})";
lambdaSb.Append(lambdaParameters);

if (lambda.Body is BlockExpression)
Expand Down Expand Up @@ -475,7 +478,7 @@ public string ConvertTopLevelFunctionDefinition(int indent, LambdaExpression fun

// Linq.Expressions needs an explicit conversion from a value type
// type to object, but the C# compiler will insert that boxing,
// so we can remove those casts.
// so we can remove those casts.
private static Expression StripBoxing(Expression node)
{
// (x as object) => x
Expand Down
Loading