Skip to content

Commit

Permalink
Merge pull request #548 from ncqa-org/static_class
Browse files Browse the repository at this point in the history
547 Static classes and libraries on 1.0
  • Loading branch information
ewoutkramer authored Oct 1, 2024
2 parents 5ad85b3 + c1ea76e commit 309cb0a
Show file tree
Hide file tree
Showing 39 changed files with 3,882 additions and 9,159 deletions.
146 changes: 8 additions & 138 deletions Cql/CodeGeneration.NET/CSharpSourceCodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,111 +198,36 @@ 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 +271,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 +280,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 +306,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 +324,7 @@ 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"));
writer.Write(expressionConverter.ConvertTopLevelFunctionDefinition(indentLevel, overload, methodName!, "public"));
}
else
{
Expand Down
17 changes: 10 additions & 7 deletions Cql/CodeGeneration.NET/ExpressionConverter.cs
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 309cb0a

Please sign in to comment.