diff --git a/Cql/CodeGeneration.NET/AssemblyCompiler.cs b/Cql/CodeGeneration.NET/AssemblyCompiler.cs index 7800bea2e..acd7638c5 100644 --- a/Cql/CodeGeneration.NET/AssemblyCompiler.cs +++ b/Cql/CodeGeneration.NET/AssemblyCompiler.cs @@ -28,7 +28,6 @@ namespace Hl7.Cql.CodeGeneration.NET { internal class AssemblyCompiler { - private readonly TypeManager _typeManager; private readonly CSharpLibrarySetToStreamsWriter _cSharpLibrarySetToStreamsWriter; private readonly CSharpCodeStreamPostProcessor? _cSharpCodeStreamPostProcessor; private readonly Lazy _referencesLazy; @@ -36,13 +35,12 @@ internal class AssemblyCompiler public AssemblyCompiler( CSharpLibrarySetToStreamsWriter cSharpLibrarySetToStreamsWriter, - TypeManager typeManager, + TypeResolver typeResolver, CSharpCodeStreamPostProcessor? cSharpCodeStreamPostProcessor = null, AssemblyDataPostProcessor? assemblyDataPostProcessor = null) { _assemblyDataPostProcessor = assemblyDataPostProcessor; _cSharpLibrarySetToStreamsWriter = cSharpLibrarySetToStreamsWriter; - _typeManager = typeManager; _cSharpCodeStreamPostProcessor = cSharpCodeStreamPostProcessor; _referencesLazy = new Lazy( () => @@ -62,7 +60,7 @@ public AssemblyCompiler( } // @formatter on .Select(type => type.Assembly) - .Concat(typeManager.Resolver.ModelAssemblies) + .Concat(typeResolver.ModelAssemblies) .Distinct() .ToArray(); return references; diff --git a/Cql/CodeGeneration.NET/PostProcessors/WriteToFileCSharpCodeStreamPostProcessor.cs b/Cql/CodeGeneration.NET/PostProcessors/WriteToFileCSharpCodeStreamPostProcessor.cs index af05fcd63..a82352393 100644 --- a/Cql/CodeGeneration.NET/PostProcessors/WriteToFileCSharpCodeStreamPostProcessor.cs +++ b/Cql/CodeGeneration.NET/PostProcessors/WriteToFileCSharpCodeStreamPostProcessor.cs @@ -11,18 +11,13 @@ namespace Hl7.Cql.CodeGeneration.NET.PostProcessors; -internal class WriteToFileCSharpCodeStreamPostProcessor : CSharpCodeStreamPostProcessor +internal class WriteToFileCSharpCodeStreamPostProcessor( + IOptions csharpCodeWriterOptions, + ILogger logger) + : CSharpCodeStreamPostProcessor { - private readonly CSharpCodeWriterOptions _csharpCodeWriterOptions; - private readonly ILogger _logger; - - public WriteToFileCSharpCodeStreamPostProcessor( - IOptions csharpCodeWriterOptions, - ILogger logger) - { - _logger = logger; - _csharpCodeWriterOptions = csharpCodeWriterOptions.Value; - } + private readonly CSharpCodeWriterOptions _csharpCodeWriterOptions = csharpCodeWriterOptions.Value; + private readonly ILogger _logger = logger; public override void ProcessStream(string name, Stream stream) { diff --git a/Cql/CoreTests/ExpressionBuilderTests.cs b/Cql/CoreTests/ExpressionBuilderTests.cs index 2ed0ecf38..c293ea84a 100644 --- a/Cql/CoreTests/ExpressionBuilderTests.cs +++ b/Cql/CoreTests/ExpressionBuilderTests.cs @@ -18,7 +18,7 @@ public void AggregateQueries_1_0_0() var cqlCompilerServices = CqlServicesInitializer.CreateCqlCompilerServices(disposeContext.Token); var elm = new FileInfo(@"Input\ELM\Test\Aggregates-1.0.0.json"); var elmPackage = Hl7.Cql.Elm.Library.LoadFromJson(elm); - var definitions = cqlCompilerServices.LibraryExpressionBuilder.ProcessLibrary(elmPackage); + var definitions = cqlCompilerServices.LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage); Assert.IsNotNull(definitions); Assert.IsTrue(definitions.Libraries.Any()); } @@ -30,7 +30,7 @@ public void FHIRConversionTest_1_0_0() var cqlCompilerServices = CqlServicesInitializer.CreateCqlCompilerServices(disposeContext.Token); var elm = new FileInfo(@"Input\ELM\HL7\FHIRConversionTest.json"); var elmPackage = Hl7.Cql.Elm.Library.LoadFromJson(elm); - var definitions = cqlCompilerServices.LibraryExpressionBuilder.ProcessLibrary(elmPackage); + var definitions = cqlCompilerServices.LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage); Assert.IsNotNull(definitions); Assert.IsTrue(definitions.Libraries.Any()); } @@ -42,7 +42,7 @@ public void QueriesTest_1_0_0() var cqlCompilerServices = CqlServicesInitializer.CreateCqlCompilerServices(disposeContext.Token); var elm = new FileInfo(@"Input\ELM\Test\QueriesTest-1.0.0.json"); var elmPackage = Hl7.Cql.Elm.Library.LoadFromJson(elm); - var definitions = cqlCompilerServices.LibraryExpressionBuilder.ProcessLibrary(elmPackage); + var definitions = cqlCompilerServices.LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage); Assert.IsNotNull(definitions); Assert.IsTrue(definitions.Libraries.Any()); } @@ -66,7 +66,7 @@ public void Medication_Request_Example_Test() var fs = new FhirDateTime(fdts); Assert.AreEqual(fdt, fs); - var definitions = cqlCompilerServices.LibrarySetExpressionBuilder.ProcessLibrarySet(librarySet); + var definitions = cqlCompilerServices.LibrarySetExpressionBuilderScoped().ProcessLibrarySet(librarySet); Assert.IsNotNull(definitions); Assert.IsTrue(definitions.Libraries.Any()); } @@ -77,7 +77,7 @@ public void Get_Property_Uses_TypeResolver() { using var disposeContext = new DisposeContext(); var cqlCompilerServices = CqlServicesInitializer.CreateCqlCompilerServices(disposeContext.Token); - var property = ExpressionBuilder.GetProperty(typeof(MeasureReport.PopulationComponent), "id", cqlCompilerServices.TypeManager.Resolver)!; + var property = ExpressionBuilder.GetProperty(typeof(MeasureReport.PopulationComponent), "id", cqlCompilerServices.TypeResolver)!; Assert.AreEqual(typeof(Element), property.DeclaringType); Assert.AreEqual(nameof(Element.ElementId), property.Name); } diff --git a/Cql/CoreTests/LibrarySetExpressionBuilderTests.cs b/Cql/CoreTests/LibrarySetExpressionBuilderTests.cs index b97c482d4..98c4bc321 100644 --- a/Cql/CoreTests/LibrarySetExpressionBuilderTests.cs +++ b/Cql/CoreTests/LibrarySetExpressionBuilderTests.cs @@ -16,7 +16,7 @@ public void LoadLibraryAndDependencies_CrossLibraryCodeSystems() LibrarySet librarySet = new(); librarySet.LoadLibraryAndDependencies(LibrarySetsDirs.Cms.ElmDir, "CumulativeMedicationDuration"); - var definitionDictionary = cqlCompilerServices.LibrarySetExpressionBuilder.ProcessLibrarySet(librarySet); + var definitionDictionary = cqlCompilerServices.LibrarySetExpressionBuilderScoped().ProcessLibrarySet(librarySet); var lambdaExpression = definitionDictionary["CumulativeMedicationDuration-4.0.000", "Every eight hours (qualifier value)"]; Assert.IsNotNull(lambdaExpression); diff --git a/Cql/CoreTests/PrimitiveTests.cs b/Cql/CoreTests/PrimitiveTests.cs index 32931371b..853efb218 100644 --- a/Cql/CoreTests/PrimitiveTests.cs +++ b/Cql/CoreTests/PrimitiveTests.cs @@ -3411,7 +3411,7 @@ public void Aggregate_Query_Test() var librarySet = new LibrarySet(); librarySet.LoadLibraryAndDependencies(new DirectoryInfo("Input\\ELM\\Test"),"Aggregates", "1.0.0"); var elmPackage = librarySet.GetLibrary("Aggregates-1.0.0"); - var definitions = cqlCodeGenerationServices.GetCqlCompilerServices().LibraryExpressionBuilder.ProcessLibrary(elmPackage); + var definitions = cqlCodeGenerationServices.GetCqlCompilerServices().LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage); var writer = cqlCodeGenerationServices.CSharpLibrarySetToStreamsWriter; var isDone = false; writer.ProcessDefinitions( diff --git a/Cql/CoreTests/QueriesTest.cs b/Cql/CoreTests/QueriesTest.cs index 08eb53979..030e1161b 100644 --- a/Cql/CoreTests/QueriesTest.cs +++ b/Cql/CoreTests/QueriesTest.cs @@ -23,7 +23,7 @@ public static void Initialize(TestContext context) var elm = new FileInfo(@"Input\ELM\Test\QueriesTest-1.0.0.json"); var elmPackage = Hl7.Cql.Elm.Library.LoadFromJson(elm); - var definitions = cqlCompilerServices.LibraryExpressionBuilder.ProcessLibrary(elmPackage); + var definitions = cqlCompilerServices.LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage); QueriesDefinitions = definitions.CompileAll(); ValueSets = new HashValueSetDictionary(); ValueSets.Add("http://hl7.org/fhir/ValueSet/example-expansion", @@ -32,7 +32,7 @@ public static void Initialize(TestContext context) elm = new FileInfo(@"Input\ELM\Test\Aggregates-1.0.0.json"); elmPackage = Hl7.Cql.Elm.Library.LoadFromJson(elm); - cqlCompilerServices.LibraryExpressionBuilder.ProcessLibrary(elmPackage, libraryDefinitions: definitions); + cqlCompilerServices.LibraryExpressionBuilderScoped().ProcessLibrary(elmPackage, libraryDefinitions: definitions); AggregatesDefinitions = definitions.CompileAll(); } diff --git a/Cql/Cql.Compiler/AmbiguousOverloadCorrector.cs b/Cql/Cql.Compiler/AmbiguousOverloadCorrector.cs index 9d39cee28..15bede3b3 100644 --- a/Cql/Cql.Compiler/AmbiguousOverloadCorrector.cs +++ b/Cql/Cql.Compiler/AmbiguousOverloadCorrector.cs @@ -6,7 +6,6 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE */ -using System.Collections; using System.Collections.Generic; using System.Linq; using Hl7.Cql.Elm; diff --git a/Cql/Cql.Compiler/CqlOperatorsBinder.cs b/Cql/Cql.Compiler/CqlOperatorsBinder.cs index 8e7abce36..b4ef227b9 100644 --- a/Cql/Cql.Compiler/CqlOperatorsBinder.cs +++ b/Cql/Cql.Compiler/CqlOperatorsBinder.cs @@ -24,38 +24,27 @@ namespace Hl7.Cql.Compiler /// by converting the method name and arguments /// to the appropriate overload of the method. /// - internal partial class CqlOperatorsBinder + /// + /// The logger used. + /// + /// + /// The type resolver used. + /// Note that if you provide a different instance of this class to , you will get errors at runtime. + /// + /// + /// If provided, this binding will use the supplied instance to determine whether + /// a conversion is possible. Note that if you provide a different instance of this class to , + /// you may get errors at runtime, because this binding will think a conversion is possible when at runtime it is not. + /// If not provided, only conversions defined in will be used. + /// + internal partial class CqlOperatorsBinder( + ILogger logger, + TypeResolver typeResolver, + TypeConverter typeConverter) { - private readonly ILogger _logger; - private readonly TypeConverter _typeConverter; - private readonly TypeResolver _typeResolver; - - - /// - /// Creates an instance. - /// - /// - /// The logger used. - /// - /// - /// The type resolver used. - /// Note that if you provide a different instance of this class to , you will get errors at runtime. - /// - /// - /// If provided, this binding will use the supplied instance to determine whether - /// a conversion is possible. Note that if you provide a different instance of this class to , - /// you may get errors at runtime, because this binding will think a conversion is possible when at runtime it is not. - /// If not provided, only conversions defined in will be used. - /// - public CqlOperatorsBinder( - ILogger logger, - TypeResolver typeResolver, - TypeConverter typeConverter) - { - _typeConverter = typeConverter; - _typeResolver = typeResolver; - _logger = logger; - } + private readonly ILogger _logger = logger; + private readonly TypeResolver _typeResolver = typeResolver; + private readonly TypeConverter _typeConverter = typeConverter; /// /// Facilitates binding to methods, diff --git a/Cql/Cql.Compiler/ExpressionBuilder.cs b/Cql/Cql.Compiler/ExpressionBuilder.cs index b4fbe7bd6..95778ba9c 100644 --- a/Cql/Cql.Compiler/ExpressionBuilder.cs +++ b/Cql/Cql.Compiler/ExpressionBuilder.cs @@ -8,9 +8,9 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; using System.Reflection; using Hl7.Cql.Abstractions; -using Hl7.Cql.Conversion; using Hl7.Cql.Elm; using Microsoft.Extensions.Logging; @@ -20,71 +20,60 @@ internal class ExpressionBuilder( ILogger logger, ExpressionBuilderSettings expressionBuilderSettings, CqlOperatorsBinder cqlOperatorsBinder, - TypeManager typeManager, - TypeConverter typeConverter, + TupleBuilderCache tupleBuilderCache, TypeResolver typeResolver, CqlContextBinder cqlContextBinder) { - internal readonly CqlOperatorsBinder _cqlOperatorsBinder = cqlOperatorsBinder; - internal readonly CqlContextBinder _cqlContextBinder = cqlContextBinder; - internal readonly TypeManager _typeManager = typeManager; - internal readonly ILogger _logger = logger; - internal readonly TypeConverter _typeConverter = typeConverter; - internal readonly TypeResolver _typeResolver = typeResolver; - internal readonly ExpressionBuilderSettings _expressionBuilderSettings = expressionBuilderSettings; + private readonly ILogger _logger = logger; + private readonly ExpressionBuilderSettings _expressionBuilderSettings = expressionBuilderSettings; + private readonly CqlOperatorsBinder _cqlOperatorsBinder = cqlOperatorsBinder; + private readonly TupleBuilderCache _tupleBuilderCache = tupleBuilderCache; + private readonly TypeResolver _typeResolver = typeResolver; + private readonly CqlContextBinder _cqlContextBinder = cqlContextBinder; /* * The ExpressionBuilderContext is created anew for each of the ProcessXXX methods. * This works, because all but the ProcessExpressionDef methods only change state - * on the ILibraryExpressionBuilderContext. + * on the LibraryExpressionBuilderContext. * * Only ProcessExpressionDef changes state on the ExpressionBuilderContext. */ - public void ProcessIncludes(ILibraryExpressionBuilderContext libCtx, IncludeDef includeDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessIncludes(includeDef); - } + public void ProcessIncludes(LibraryExpressionBuilderContext libCtx, IncludeDef includeDef) => + NewExpressionBuilderContext(libCtx) + .ProcessIncludes(includeDef); - public void ProcessValueSetDef(ILibraryExpressionBuilderContext libCtx, ValueSetDef valueSetDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessValueSetDef(valueSetDef); - } + internal ExpressionBuilderContext NewExpressionBuilderContext( + LibraryExpressionBuilderContext libCtx, + Dictionary? operands = null) => + new(_logger, _expressionBuilderSettings, _cqlOperatorsBinder, _tupleBuilderCache, _typeResolver, _cqlContextBinder, libCtx, operands); + + public void ProcessValueSetDef(LibraryExpressionBuilderContext libCtx, ValueSetDef valueSetDef) => + NewExpressionBuilderContext(libCtx) + .ProcessValueSetDef(valueSetDef); public void ProcessCodeDef( - ILibraryExpressionBuilderContext libCtx, + LibraryExpressionBuilderContext libCtx, CodeDef codeDef, - HashSet<(string codeName, string codeSystemUrl)> foundCodeNameCodeSystemUrls) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessCodeDef(codeDef, foundCodeNameCodeSystemUrls); - } + HashSet<(string codeName, string codeSystemUrl)> foundCodeNameCodeSystemUrls) => + NewExpressionBuilderContext(libCtx) + .ProcessCodeDef(codeDef, foundCodeNameCodeSystemUrls); - public void ProcessCodeSystemDef(ILibraryExpressionBuilderContext libCtx, CodeSystemDef codeSystemDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessCodeSystemDef(codeSystemDef); - } + public void ProcessCodeSystemDef(LibraryExpressionBuilderContext libCtx, CodeSystemDef codeSystemDef) => + NewExpressionBuilderContext(libCtx) + .ProcessCodeSystemDef(codeSystemDef); - public void ProcessConceptDef(ILibraryExpressionBuilderContext libCtx, ConceptDef conceptDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessConceptDef(conceptDef); - } + public void ProcessConceptDef(LibraryExpressionBuilderContext libCtx, ConceptDef conceptDef) => + NewExpressionBuilderContext(libCtx) + .ProcessConceptDef(conceptDef); - public void ProcessParameterDef(ILibraryExpressionBuilderContext libCtx, ParameterDef parameterDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx); - context.ProcessParameterDef(parameterDef); - } + public void ProcessParameterDef(LibraryExpressionBuilderContext libCtx, ParameterDef parameterDef) => + NewExpressionBuilderContext(libCtx) + .ProcessParameterDef(parameterDef); - public void ProcessExpressionDef(ILibraryExpressionBuilderContext libCtx, ExpressionDef expressionDef) - { - ExpressionBuilderContext context = new ExpressionBuilderContext(this, libCtx, new()); - context.ProcessExpressionDef(expressionDef); - } + public void ProcessExpressionDef(LibraryExpressionBuilderContext libCtx, ExpressionDef expressionDef) => + NewExpressionBuilderContext(libCtx, new Dictionary()) + .ProcessExpressionDef(expressionDef); internal static PropertyInfo? GetProperty(Type type, string name, TypeResolver typeResolver) { diff --git a/Cql/Cql.Compiler/ExpressionBuilderContext.DebuggerView.cs b/Cql/Cql.Compiler/ExpressionBuilderContext.DebuggerView.cs index fbb7a45ba..9bc04563d 100644 --- a/Cql/Cql.Compiler/ExpressionBuilderContext.DebuggerView.cs +++ b/Cql/Cql.Compiler/ExpressionBuilderContext.DebuggerView.cs @@ -52,7 +52,7 @@ private IPopToken PushElement(Elm.Element element) private readonly record struct ExpressionBuilderNode : IBuilderContext { - public ILibraryExpressionBuilderContext LibraryExpressionBuilder { get; init; } + public LibraryExpressionBuilderContext LibraryExpressionBuilder { get; init; } public IReadOnlyList ElementStackList { get; init; } public int ElementStackPosition { get; init; } diff --git a/Cql/Cql.Compiler/ExpressionBuilderContext.LibraryDefs.cs b/Cql/Cql.Compiler/ExpressionBuilderContext.LibraryDefs.cs index ae5f3dba4..84ee61a58 100644 --- a/Cql/Cql.Compiler/ExpressionBuilderContext.LibraryDefs.cs +++ b/Cql/Cql.Compiler/ExpressionBuilderContext.LibraryDefs.cs @@ -13,7 +13,6 @@ using Hl7.Cql.Compiler.Infrastructure; using Hl7.Cql.Elm; using Hl7.Cql.Primitives; -using Hl7.Cql.Runtime; using Microsoft.Extensions.Logging; using Expression = System.Linq.Expressions.Expression; @@ -131,6 +130,9 @@ public void ProcessExpressionDef( ExpressionDef expressionDef) => this.CatchRethrowExpressionBuildingException(_ => { + if (_operands is null) + throw new InvalidOperationException("Operands dictionary is null."); + using (PushElement(expressionDef)) { if (string.IsNullOrWhiteSpace(expressionDef.name)) @@ -260,7 +262,7 @@ public void ProcessParameterDef( defaultValue = TranslateArg(parameter.@default).NewTypeAsExpression(); else defaultValue = NullExpression.Object; - var resolveParam = _contextBinder.ResolveParameter(_libraryContext.LibraryKey, parameter.name, defaultValue); + var resolveParam = _cqlContextBinder.ResolveParameter(_libraryContext.LibraryKey, parameter.name, defaultValue); var parameterType = TypeFor(parameter.parameterTypeSpecifier)!; var cast = _cqlOperatorsBinder.CastToType(resolveParam, parameterType); diff --git a/Cql/Cql.Compiler/ExpressionBuilderContext.TypeFor.cs b/Cql/Cql.Compiler/ExpressionBuilderContext.TypeFor.cs index 0a27c6065..ca2fbd85b 100644 --- a/Cql/Cql.Compiler/ExpressionBuilderContext.TypeFor.cs +++ b/Cql/Cql.Compiler/ExpressionBuilderContext.TypeFor.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; +using System.Linq.Expressions; using Hl7.Cql.Abstractions.Infrastructure; using Hl7.Cql.Elm; using Hl7.Cql.Primitives; @@ -114,7 +114,8 @@ partial class ExpressionBuilderContext case OperandRef operandRef when !string.IsNullOrWhiteSpace(operandRef.name): { - _operands.TryGetValue(operandRef.name, out var operand); + ParameterExpression? operand = null; + _operands?.TryGetValue(operandRef.name, out operand); if (operand != null) return operand.Type; break; @@ -250,46 +251,6 @@ private Type TupleTypeFor((string name, TypeSpecifier elementType)[] elements, F return type; }); - return TupleTypeFor(elementInfo); - } - - private Type TupleTypeFor(IReadOnlyDictionary elementInfo) - { - var normalizedProperties = elementInfo - .SelectToArray(kvp => - { - var propName = NormalizeIdentifier(kvp.Key); - var propType = kvp.Value; - return (propName, propType); - }); - - var matchedTupleType = _typeManager.TupleTypes - .FirstOrDefault(tupleType => - { - var isMatch = normalizedProperties - .All(prop => - tupleType.GetProperty(prop.propName) is { PropertyType: { } tuplePropertyType } - && tuplePropertyType == prop.propType); - return isMatch; - }); - if (matchedTupleType != null) - return matchedTupleType; - - var typeName = $"Tuples.{_typeManager.TupleTypeNameFor(elementInfo)}"; - - var myTypeBuilder = _typeManager.ModuleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, typeof(TupleBaseType)); - - foreach (var kvp in elementInfo) - { - if (kvp.Key != null) - { - var name = NormalizeIdentifier(kvp.Key); - var type = kvp.Value; - TypeManager.DefineProperty(myTypeBuilder, name!, kvp.Key, type); - } - } - var typeInfo = myTypeBuilder.CreateTypeInfo(); - _typeManager.AddTupleType(typeInfo!); // TODO: PDB - This is changing external state. Should become internal instead - return typeInfo!; + return _tupleBuilderCache.CreateOrGetTupleTypeFor(elementInfo); } } \ No newline at end of file diff --git a/Cql/Cql.Compiler/ExpressionBuilderContext.cs b/Cql/Cql.Compiler/ExpressionBuilderContext.cs index 53ea6ad4a..62cd55f68 100644 --- a/Cql/Cql.Compiler/ExpressionBuilderContext.cs +++ b/Cql/Cql.Compiler/ExpressionBuilderContext.cs @@ -32,7 +32,6 @@ using Convert = System.Convert; using DateTime = Hl7.Cql.Elm.DateTime; using Expression = System.Linq.Expressions.Expression; -using TypeConverter = Hl7.Cql.Conversion.TypeConverter; using TypeSpecifier = Hl7.Cql.Elm.TypeSpecifier; using ListTypeSpecifier = Hl7.Cql.Elm.ListTypeSpecifier; using NamedTypeSpecifier = Hl7.Cql.Elm.NamedTypeSpecifier; @@ -49,56 +48,35 @@ namespace Hl7.Cql.Compiler; /// /// The scope information in this class is useful for and is supplied to . /// -partial class ExpressionBuilderContext +partial class ExpressionBuilderContext( + ILogger logger, + ExpressionBuilderSettings expressionBuilderSettings, + CqlOperatorsBinder cqlOperatorsBinder, + TupleBuilderCache tupleBuilderCache, + TypeResolver typeResolver, + CqlContextBinder cqlContextBinder, + LibraryExpressionBuilderContext libraryContext, + Dictionary? operands = null // Parameters for function definitions. Used during ProcessExpressionDef. + ) { - private readonly CqlOperatorsBinder _cqlOperatorsBinder; - private readonly CqlContextBinder _contextBinder; - private readonly TypeManager _typeManager; - private readonly ILogger _logger; - private readonly TypeConverter _typeConverter; - private readonly TypeResolver _typeResolver; - private readonly ExpressionBuilderSettings _expressionBuilderSettings; - private readonly ILibraryExpressionBuilderContext _libraryContext; - - private ImmutableStack _elementStack; + private readonly ILogger _logger = logger; + private readonly ExpressionBuilderSettings _expressionBuilderSettings = expressionBuilderSettings; + private readonly CqlOperatorsBinder _cqlOperatorsBinder = cqlOperatorsBinder; + private readonly TupleBuilderCache _tupleBuilderCache = tupleBuilderCache; + private readonly TypeResolver _typeResolver = typeResolver; + private readonly CqlContextBinder _cqlContextBinder = cqlContextBinder; + private readonly LibraryExpressionBuilderContext _libraryContext = libraryContext; + private readonly Dictionary? _operands = operands; + private readonly IReadOnlyCollection _expressionMutators = ReadOnlyCollection.Empty; // Not used yet, since it's always empty + private ImmutableStack _elementStack = ImmutableStack.Empty; /// /// Contains query aliases and let declarations, and any other symbol that is now "in scope" /// - private ImmutableStack<(object? id, string? impliedAlias, IReadOnlyDictionary? scopes)> _impliedAliasAndScopesStack; // - - /// - /// Parameters for function definitions. - /// - private readonly Dictionary _operands; // Used during ProcessExpressionDef - - private readonly IReadOnlyCollection _expressionMutators; // Not used yet, since it's always empty - - internal ExpressionBuilderContext( - ExpressionBuilder builder, - ILibraryExpressionBuilderContext libContext, - Dictionary? operands = null) - { - // External Services - _logger = builder._logger; - _cqlOperatorsBinder = builder._cqlOperatorsBinder; - _contextBinder = builder._cqlContextBinder; - _typeManager = builder._typeManager; - _expressionBuilderSettings = builder._expressionBuilderSettings; - _typeConverter = builder._typeConverter; - _typeResolver = builder._typeResolver; - _expressionMutators = ReadOnlyCollection.Empty; - - // External State - _libraryContext = libContext; - _operands = operands!; - - // Internal State - _elementStack = ImmutableStack.Empty; - _impliedAliasAndScopesStack = ImmutableStack<(object? id, string? impliedAlias, IReadOnlyDictionary? scopes)>.Empty; - } + private ImmutableStack<(object? id, string? impliedAlias, IReadOnlyDictionary? scopes)> _impliedAliasAndScopesStack = ImmutableStack<(object? id, string? impliedAlias, IReadOnlyDictionary? scopes)>.Empty; private static Expression[] NoArgs { get; } = []; + private static Type[] NoTypes { get; } = []; private Expression BindCqlOperator( @@ -841,7 +819,7 @@ protected Expression Literal(Literal lit) protected Expression OperandRef(OperandRef ore) { - if (_operands.TryGetValue(ore.name!, out var expression)) + if (_operands?.TryGetValue(ore.name!, out var expression) == true) return expression; throw this.NewExpressionBuildingException($"Operand reference to {ore.name} not found in definition operands."); } @@ -977,7 +955,7 @@ protected Expression Property(Property op) return BindCqlOperator(nameof(ICqlOperators.LateBoundProperty), scopeExpression, Expression.Constant(op.path, typeof(string)), Expression.Constant(expectedType, typeof(Type))); } var propogate = PropagateNull(scopeExpression, pathMemberInfo); - string message = $"TypeManager failed to resolve type."; + string message = $"TupleBuilderCache failed to resolve type."; var resultType = TypeFor(op) ?? throw this.NewExpressionBuildingException(message); if (resultType != propogate.Type) { @@ -1651,7 +1629,7 @@ void PushScopes( // inside every if statement here (so for where, return, etc). // ----- // The element type may have changed - // elementType = TypeManager.Resolver.GetListElementType(@return.Type, @throw: true)!; + // elementType = TypeResolver.GetListElementType(@return.Type, @throw: true)!; if (query.where is { } queryWhere) { @return = Where(queryWhere, scopeParameter, @return); @@ -1908,7 +1886,7 @@ private void QueryDumpDebugInfoToLog(Query query) Type valueTupleType = _typeResolver.GetListElementType(funcResultType, true)!; FieldInfo[] valueTupleFields = valueTupleType.GetFields(bfPublicInstance | BindingFlags.GetField); - Type cqlTupleType = TupleTypeFor(aliasAndElementTypes); + Type cqlTupleType = _tupleBuilderCache.CreateOrGetTupleTypeFor(aliasAndElementTypes); PropertyInfo[] cqlTupleProperties = cqlTupleType.GetProperties(bfPublicInstance | BindingFlags.SetProperty); Debug.Assert(valueTupleFields.Length > 0); diff --git a/Cql/Cql.Compiler/ExpressionRefCorrector.cs b/Cql/Cql.Compiler/ExpressionRefCorrector.cs index 1bf00cfc3..3a1960280 100644 --- a/Cql/Cql.Compiler/ExpressionRefCorrector.cs +++ b/Cql/Cql.Compiler/ExpressionRefCorrector.cs @@ -7,7 +7,6 @@ */ using System; -using System.Diagnostics; using System.Linq; using Hl7.Cql.Abstractions.Exceptions; using Hl7.Cql.Elm; diff --git a/Cql/Cql.Compiler/Hosting/CqlCompilerServices.cs b/Cql/Cql.Compiler/Hosting/CqlCompilerServices.cs index 0ef0ab9f9..f14fa3f4b 100644 --- a/Cql/Cql.Compiler/Hosting/CqlCompilerServices.cs +++ b/Cql/Cql.Compiler/Hosting/CqlCompilerServices.cs @@ -28,13 +28,13 @@ internal readonly struct CqlCompilerServices(IServiceProvider serviceProvider) public CqlContextBinder CqlContextBinder => ServiceProvider.GetRequiredService(); - public TypeManager TypeManager => ServiceProvider.GetRequiredService(); + public TupleBuilderCache TupleBuilderCacheScoped() => ServiceProvider.GetRequiredService(); - public LibrarySetExpressionBuilder LibrarySetExpressionBuilder => ServiceProvider.GetRequiredService(); + public LibrarySetExpressionBuilder LibrarySetExpressionBuilderScoped() => ServiceProvider.GetRequiredService(); - public LibraryExpressionBuilder LibraryExpressionBuilder => ServiceProvider.GetRequiredService(); + public LibraryExpressionBuilder LibraryExpressionBuilderScoped() => ServiceProvider.GetRequiredService(); public ExpressionBuilderSettings ExpressionBuilderSettings => ServiceProvider.GetRequiredService(); - public ExpressionBuilder ExpressionBuilder => ServiceProvider.GetRequiredService(); + public ExpressionBuilder ExpressionBuilderScoped() => ServiceProvider.GetRequiredService(); } \ No newline at end of file diff --git a/Cql/Cql.Compiler/Hosting/CqlCompilerServicesInitializer.cs b/Cql/Cql.Compiler/Hosting/CqlCompilerServicesInitializer.cs index adcc74585..923a7411b 100644 --- a/Cql/Cql.Compiler/Hosting/CqlCompilerServicesInitializer.cs +++ b/Cql/Cql.Compiler/Hosting/CqlCompilerServicesInitializer.cs @@ -10,10 +10,10 @@ using Hl7.Cql.Abstractions; using Hl7.Cql.Conversion; using Hl7.Cql.Fhir; +using Hl7.Cql.Runtime.Hosting; using Hl7.Fhir.Introspection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; namespace Hl7.Cql.Compiler.Hosting; @@ -29,8 +29,8 @@ internal static IServiceCollection AddCqlCompilerServices(this IServiceCollectio const int cacheSize = 0; // TODO: Must move to configuration services.TryAddSingleton(sp => { - var modelInspector = sp.GetRequiredService(); - var logger = sp.GetRequiredService>(); + var modelInspector = sp.GetCqlCompilerServices().ModelInspector; + var logger = sp.GetLogger(); var converter = FhirTypeConverter .Create(modelInspector, cacheSize) .UseLogger(logger); @@ -44,15 +44,15 @@ internal static IServiceCollection AddCqlCompilerServices(this IServiceCollectio services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddScoped(); - services.TryAddSingleton(); + services.TryAddScoped(); - services.TryAddSingleton(); + services.TryAddScoped(); services.TryAddSingleton(_ => ExpressionBuilderSettings.Default); // TODO: Must move to configuration - services.TryAddSingleton(); + services.TryAddScoped(); return services; } diff --git a/Cql/Cql.Compiler/ILibraryExpressionBuilderContext.cs b/Cql/Cql.Compiler/ILibraryExpressionBuilderContext.cs deleted file mode 100644 index e4328338b..000000000 --- a/Cql/Cql.Compiler/ILibraryExpressionBuilderContext.cs +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2024, 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 - */ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using Hl7.Cql.Elm; -using Hl7.Cql.Primitives; -using Hl7.Cql.Runtime; - -namespace Hl7.Cql.Compiler; - -/// -/// The interface for the library expression builder context. -/// -internal interface ILibraryExpressionBuilderContext : IBuilderContext -{ - /// - /// Gets the library associated with the expression builder context. - /// - Library Library { get; } - - /// - /// Gets the dictionary of library definitions. - /// Before the library is built, this dictionary contains the definitions from all the included libraries. - /// After processing of the library, this dictionary also contains the definitions from the library itself. - /// If the library was processed within the context of a library set, - /// then this dictionary will be merged with the library set's dictionary. - /// - DefinitionDictionary LibraryDefinitions { get; } - - /// - /// Gets the key of the library, which is the name and version of the library. - /// - /// - string LibraryKey { get; } - - /// - /// Tries to get the codes by code system name. - /// - /// The name of the code system. - /// The list of CqlCode objects. - /// True if the codes are found, false otherwise. - bool TryGetCodesByCodeSystemName(string codeSystemName, [NotNullWhen(true)] out List? codes); - - /// - /// Tries to get the code system name by code system reference. - /// - /// The code system reference. - /// The URL of the code system. - /// True if the code system name is found, false otherwise. - bool TryGetCodeSystemName(CodeSystemRef codeSystemRef, [NotNullWhen(true)] out string? url); - - /// - /// Tries to get the CqlCode object by code reference. - /// - /// The code reference. - /// The CqlCode object. - /// True if the CqlCode object is found, false otherwise. - bool TryGetCode(CodeRef codeRef, [NotNullWhen(true)] out CqlCode? systemCode); - - /// - /// Adds a code definition and its corresponding CqlCode object. - /// - /// The code definition. - /// The CqlCode object. - void AddCode(CodeDef codeDef, CqlCode cqlCode); - - /// - /// Adds an alias for the library name and version. - /// - /// The alias. - /// The library key. - void AddAliasForNameAndVersion(string alias, string libraryKey); - - /// - /// Gets the name and version of the library from the alias. - /// References to definitions from included libraries are resolved using the alias. - /// - /// The alias. - /// Indicates whether to throw an error if the alias is not found. - /// The name and version of the library. - string? GetNameAndVersionFromAlias(string? alias, bool throwError = true); -} diff --git a/Cql/Cql.Compiler/ILibrarySetExpressionBuilderContext.cs b/Cql/Cql.Compiler/ILibrarySetExpressionBuilderContext.cs deleted file mode 100644 index cf43426dc..000000000 --- a/Cql/Cql.Compiler/ILibrarySetExpressionBuilderContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) 2024, 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 - */ -using System.Linq.Expressions; -using Hl7.Cql.Runtime; - -namespace Hl7.Cql.Compiler; - -internal interface ILibrarySetExpressionBuilderContext : IBuilderContext -{ - DefinitionDictionary LibrarySetDefinitions { get; } - LibrarySet LibrarySet { get; } -} \ No newline at end of file diff --git a/Cql/Cql.Compiler/LibraryExpressionBuilder.cs b/Cql/Cql.Compiler/LibraryExpressionBuilder.cs index 4c1619c39..6717ba6bf 100644 --- a/Cql/Cql.Compiler/LibraryExpressionBuilder.cs +++ b/Cql/Cql.Compiler/LibraryExpressionBuilder.cs @@ -6,6 +6,7 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE */ +using System.Collections.Generic; using System.Linq.Expressions; using Hl7.Cql.Elm; using Hl7.Cql.Runtime; @@ -16,25 +17,32 @@ namespace Hl7.Cql.Compiler; /// /// Encapsulates the ExpressionBuilder and state dictionaries for building definitions. /// -internal class LibraryExpressionBuilder +internal class LibraryExpressionBuilder( + ILogger logger, + ExpressionBuilder expressionBuilder) { - internal readonly ILogger _logger; - internal readonly ExpressionBuilder _expressionBuilder; - - public LibraryExpressionBuilder( - ILogger logger, - ExpressionBuilder expressionBuilder) - { - _logger = logger; - _expressionBuilder = expressionBuilder; - } + private readonly ILogger _logger = logger; + private readonly ExpressionBuilder _expressionBuilder = expressionBuilder; public DefinitionDictionary ProcessLibrary( Library library, DefinitionDictionary? libraryDefinitions = null, - ILibrarySetExpressionBuilderContext? libsCtx = null) + LibrarySetExpressionBuilderContext? libsCtx = null) => + NewLibraryExpressionBuilderContext(library, libraryDefinitions, libsCtx) + .ProcessLibrary(); + + public LibraryExpressionBuilderContext NewLibraryExpressionBuilderContext( + Library library, + DefinitionDictionary? libraryDefinitions = null, + LibrarySetExpressionBuilderContext? libsCtx = null) => + new(_logger, _expressionBuilder, library, libraryDefinitions ?? new(), libsCtx); + + public ExpressionBuilderContext NewExpressionBuilderContext( + Library library, + DefinitionDictionary? libraryDefinitions = null, + Dictionary? operands = null) { - LibraryExpressionBuilderContext ctx = new(this, library, libraryDefinitions ?? new(), libsCtx); - return ctx.ProcessLibrary(); + var libraryExpressionBuilderContext = NewLibraryExpressionBuilderContext(library, libraryDefinitions); + return _expressionBuilder.NewExpressionBuilderContext(libraryExpressionBuilderContext, operands); } } \ No newline at end of file diff --git a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.DebuggerView.cs b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.DebuggerView.cs index 10d28407e..a82b0433b 100644 --- a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.DebuggerView.cs +++ b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.DebuggerView.cs @@ -10,7 +10,7 @@ namespace Hl7.Cql.Compiler; [DebuggerDisplay("{DebuggerView}")] -partial class LibraryExpressionBuilderContext : ILibraryExpressionBuilderContext +partial class LibraryExpressionBuilderContext : IBuilderContext { IBuilderContext? IBuilderContext.OuterBuilderContext => LibrarySetContext; diff --git a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.LibraryDefs.cs b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.LibraryDefs.cs index 1027f66d0..27bf57e33 100644 --- a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.LibraryDefs.cs +++ b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.LibraryDefs.cs @@ -20,8 +20,14 @@ partial class LibraryExpressionBuilderContext { #region Definitions - /// - public DefinitionDictionary LibraryDefinitions { get; } + /// + /// Gets the dictionary of library definitions. + /// Before the library is built, this dictionary contains the definitions from all the included libraries. + /// After processing of the library, this dictionary also contains the definitions from the library itself. + /// If the library was processed within the context of a library set, + /// then this dictionary will be merged with the library set's dictionary. + /// + public DefinitionDictionary LibraryDefinitions => libraryDefinitions; private void AddLibraryDefinitionsFromIncludes() { @@ -52,11 +58,23 @@ void AddDefinitions(Library library) #region Library Identifiers by Alias - private readonly Dictionary _libraryIdentifiersByAlias; + private readonly Dictionary _libraryIdentifiersByAlias = new(); + /// + /// Adds an alias for the library name and version. + /// + /// The alias. + /// The library key. public void AddAliasForNameAndVersion(string alias, string libraryKey) => _libraryIdentifiersByAlias.Add(alias, libraryKey); + /// + /// Gets the name and version of the library from the alias. + /// References to definitions from included libraries are resolved using the alias. + /// + /// The alias. + /// Indicates whether to throw an error if the alias is not found. + /// The name and version of the library. public string? GetNameAndVersionFromAlias(string? alias, bool throwError = true) { if (alias == null) @@ -74,9 +92,14 @@ public bool HasAliasForNameAndVersion(string libraryKey) => #region Codes By CodeSystemName - private readonly Dictionary> _codesByCodeSystemName; + private readonly Dictionary> _codesByCodeSystemName = new(); - /// + /// + /// Tries to get the codes by code system name. + /// + /// The name of the code system. + /// The list of CqlCode objects. + /// True if the codes are found, false otherwise. public bool TryGetCodesByCodeSystemName(string codeSystemName, [NotNullWhen(true)] out List? codes) => _codesByCodeSystemName.TryGetValue(codeSystemName, out codes); @@ -89,17 +112,29 @@ private List GetOrCreateCodesByCodeSystemName(string codeSystemName) _codesByCodeSystemName.Add(codeSystemName!, codings); return codings; } - public ILibrarySetExpressionBuilderContext? LibrarySetContext { get; } + + public LibrarySetExpressionBuilderContext? LibrarySetContext => libsCtx; #endregion #region Codes By Name (cross library???) - private readonly Dictionary _codesByName; + private readonly Dictionary _codesByName = new(); + /// + /// Tries to get the CqlCode object by code reference. + /// + /// The code reference. + /// The CqlCode object. + /// True if the CqlCode object is found, false otherwise. public bool TryGetCode(CodeRef codeRef, [NotNullWhen(true)] out CqlCode? systemCode) => _codesByName.TryGetValue(codeRef.name, out systemCode); + /// + /// Adds a code definition and its corresponding CqlCode object. + /// + /// The code definition. + /// The CqlCode object. public void AddCode(CodeDef codeDef, CqlCode cqlCode) { _codesByName.Add(codeDef.name, cqlCode); @@ -113,7 +148,7 @@ public void AddCode(CodeDef codeDef, CqlCode cqlCode) #region Url By CodeSystemRef (cross library) - private readonly ByLibraryNameAndNameDictionary _codeSystemIdsByCodeSystemRefs; + private readonly ByLibraryNameAndNameDictionary _codeSystemIdsByCodeSystemRefs = new(); private void AddCodeSystemRefsFromIncludes() { @@ -147,7 +182,12 @@ private void AddCodeSystemRefs(Library library) } } - /// + /// + /// Tries to get the code system name by code system reference. + /// + /// The code system reference. + /// The URL of the code system. + /// True if the code system name is found, false otherwise. public bool TryGetCodeSystemName(CodeSystemRef codeSystemRef, [NotNullWhen(true)] out string? url) { var libraryName = GetNameAndVersionFromAlias(codeSystemRef.libraryName); diff --git a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.cs b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.cs index 5663c00eb..b4d13d8d9 100644 --- a/Cql/Cql.Compiler/LibraryExpressionBuilderContext.cs +++ b/Cql/Cql.Compiler/LibraryExpressionBuilderContext.cs @@ -13,40 +13,28 @@ namespace Hl7.Cql.Compiler; -internal partial class LibraryExpressionBuilderContext +internal partial class LibraryExpressionBuilderContext( + ILogger logger, + ExpressionBuilder expressionBuilder, + Library library, + DefinitionDictionary libraryDefinitions, + LibrarySetExpressionBuilderContext? libsCtx = null) { + private readonly ILogger _logger = logger; + private readonly ExpressionBuilder _expressionBuilder = expressionBuilder; private static readonly AmbiguousOverloadCorrector AmbiguousOverloadCorrector = new AmbiguousOverloadCorrector(); - private readonly ILogger _logger; - private readonly ExpressionBuilder _expressionBuilder; - /// - public Library Library { get; } + /// + /// Gets the library associated with the expression builder context. + /// + public Library Library => library; - /// + /// + /// Gets the key of the library, which is the name and version of the library. + /// + /// public string LibraryKey => Library.NameAndVersion()!; - public LibraryExpressionBuilderContext( - LibraryExpressionBuilder builder, - Library library, - DefinitionDictionary libraryDefinitions, - ILibrarySetExpressionBuilderContext? libsCtx = null) - { - // External Services - _logger = builder._logger; - _expressionBuilder = builder._expressionBuilder; - - // External State - LibraryDefinitions = libraryDefinitions; - Library = library; - LibrarySetContext = libsCtx; - - // Internal State - _libraryIdentifiersByAlias = new(); - _codesByName = new(); - _codesByCodeSystemName = new(); - _codeSystemIdsByCodeSystemRefs = new ByLibraryNameAndNameDictionary(); - } - public DefinitionDictionary ProcessLibrary() => this.CatchRethrowExpressionBuildingException(_ => { diff --git a/Cql/Cql.Compiler/LibrarySetExpressionBuilder.cs b/Cql/Cql.Compiler/LibrarySetExpressionBuilder.cs index 52c61c263..125914f5a 100644 --- a/Cql/Cql.Compiler/LibrarySetExpressionBuilder.cs +++ b/Cql/Cql.Compiler/LibrarySetExpressionBuilder.cs @@ -11,21 +11,19 @@ namespace Hl7.Cql.Compiler; -internal class LibrarySetExpressionBuilder +internal class LibrarySetExpressionBuilder( + LibraryExpressionBuilder libraryExpressionBuilder) { - internal readonly LibraryExpressionBuilder _libraryExpressionBuilder; - - public LibrarySetExpressionBuilder( - LibraryExpressionBuilder libraryExpressionBuilder) - { - _libraryExpressionBuilder = libraryExpressionBuilder; - } + private readonly LibraryExpressionBuilder _libraryExpressionBuilder = libraryExpressionBuilder; public DefinitionDictionary ProcessLibrarySet( LibrarySet librarySet, - DefinitionDictionary? librarySetDefinitions = null) - { - LibrarySetExpressionBuilderContext context = new(this, librarySetDefinitions ?? new(), librarySet); - return context.ProcessLibrarySet(); - } + DefinitionDictionary? librarySetDefinitions = null) => + NewLibrarySetExpressionBuilderContext(librarySet, librarySetDefinitions) + .ProcessLibrarySet(); + + private LibrarySetExpressionBuilderContext NewLibrarySetExpressionBuilderContext( + LibrarySet librarySet, + DefinitionDictionary? librarySetDefinitions = null) => + new(_libraryExpressionBuilder, librarySet, librarySetDefinitions ?? new()); } \ No newline at end of file diff --git a/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.DebuggerView.cs b/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.DebuggerView.cs index 77fc0b7af..01d1aee58 100644 --- a/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.DebuggerView.cs +++ b/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.DebuggerView.cs @@ -10,7 +10,7 @@ namespace Hl7.Cql.Compiler; [DebuggerDisplay("{DebuggerView}")] -partial class LibrarySetExpressionBuilderContext : ILibrarySetExpressionBuilderContext +partial class LibrarySetExpressionBuilderContext : IBuilderContext { public IBuilderContext? OuterBuilderContext => null; public BuilderContextDebuggerInfo? DebuggerInfo { get; } diff --git a/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.cs b/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.cs index f1f8e1a0b..7b89d47bb 100644 --- a/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.cs +++ b/Cql/Cql.Compiler/LibrarySetExpressionBuilderContext.cs @@ -6,7 +6,6 @@ * available at https://raw.githubusercontent.com/FirelyTeam/firely-cql-sdk/main/LICENSE */ -using System.Linq; using System.Linq.Expressions; using Hl7.Cql.Runtime; @@ -17,17 +16,24 @@ internal partial class LibrarySetExpressionBuilderContext private readonly LibraryExpressionBuilder _libraryExpressionBuilder; public LibrarySetExpressionBuilderContext( - LibrarySetExpressionBuilder builder, - DefinitionDictionary librarySetDefinitions, - LibrarySet librarySet) + LibraryExpressionBuilder libraryExpressionBuilder, + LibrarySet librarySet, + DefinitionDictionary librarySetDefinitions) { - _libraryExpressionBuilder = builder._libraryExpressionBuilder; + _libraryExpressionBuilder = libraryExpressionBuilder; LibrarySetDefinitions = librarySetDefinitions; LibrarySet = librarySet; DebuggerInfo = new BuilderContextDebuggerInfo("LibrarySet", Name: LibrarySet!.Name!); } + /// + /// Gets the merged definitions of all the libraries processed in the . + /// public DefinitionDictionary LibrarySetDefinitions { get; } + + /// + /// Gets the library set being processed. + /// public LibrarySet LibrarySet { get; } public DefinitionDictionary ProcessLibrarySet() => diff --git a/Cql/Cql.Compiler/TupleBuilderCache.cs b/Cql/Cql.Compiler/TupleBuilderCache.cs new file mode 100644 index 000000000..1775e957d --- /dev/null +++ b/Cql/Cql.Compiler/TupleBuilderCache.cs @@ -0,0 +1,146 @@ +/* + * 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 + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using Hl7.Cql.Abstractions; +using Hl7.Cql.Abstractions.Infrastructure; +using Hl7.Cql.Primitives; +using Microsoft.Extensions.Logging; + +namespace Hl7.Cql.Compiler; + +/// +/// The TupleBuilderCache creates and caches dynamic tuple types. +/// +internal class TupleBuilderCache : IDisposable +{ + private readonly ILogger _logger; + private readonly List _tupleTypeList; + private readonly ModuleBuilder _moduleBuilder; + private readonly string _tupleBuilderCacheName; + + /// + public TupleBuilderCache( + ILogger logger) + { + _logger = logger; + _tupleBuilderCacheName = $"Tuples{Guid.NewGuid():N}"; + _logger.LogInformation("Creating tuple type cache {name}", _tupleBuilderCacheName); + + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(_tupleBuilderCacheName), AssemblyBuilderAccess.RunAndCollect); + _tupleTypeList = []; + _moduleBuilder = assemblyBuilder.DefineDynamicModule(_tupleBuilderCacheName); + } + + public void Dispose() + { + _logger.LogInformation("Disposing tuple type cache {name}", _tupleBuilderCacheName); + } + + /// + /// Creates or gets from the cache, a tuple type for the specified property names and types. + /// + /// A readonly collection of property names with their corresponding types. + /// Gets the type that matches the properties. + public Type CreateOrGetTupleTypeFor(IReadOnlyDictionary propertyNamesAndTypes) + { + var normalizedProperties = propertyNamesAndTypes + .SelectToArray(kvp => + { + var propName = ExpressionBuilderContext.NormalizeIdentifier(kvp.Key); + var propType = kvp.Value; + return (propName, propType); + }); + + var matchedTupleType = _tupleTypeList + .FirstOrDefault(tupleType => + { + var isMatch = normalizedProperties + .All(prop => + tupleType.GetProperty(prop.propName) is { PropertyType: { } tuplePropertyType } + && tuplePropertyType == prop.propType); + return isMatch; + }); + if (matchedTupleType != null) + return matchedTupleType; + + var typeName = $"Tuples.{TupleTypeNameFor(propertyNamesAndTypes)}"; + + var myTypeBuilder = _moduleBuilder.DefineType(typeName, TypeAttributes.Public | TypeAttributes.Class, typeof(TupleBaseType)); + + foreach (var kvp in propertyNamesAndTypes) + { + var name = ExpressionBuilderContext.NormalizeIdentifier(kvp.Key); + var type = kvp.Value; + DefineProperty(myTypeBuilder, name!, kvp.Key, type); + } + var typeInfo = myTypeBuilder.CreateTypeInfo(); + AddTupleType(typeInfo!); + return typeInfo!; + } + + private void AddTupleType(Type type) + { + if (_tupleTypeList.Contains(type)) + throw new ArgumentException($"Type {type.Name} already exists", nameof(type)); + _tupleTypeList.Add(type); + } + + private static void DefineProperty(TypeBuilder myTypeBuilder, string normalizedName, string cqlName, Type type) + { + var fieldBuilder = myTypeBuilder.DefineField($"_{normalizedName}", type, FieldAttributes.Private); + var propertyBuilder = myTypeBuilder.DefineProperty(normalizedName, PropertyAttributes.None, type, null); + var customAttributeBuilder = new CustomAttributeBuilder(typeof(CqlDeclarationAttribute).GetConstructor([ + typeof(string) + ])!, [cqlName]); + propertyBuilder.SetCustomAttribute(customAttributeBuilder); + MethodAttributes attributes = MethodAttributes.Public + | MethodAttributes.SpecialName + | MethodAttributes.HideBySig; + { + var get = myTypeBuilder.DefineMethod($"get_{normalizedName}", attributes, type, Type.EmptyTypes); + ILGenerator getIL = get.GetILGenerator(); + getIL.Emit(OpCodes.Ldarg_0); + getIL.Emit(OpCodes.Ldfld, fieldBuilder); + getIL.Emit(OpCodes.Ret); + propertyBuilder.SetGetMethod(get); + } + + { + var set = myTypeBuilder.DefineMethod($"set_{normalizedName}", attributes, null, [type]); + ILGenerator setIL = set.GetILGenerator(); + setIL.Emit(OpCodes.Ldarg_0); + setIL.Emit(OpCodes.Ldarg_1); + setIL.Emit(OpCodes.Stfld, fieldBuilder); + setIL.Emit(OpCodes.Ret); + propertyBuilder.SetSetMethod(set); + } + } + + /// + /// Gets a unique tuple name given the elements (members) of the type. + /// This method must return the same value for equal values of . + /// Equality is determined by comparing using default string equality + /// and using default equality. + /// + /// Key value pairs where key is the name of the element and the value is its type. + /// The unique tuple type name. + private static string TupleTypeNameFor(IReadOnlyDictionary elementInfo) + { + var nameTypes = elementInfo + .OrderBy(k => k.Key) + .Select(kvp => $"{kvp.Key}:{kvp.Value.ToCSharpString()}"); + var hashInput = string.Join("+", nameTypes); + var tupleId = Hasher.Instance.Hash(hashInput); + return $"Tuple_{tupleId}"; + } +} \ No newline at end of file diff --git a/Cql/Cql.Compiler/TypeManager.cs b/Cql/Cql.Compiler/TypeManager.cs deleted file mode 100644 index c0e5ce2f6..000000000 --- a/Cql/Cql.Compiler/TypeManager.cs +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 - */ - -using Hl7.Cql.Abstractions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using Hl7.Cql.Abstractions.Infrastructure; - - -namespace Hl7.Cql.Compiler -{ - /// - /// The TypeManager class maps ELM types to .NET types usually through its , and - /// also creates new types (e.g. for Tuples) as needed. - /// - internal class TypeManager - { - /// - /// Gets the assembly name for any generated types created by this type manager. - /// - private string AssemblyName { get; } - - /// - /// Gets the this TypeManager uses. - /// - protected internal TypeResolver Resolver { get; } - - /// - /// Gets the tuple types created by this . - /// - public IReadOnlyCollection TupleTypes => TupleTypeList; - - private readonly List TupleTypeList; - - public ModuleBuilder ModuleBuilder { get; } - - private Hasher Hasher { get; } - - /// - /// Creates an instance with the specified resolver, assembly name, and tuple type namespace. - /// - /// The that this instance uses. - /// The name of the assembly in which generated tuple types will be created. If not specified, the value will be "Tuples". - /// If is null. - public TypeManager( - TypeResolver resolver, - string assemblyName = "Tuples") - { - if (string.IsNullOrWhiteSpace(assemblyName)) - assemblyName = "Tuples"; - - AssemblyName = assemblyName; - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(AssemblyName), AssemblyBuilderAccess.Run); - TupleTypeList = []; - Hasher = Hasher.Instance; - ModuleBuilder = assemblyBuilder.DefineDynamicModule(AssemblyName); - Resolver = resolver ?? throw new ArgumentNullException(nameof(resolver)); - } - - /// - /// Gets a unique tuple name given the elements (members) of the type. - /// This method must return the same value for equal values of . - /// Equality is determined by comparing using default string equality - /// and using default equality. - /// - /// Key value pairs where key is the name of the element and the value is its type. - /// The unique tuple type name. - public string TupleTypeNameFor(IReadOnlyDictionary elementInfo) - { - var hashInput = string.Join("+", elementInfo - .OrderBy(k => k.Key) - .Select(kvp => $"{kvp.Key}:{kvp.Value.ToCSharpString()}")); - var tupleId = Hasher.Hash(hashInput); - return $"Tuple_{tupleId}"; - } - - public void AddTupleType(Type type) - { - if (TupleTypeList.Contains(type)) - throw new ArgumentException($"Type {type.Name} already exists", nameof(type)); - TupleTypeList.Add(type); - } - - public static void DefineProperty(TypeBuilder myTypeBuilder, string normalizedName, string cqlName, Type type) - { - var fieldBuilder = myTypeBuilder.DefineField($"_{normalizedName}", type, FieldAttributes.Private); - var propertyBuilder = myTypeBuilder.DefineProperty(normalizedName, PropertyAttributes.None, type, null); - var customAttributeBuilder = new CustomAttributeBuilder(typeof(CqlDeclarationAttribute).GetConstructor([ - typeof(string) - ])!, [cqlName]); - propertyBuilder.SetCustomAttribute(customAttributeBuilder); - MethodAttributes attributes = MethodAttributes.Public - | MethodAttributes.SpecialName - | MethodAttributes.HideBySig; - { - var get = myTypeBuilder.DefineMethod($"get_{normalizedName}", attributes, type, Type.EmptyTypes); - ILGenerator getIL = get.GetILGenerator(); - getIL.Emit(OpCodes.Ldarg_0); - getIL.Emit(OpCodes.Ldfld, fieldBuilder); - getIL.Emit(OpCodes.Ret); - propertyBuilder.SetGetMethod(get); - } - - { - var set = myTypeBuilder.DefineMethod($"set_{normalizedName}", attributes, null, [type]); - ILGenerator setIL = set.GetILGenerator(); - setIL.Emit(OpCodes.Ldarg_0); - setIL.Emit(OpCodes.Ldarg_1); - setIL.Emit(OpCodes.Stfld, fieldBuilder); - setIL.Emit(OpCodes.Ret); - propertyBuilder.SetSetMethod(set); - } - } - } -} diff --git a/Cql/Cql.Conversion/TypeConverter.cs b/Cql/Cql.Conversion/TypeConverter.cs index 59a9c13e1..dc538db22 100644 --- a/Cql/Cql.Conversion/TypeConverter.cs +++ b/Cql/Cql.Conversion/TypeConverter.cs @@ -37,8 +37,7 @@ public interface ITypeConverterEntry /// public class TypeConverter : IDisposable { - private readonly Dictionary>> _converters - = new(); + private readonly Dictionary>> _converters = new(); private readonly List _customConverters = []; private readonly HashSet _conversionsAvailable = new(); private readonly HashSet _conversionsUsed = new(); diff --git a/Cql/Cql.Packaging/Hosting/CqlPackagerServices.cs b/Cql/Cql.Packaging/Hosting/CqlPackagingServices.cs similarity index 84% rename from Cql/Cql.Packaging/Hosting/CqlPackagerServices.cs rename to Cql/Cql.Packaging/Hosting/CqlPackagingServices.cs index 98cc0eb9e..1eb6e370e 100644 --- a/Cql/Cql.Packaging/Hosting/CqlPackagerServices.cs +++ b/Cql/Cql.Packaging/Hosting/CqlPackagingServices.cs @@ -12,7 +12,7 @@ namespace Hl7.Cql.Packaging.Hosting; -internal readonly struct CqlPackagerServices(IServiceProvider serviceProvider) +internal readonly struct CqlPackagingServices(IServiceProvider serviceProvider) { public CqlCodeGenerationServices GetCqlCodeGenerationServices() => new(serviceProvider); @@ -22,5 +22,5 @@ internal readonly struct CqlPackagerServices(IServiceProvider serviceProvider) public ResourcePackager ResourcePackager => serviceProvider.GetRequiredService(); - public CqlToResourcePackagingPipeline CqlToResourcePackagingPipeline => serviceProvider.GetRequiredService(); + public CqlToResourcePackagingPipeline CqlToResourcePackagingPipelineScoped() => serviceProvider.GetRequiredService(); } \ No newline at end of file diff --git a/Cql/Cql.Packaging/Hosting/CqlPackagerServicesInitializer.cs b/Cql/Cql.Packaging/Hosting/CqlPackagingServicesInitializer.cs similarity index 74% rename from Cql/Cql.Packaging/Hosting/CqlPackagerServicesInitializer.cs rename to Cql/Cql.Packaging/Hosting/CqlPackagingServicesInitializer.cs index d56746b7c..b45ceb130 100644 --- a/Cql/Cql.Packaging/Hosting/CqlPackagerServicesInitializer.cs +++ b/Cql/Cql.Packaging/Hosting/CqlPackagingServicesInitializer.cs @@ -15,12 +15,12 @@ namespace Hl7.Cql.Packaging.Hosting; -internal static class CqlPackagerServicesInitializer +internal static class CqlPackagingServicesInitializer { - internal static CqlPackagerServices GetCqlPackagerServices(this IServiceProvider serviceProvider) => - new CqlPackagerServices(serviceProvider); + internal static CqlPackagingServices GetCqlPackagingServices(this IServiceProvider serviceProvider) => + new CqlPackagingServices(serviceProvider); - public static IServiceCollection AddCqlPackagerServices(this IServiceCollection services) + public static IServiceCollection AddCqlPackagingServices(this IServiceCollection services) { services.AddCqlCodeGenerationServices(); @@ -38,7 +38,7 @@ public static IServiceCollection AddCqlPackagerServices(this IServiceCollection services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddScoped(); return services; } diff --git a/Cql/CqlToElmTests/Base.cs b/Cql/CqlToElmTests/Base.cs index cd6cf6014..57e6c3d45 100644 --- a/Cql/CqlToElmTests/Base.cs +++ b/Cql/CqlToElmTests/Base.cs @@ -29,7 +29,7 @@ public class Base internal static CqlToElmConverter DefaultConverter => CqlToElmServices.CqlToElmConverter; - internal static LibraryExpressionBuilder LibraryExpressionBuilder => CqlCodeGenerationServices.GetCqlCompilerServices().LibraryExpressionBuilder; + internal static LibraryExpressionBuilder LibraryExpressionBuilder => CqlCodeGenerationServices.GetCqlCompilerServices().LibraryExpressionBuilderScoped(); internal static CSharpLibrarySetToStreamsWriter SourceCodeWriter => CqlCodeGenerationServices.CSharpLibrarySetToStreamsWriter; diff --git a/Cql/CqlToElmTests/LibraryExpressionBuilderExtensions.cs b/Cql/CqlToElmTests/LibraryExpressionBuilderExtensions.cs index 198120009..7f1df75b8 100644 --- a/Cql/CqlToElmTests/LibraryExpressionBuilderExtensions.cs +++ b/Cql/CqlToElmTests/LibraryExpressionBuilderExtensions.cs @@ -28,11 +28,7 @@ internal static LambdaExpression Lambda( Elm.Expression expression) { DefinitionDictionary lambdas = new DefinitionDictionary(); - - var library = Library; - var libctx = new LibraryExpressionBuilderContext(libraryExpressionBuilder, library, lambdas); - Dictionary? parameterExpressions = null; - var ctx = new ExpressionBuilderContext(libraryExpressionBuilder._expressionBuilder, libctx, parameterExpressions); + var ctx = libraryExpressionBuilder.NewExpressionBuilderContext(Library, lambdas); Expression translated = ctx.TranslateArg(expression); var contextParameter = CqlExpressions.ParameterExpression; LambdaExpression lambda = Expression.Lambda(translated, contextParameter); diff --git a/Cql/PackagerCLI/Hosting/PackagerCLiLoggingInitializer.cs b/Cql/PackagerCLI/Hosting/PackagerCLiLoggingInitializer.cs new file mode 100644 index 000000000..9a156aada --- /dev/null +++ b/Cql/PackagerCLI/Hosting/PackagerCLiLoggingInitializer.cs @@ -0,0 +1,83 @@ +/* + *Copyright(c) 2024, 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 + */ + +using System.Diagnostics; +using System.Globalization; +using Hl7.Cql.Abstractions.Exceptions; +using Hl7.Cql.Packager.Logging; +using Hl7.Cql.Packaging; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Serilog; +using Serilog.Events; + +namespace Hl7.Cql.Packager.Hosting; + +internal static class PackagerCLiLoggingInitializer +{ + public static ILoggingBuilder AddPackagerCLiLogging( + this ILoggingBuilder logging, + IConfiguration configuration) + { + logging.ClearProviders(); + + bool enableDebugLogging = Debugger.IsAttached; + enableDebugLogging = enableDebugLogging || configuration.GetCommandLineSwitchValue(CqlToResourcePackagingOptions.ArgNameLogDebugEnabled); + var minLogLevel = enableDebugLogging ? LogLevel.Trace : LogLevel.Information; + + bool shouldClearLog = !configuration.GetCommandLineSwitchValue(CqlToResourcePackagingOptions.ArgNameLogDontClear); + + logging.AddFilter(level => level >= minLogLevel); + + logging.AddCleanConsole(opt => + { + // opt.NoColor = true; + }); + + var logFile = Path.Combine(".", "build.log"); + + if (shouldClearLog) + File.WriteAllText(logFile, ""); // Create or clear the log file + //File.Delete(logFile); + else + File.OpenText(logFile).Close(); // Touch the file + + Log.Logger = new LoggerConfiguration() + .Enrich.FromLogContext() + .MinimumLevel.Is(MapToSeriLogLogEventLevel(minLogLevel)!.Value) + .WriteTo.File( + logFile, + outputTemplate: "____{NewLine}{Level:u4}: {Message:lj}{NewLine}{Exception}", + formatProvider: CultureInfo.InvariantCulture) + .CreateLogger(); + + return logging.AddSerilog(); + } + + private static LogEventLevel? MapToSeriLogLogEventLevel(LogLevel logLevel) => + logLevel switch + { + // @formatter: off + /* 0 */ + LogLevel.Trace => /* 0 */ LogEventLevel.Verbose, + /* 1 */ + LogLevel.Debug => /* 1 */ LogEventLevel.Debug, + /* 2 */ + LogLevel.Information => /* 2 */ LogEventLevel.Information, + /* 3 */ + LogLevel.Warning => /* 3 */ LogEventLevel.Warning, + /* 4 */ + LogLevel.Error => /* 4 */ LogEventLevel.Error, + /* 5 */ + LogLevel.Critical => /* 5 */ LogEventLevel.Fatal, + /* 6 */ + LogLevel.None => /* n/a */ null, + // @formatter: on + _ => throw new UnsupportedSwitchCaseError(logLevel, typeof(LogLevel).FullName).ToException(), + }; +} \ No newline at end of file diff --git a/Cql/PackagerCLI/Hosting/PackagerCliCommandLineSwitchInitializer.cs b/Cql/PackagerCLI/Hosting/PackagerCliCommandLineSwitchInitializer.cs new file mode 100644 index 000000000..d286c951e --- /dev/null +++ b/Cql/PackagerCLI/Hosting/PackagerCliCommandLineSwitchInitializer.cs @@ -0,0 +1,58 @@ +/* + *Copyright(c) 2024, 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 + */ + +using Hl7.Cql.CodeGeneration.NET; +using Hl7.Cql.Packaging; +using Hl7.Cql.Packaging.PostProcessors; +using Microsoft.Extensions.Configuration; + +namespace Hl7.Cql.Packager.Hosting; + +internal static class PackagerCliCommandLineSwitchInitializer +{ + private static IDictionary CommandLineSwitchMappings { get; } = BuildCommandLineSwitchMappings(); + + static IDictionary BuildCommandLineSwitchMappings() + { + const string PackageSection = CqlToResourcePackagingOptions.ConfigSection + ":"; + const string CSharpCodeWriterSection = CSharpCodeWriterOptions.ConfigSection + ":"; + const string AssemblyDataWriterSection = AssemblyDataWriterOptions.ConfigSection + ":"; + const string FhirResourceWriterSection = FhirResourceWriterOptions.ConfigSection + ":"; + + return new SortedDictionary + { + // @formatter:off + [CqlToResourcePackagingOptions.ArgNameElmDirectory] = PackageSection + nameof(CqlToResourcePackagingOptions.ElmDirectory), + [CqlToResourcePackagingOptions.ArgNameCqlDirectory] = PackageSection + nameof(CqlToResourcePackagingOptions.CqlDirectory), + [CqlToResourcePackagingOptions.ArgNameLogDebugEnabled] = PackageSection + nameof(CqlToResourcePackagingOptions.LogDebugEnabled), + [CqlToResourcePackagingOptions.ArgNameLogDontClear] = PackageSection + nameof(CqlToResourcePackagingOptions.DontLogClear), + [CqlToResourcePackagingOptions.ArgNameCanonicalRootUrl] = PackageSection + nameof(CqlToResourcePackagingOptions.CanonicalRootUrl), + + [CSharpCodeWriterOptions.ArgNameOutDirectory] = CSharpCodeWriterSection + nameof(CSharpCodeWriterOptions.OutDirectory), + [CSharpCodeWriterOptions.ArgNameTypeFormat] = CSharpCodeWriterSection + nameof(CSharpCodeWriterOptions.TypeFormat), + + [AssemblyDataWriterOptions.ArgNameOutDirectory] = AssemblyDataWriterSection + nameof(CSharpCodeWriterOptions.OutDirectory), + + [FhirResourceWriterOptions.ArgNameOutDirectory] = FhirResourceWriterSection + nameof(FhirResourceWriterOptions.OutDirectory), + [FhirResourceWriterOptions.ArgNameOverrideDate] = FhirResourceWriterSection + nameof(FhirResourceWriterOptions.OverrideDate), + // @formatter:on + }; + } + + public static T? GetCommandLineSwitchValue( + this IConfiguration configuration, + string switchMapKey) => + configuration.GetValue(CommandLineSwitchMappings[switchMapKey]); + + public static void AddPackagerCliCommandLineSwitches( + this IConfigurationBuilder config, + string[] args) + { + config.AddCommandLine(args, CommandLineSwitchMappings); + } +} \ No newline at end of file diff --git a/Cql/PackagerCLI/Hosting/PackagerCliOptionsInitializer.cs b/Cql/PackagerCLI/Hosting/PackagerCliOptionsInitializer.cs new file mode 100644 index 000000000..c52e19054 --- /dev/null +++ b/Cql/PackagerCLI/Hosting/PackagerCliOptionsInitializer.cs @@ -0,0 +1,52 @@ +/* + *Copyright(c) 2024, 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 + */ + +using Hl7.Cql.CodeGeneration.NET; +using Hl7.Cql.Packaging; +using Hl7.Cql.Packaging.PostProcessors; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Hl7.Cql.Packager.Hosting; + +internal static class PackagerCliOptionsInitializer +{ + public static IServiceCollection AddPackagerCliOptions( + this IServiceCollection services, + IConfiguration configuration) + { + if (services.Any(s => s.ServiceType == typeof(IValidateOptions))) + return services; + + services.AddSingleton, CqlToResourcePackagingOptions.Validator>(); + services.AddSingleton, CSharpCodeWriterOptions.Validator>(); + + services + .AddOptions() + .Configure(CqlToResourcePackagingOptions.BindConfig) + .ValidateOnStart(); + + services + .AddOptions() + .Configure(FhirResourceWriterOptions.BindConfig) + .ValidateOnStart(); + + services + .AddOptions() + .Configure(CSharpCodeWriterOptions.BindConfig) + .ValidateOnStart(); + + services + .AddOptions() + .Configure(AssemblyDataWriterOptions.BindConfig) + .ValidateOnStart(); + + return services; + } +} \ No newline at end of file diff --git a/Cql/PackagerCLI/Hosting/PackagerCliServices.cs b/Cql/PackagerCLI/Hosting/PackagerCliServices.cs new file mode 100644 index 000000000..43a7ebe06 --- /dev/null +++ b/Cql/PackagerCLI/Hosting/PackagerCliServices.cs @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, 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 + */ + +using Microsoft.Extensions.DependencyInjection; + +namespace Hl7.Cql.Packager.Hosting; + +internal readonly record struct PackagerCliServices(IServiceProvider serviceProvider) +{ + public IServiceProvider ServiceProvider { get; } = serviceProvider; + + public PackagerCliProgram PackagerCliProgramScoped() => serviceProvider.GetRequiredService(); + public OptionsConsoleDumper OptionsConsoleDumper => serviceProvider.GetRequiredService(); +} \ No newline at end of file diff --git a/Cql/PackagerCLI/Hosting/PackagerCliServicesInitializer.cs b/Cql/PackagerCLI/Hosting/PackagerCliServicesInitializer.cs new file mode 100644 index 000000000..207d14880 --- /dev/null +++ b/Cql/PackagerCLI/Hosting/PackagerCliServicesInitializer.cs @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, 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 + */ + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Hl7.Cql.Packaging.Hosting; + +namespace Hl7.Cql.Packager.Hosting; + +internal static class PackagerCliServicesInitializer +{ + internal static PackagerCliServices GetPackagerCliServices( + this IServiceProvider serviceProvider) => + new PackagerCliServices(serviceProvider); + + internal static IServiceCollection AddPackagerCliServices( + this IServiceCollection services) + { + services.AddCqlPackagingServices(); + + services.TryAddScoped(); + services.TryAddSingleton(); + + return services; + } +} \ No newline at end of file diff --git a/Cql/PackagerCLI/PackagerCliProgram.cs b/Cql/PackagerCLI/PackagerCliProgram.cs index 178bdcc9b..1370d4c55 100644 --- a/Cql/PackagerCLI/PackagerCliProgram.cs +++ b/Cql/PackagerCLI/PackagerCliProgram.cs @@ -7,7 +7,6 @@ */ using Hl7.Cql.Packaging; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Hl7.Cql.Packager; @@ -15,8 +14,7 @@ namespace Hl7.Cql.Packager; internal class PackagerCliProgram( ILogger logger, OptionsConsoleDumper optionsConsoleDumper, - CqlToResourcePackagingPipeline cqlToResourcePackagingPipeline, - IHostApplicationLifetime hostLifetime) + CqlToResourcePackagingPipeline cqlToResourcePackagingPipeline) { public int Run() { @@ -29,30 +27,8 @@ public int Run() catch (Exception e) { logger.LogError(e, "An error occurred while running the packager"); - Console.Error.WriteLine( - "An error occurred while running PackagerCLI. Consult the build.log file for more detail."); + Console.Error.WriteLine("An error occurred while running PackagerCLI. Consult the build.log file for more detail."); return -1; } - finally - { - hostLifetime.StopApplication(); - } } -} - - - -/*internal class ProgramCqlPackagerFactory( - ILoggerFactory loggerFactory, - IHostApplicationLifetime hostLifetime, - IOptions cqlToResourcePackagingOptions, - IOptions cSharpCodeWriterOptions, - IOptions fhirResourceWriterOptions, - IOptions assemblyDataWriterOptions) - : CqlPackagerFactory(loggerFactory, - cacheSize: 0, - cqlToResourcePackagingOptions: cqlToResourcePackagingOptions.Value, - cSharpCodeWriterOptions: cSharpCodeWriterOptions.Value, - fhirResourceWriterOptions: fhirResourceWriterOptions.Value, - assemblyDataWriterOptions: assemblyDataWriterOptions.Value, - cancellationToken: hostLifetime.ApplicationStopping);*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Cql/PackagerCLI/Program.cs b/Cql/PackagerCLI/Program.cs index d8a8a1e79..82b751f00 100644 --- a/Cql/PackagerCLI/Program.cs +++ b/Cql/PackagerCLI/Program.cs @@ -7,30 +7,20 @@ */ #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System.Diagnostics; using System.Globalization; -using Hl7.Cql.Abstractions.Exceptions; using Hl7.Cql.CodeGeneration.NET; -using Hl7.Cql.Packager.Logging; +using Hl7.Cql.Packager.Hosting; using Hl7.Cql.Packaging; -using Hl7.Cql.Packaging.Hosting; using Hl7.Cql.Packaging.PostProcessors; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Serilog; -using Serilog.Events; namespace Hl7.Cql.Packager; public class Program { - private static IDictionary SwitchMappings { get; } - - public static int Main(string[] args) { if (args.Length == 0 || @@ -41,7 +31,6 @@ public static int Main(string[] args) } var hostBuilder = CreateHostBuilder(args); - try { return Run(hostBuilder); @@ -54,16 +43,28 @@ public static int Main(string[] args) public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder() - .ConfigureAppConfiguration((context, config) => ConfigureAppConfiguration(config, args)) - .ConfigureLogging((context, logging) => ConfigureLogging(context, logging)) - .ConfigureServices((context, services) => ConfigureServices(context, services)); + .UseServiceProviderFactory(context => + new DefaultServiceProviderFactory( + new ServiceProviderOptions() + { + // Since we are using scopes, + // we need to validate that they are never used from the root provider, + // nor that they are included into singletons. + ValidateScopes = true + })) + .ConfigureAppConfiguration((context, config) => config.AddPackagerCliCommandLineSwitches(args)) + .ConfigureLogging((context, logging) => logging.AddPackagerCLiLogging(context.Configuration)) + .ConfigureServices((context, services) => services + .AddPackagerCliOptions(context.Configuration) + .AddPackagerCliServices()) + .UseConsoleLifetime() + ; private static string Usage { get; } static Program() { CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture; - SwitchMappings = BuildSwitchMappings(); Usage = $""" Packager CLI Usage: @@ -84,137 +85,8 @@ static Program() """; static string Optional(string s) => $"[{s}]"; - - static IDictionary BuildSwitchMappings() - { - const string PackageSection = CqlToResourcePackagingOptions.ConfigSection + ":"; - const string CSharpCodeWriterSection = CSharpCodeWriterOptions.ConfigSection + ":"; - const string AssemblyDataWriterSection = AssemblyDataWriterOptions.ConfigSection + ":"; - const string FhirResourceWriterSection = FhirResourceWriterOptions.ConfigSection + ":"; - - return new SortedDictionary - { - // @formatter:off - [CqlToResourcePackagingOptions.ArgNameElmDirectory] = PackageSection + nameof(CqlToResourcePackagingOptions.ElmDirectory), - [CqlToResourcePackagingOptions.ArgNameCqlDirectory] = PackageSection + nameof(CqlToResourcePackagingOptions.CqlDirectory), - [CqlToResourcePackagingOptions.ArgNameLogDebugEnabled] = PackageSection + nameof(CqlToResourcePackagingOptions.LogDebugEnabled), - [CqlToResourcePackagingOptions.ArgNameLogDontClear] = PackageSection + nameof(CqlToResourcePackagingOptions.DontLogClear), - [CqlToResourcePackagingOptions.ArgNameCanonicalRootUrl] = PackageSection + nameof(CqlToResourcePackagingOptions.CanonicalRootUrl), - - [CSharpCodeWriterOptions.ArgNameOutDirectory] = CSharpCodeWriterSection + nameof(CSharpCodeWriterOptions.OutDirectory), - [CSharpCodeWriterOptions.ArgNameTypeFormat] = CSharpCodeWriterSection + nameof(CSharpCodeWriterOptions.TypeFormat), - - [AssemblyDataWriterOptions.ArgNameOutDirectory] = AssemblyDataWriterSection + nameof(CSharpCodeWriterOptions.OutDirectory), - - [FhirResourceWriterOptions.ArgNameOutDirectory] = FhirResourceWriterSection + nameof(FhirResourceWriterOptions.OutDirectory), - [FhirResourceWriterOptions.ArgNameOverrideDate] = FhirResourceWriterSection + nameof(FhirResourceWriterOptions.OverrideDate), - // @formatter:on - }; - } - } - - - private static void ConfigureAppConfiguration(IConfigurationBuilder config, string[] args) - { - config.AddCommandLine(args, SwitchMappings); - } - - private static void ConfigureLogging(HostBuilderContext context, ILoggingBuilder logging) - { - logging.ClearProviders(); - - bool enableDebugLogging = Debugger.IsAttached; - enableDebugLogging = enableDebugLogging || context.Configuration.GetValue(SwitchMappings[CqlToResourcePackagingOptions.ArgNameLogDebugEnabled]); - var minLogLevel = enableDebugLogging ? LogLevel.Trace : LogLevel.Information; - - bool shouldClearLog = !context.Configuration.GetValue(SwitchMappings[CqlToResourcePackagingOptions.ArgNameLogDontClear]); - - logging.AddFilter(level => level >= minLogLevel); - - logging.AddCleanConsole(opt => - { - // opt.NoColor = true; - }); - - var logFile = Path.Combine(".", "build.log"); - - if (shouldClearLog) - File.WriteAllText(logFile, ""); // Create or clear the log file - //File.Delete(logFile); - else - File.OpenText(logFile).Close(); // Touch the file - - Log.Logger = new LoggerConfiguration() - .Enrich.FromLogContext() - .MinimumLevel.Is(MapToSeriLogLogEventLevel(minLogLevel)!.Value) - .WriteTo.File( - logFile, - outputTemplate: "____{NewLine}{Level:u4}: {Message:lj}{NewLine}{Exception}", - formatProvider: CultureInfo.InvariantCulture) - .CreateLogger(); - logging.AddSerilog(); - } - - private static LogEventLevel? MapToSeriLogLogEventLevel(LogLevel logLevel) => - logLevel switch - { - // @formatter: off - /* 0 */ - LogLevel.Trace => /* 0 */ LogEventLevel.Verbose, - /* 1 */ - LogLevel.Debug => /* 1 */ LogEventLevel.Debug, - /* 2 */ - LogLevel.Information => /* 2 */ LogEventLevel.Information, - /* 3 */ - LogLevel.Warning => /* 3 */ LogEventLevel.Warning, - /* 4 */ - LogLevel.Error => /* 4 */ LogEventLevel.Error, - /* 5 */ - LogLevel.Critical => /* 5 */ LogEventLevel.Fatal, - /* 6 */ - LogLevel.None => /* n/a */ null, - // @formatter: on - _ => throw new UnsupportedSwitchCaseError(logLevel, typeof(LogLevel).FullName).ToException(), - }; - - public static void ConfigureServices(HostBuilderContext context, IServiceCollection services) - { - TryAddPackagerOptions(services, context.Configuration); - services.AddCqlPackagerServices(); - services.AddSingleton(); - services.TryAddSingleton(); } - public static void TryAddPackagerOptions(IServiceCollection services, IConfiguration configuration) - { - if (services.Any(s => s.ServiceType == typeof(IValidateOptions))) - return; - - services.AddSingleton, CqlToResourcePackagingOptions.Validator>(); - services.AddSingleton, CSharpCodeWriterOptions.Validator>(); - - services - .AddOptions() - .Configure(CqlToResourcePackagingOptions.BindConfig) - .ValidateOnStart(); - - services - .AddOptions() - .Configure(FhirResourceWriterOptions.BindConfig) - .ValidateOnStart(); - - services - .AddOptions() - .Configure(CSharpCodeWriterOptions.BindConfig) - .ValidateOnStart(); - - services - .AddOptions() - .Configure(AssemblyDataWriterOptions.BindConfig) - .ValidateOnStart(); - } - - private static int Run(IHostBuilder hostBuilder) { using var host = CreateHost(hostBuilder); @@ -224,9 +96,17 @@ private static int Run(IHostBuilder hostBuilder) return -1; } - using var mainScope = host.Services.CreateScope(); - var packageService = mainScope.ServiceProvider.GetRequiredService(); - return packageService.Run(); + try + { + using IServiceScope mainScope = host.Services.CreateScope(); + var packageService = mainScope.ServiceProvider.GetPackagerCliServices().PackagerCliProgramScoped(); + return packageService.Run(); + } + finally + { + var hostLifetime = host.Services.GetRequiredService(); + hostLifetime.StopApplication(); + } } private static IHost? CreateHost(IHostBuilder hostBuilder) diff --git a/Demo/Test.Measures.Demo/MeasuresTest.cs b/Demo/Test.Measures.Demo/MeasuresTest.cs index ee5496c6a..8a215039f 100644 --- a/Demo/Test.Measures.Demo/MeasuresTest.cs +++ b/Demo/Test.Measures.Demo/MeasuresTest.cs @@ -112,15 +112,6 @@ public static AssemblyLoadContext LoadResources( var allLibs = library.GetDependenciesAndSelf(dir); var asmContext = new AssemblyLoadContext($"{lib}-{version}"); allLibs.LoadAssemblies(asmContext); - - var tupleTypes = new FileInfo(Path.Combine(dir.FullName, "TupleTypes-Binary.json")); - using var tupleFs = tupleTypes.OpenRead(); - var binaries = new[] - { - tupleFs.ParseFhir() - }; - - binaries.LoadAssemblies(asmContext); return asmContext; } @@ -160,7 +151,7 @@ public static AssemblyLoadContext LoadElm( using var disposeContext = new DisposeContext(); var cqlCodeGenerationServices = CqlServicesInitializer.CreateCqlCodeGenerationServices(disposeContext.Token); - var definitions = cqlCodeGenerationServices.GetCqlCompilerServices().LibrarySetExpressionBuilder.ProcessLibrarySet(librarySet); + var definitions = cqlCodeGenerationServices.GetCqlCompilerServices().LibrarySetExpressionBuilderScoped().ProcessLibrarySet(librarySet); var assemblyData = cqlCodeGenerationServices.AssemblyCompiler.Compile(librarySet, definitions); var asmContext = new AssemblyLoadContext($"{lib}-{version}"); foreach (var (_, asmData) in assemblyData) diff --git a/docs/packager-cli-dependency-graph.md b/docs/packager-cli-dependency-graph.md index 8b7349448..02eaefa46 100644 --- a/docs/packager-cli-dependency-graph.md +++ b/docs/packager-cli-dependency-graph.md @@ -1,252 +1,124 @@ # PackagerCLI Diagrams The diagram is split into two, the first one showing the high-level dependencies for the application, and the second the detailed dependencies for expression building. -### Application Dependencies (excl Logger and Options) +### CQL SDK Service Dependencies -```mermaid -classDiagram - direction TB - - %% HACK: Mermaid doesnt support commas withing generic, so use a similar looking character (﹐) - - namespace CSharpCode_Generate_And_Compile { - class AssemblyCompiler { -%% Compile(librarySet : LibrarySet,definitions : DefinitionDictionary~LambdaExpression~? = null) IDictionary~string, AssemblyData~ - } - - class TypeToCSharpConverter { - } - - class CSharpLibrarySetToStreamsWriter { - } +Remarks +* Excl Logger and Options +* Cyan classes indicate scoped services +* All others are singleton services +* Classes are group by their respective projects - class CSharpCodeStreamPostProcessor { - ProcessStream(name : string, stream : Stream) void - } +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables':{ + 'primaryColor': '#222', + 'primaryTextColor': '#fff', + 'primaryBorderColor': 'gray', + 'lineColor': '#888' +}}}%% - class WriteToFileCSharpCodeStreamPostProcessor { - } +classDiagram - class AssemblyDataPostProcessor { - ProcessAssemblyData(name : string, assemblyData : AssemblyData) void - } + direction LR - class WriteToFileAssemblyDataPostProcessor { - } + namespace Compiler { + class LibrarySetExpressionBuilder { } + class LibraryExpressionBuilder { } + class ExpressionBuilder { } + class ExpressionBuilderSettings { } + class TupleBuilderCache { } + class CqlContextBinder { } + class CqlOperatorsBinder { } } - namespace Expression_Building { - class LibrarySetExpressionBuilder { - ProcessLibrarySet(librarySet : LibrarySet) DefinitionDictionary - } - - class OperatorsBinder { - } - - class CqlOperatorsBinder { - } - - class ContextBinder{ - } - - class CqlContextBinder{ - } - - class TypeConverter { - } - - class ModelInspector { - } - - class CqlCompilerFactory { - } + namespace CodeGeneration { + class CSharpCodeStreamPostProcessor { } + class WriteToFileCSharpCodeStreamPostProcessor { } + class StubCSharpCodeStreamPostProcessor { } + class AssemblyDataPostProcessor { } + class WriteToFileAssemblyDataPostProcessor { } + class StubAssemblyDataPostProcessor { } + class TypeToCSharpConverter { } + class CSharpLibrarySetToStreamsWriter { } + class AssemblyCompiler { } } - namespace Fhir_Resource_Building { - class ResourcePackager { -%% PackageResources(elmDirectory : DirectoryInfo, cqlDirectory : DirectoryInfo, resourceCanonicalRootUrl : string? = null) IReadOnlyCollection~Resource~ - } - - class FhirResourcePostProcessor { -%% ProcessResource(resource : Resource) void - } + namespace Packaging { + class FhirResourcePostProcessor { } + class WriteToFileFhirResourcePostProcessor { } + class StubFhirResourcePostProcessor { } + class CqlToResourcePackagingPipeline { } + class ResourcePackager { } + } - class WriteToFileFhirResourcePostProcessor { - } + namespace Abstraction { + class TypeResolver { } } - namespace Cql_To_Resource_Pipeline { - class CqlToResourcePackagingPipeline { - } + namespace Runtime { + class BaseTypeResolver { } } - namespace Application { - class PackagerCliProgram { - } + namespace Fhir { + class FhirTypeResolver { } + class ModelInspector { } + } - class OptionsConsoleDumper { - } + namespace Conversion { + class TypeConverter { } } -%% namespace Dependencies { - class TypeManager { - get_TypeResolver() TypeResolver - get_TupleTypes() IEnumerable~Type~ - } + %% Style Scoped Types as Cyan - - class TypeResolver { - } -%% } + style LibrarySetExpressionBuilder fill:#055 + style LibraryExpressionBuilder fill:#055 + style ExpressionBuilder fill:#055 + style TupleBuilderCache fill:#055 + style CqlToResourcePackagingPipeline fill:#055 %% Inheritance - CqlOperatorsBinder --> OperatorsBinder : inherits - CqlContextBinder --> ContextBinder : inherits + BaseTypeResolver --> TypeResolver : inherits + FhirTypeResolver --> BaseTypeResolver : inherits WriteToFileCSharpCodeStreamPostProcessor --> CSharpCodeStreamPostProcessor : inherits + StubCSharpCodeStreamPostProcessor --> CSharpCodeStreamPostProcessor : inherits WriteToFileAssemblyDataPostProcessor --> AssemblyDataPostProcessor : inherits + StubAssemblyDataPostProcessor --> AssemblyDataPostProcessor : inherits WriteToFileFhirResourcePostProcessor --> FhirResourcePostProcessor : inherits + StubFhirResourcePostProcessor --> FhirResourcePostProcessor : inherits - %% Injected Dependencies - - TypeToCSharpConverter ..> CSharpLibrarySetToStreamsWriter : injected - - AssemblyDataPostProcessor ..> AssemblyCompiler : injected\n(optional) - CSharpCodeStreamPostProcessor ..> AssemblyCompiler : injected\n(optional) - CSharpLibrarySetToStreamsWriter ..> AssemblyCompiler : injected - TypeManager ..> AssemblyCompiler : injected - - AssemblyCompiler ..> CqlToResourcePackagingPipeline : injected - ILibrarySetExpressionBuilder ..> CqlToResourcePackagingPipeline : injected - ResourcePackager ..> CqlToResourcePackagingPipeline : injected - - OptionsConsoleDumper ..> PackagerCliProgram : injected - CqlToResourcePackagingPipeline ..> PackagerCliProgram : injected - - TypeResolver ..> CqlOperatorsBinder : injected - TypeConverter ..> CqlOperatorsBinder : injected - - ModelInspector ..> TypeConverter : injected - - TypeResolver ..> TypeManager : injected - - TypeResolver ..> ResourcePackager : injected - FhirResourcePostProcessor ..> ResourcePackager : injected\n(optional) - - TypeResolver ..> CSharpLibrarySetToStreamsWriter : injected -``` - - - -### Expression Builder Dependencies (excl Logger and Options) - -```mermaid -classDiagram - direction TB - - %% HACK: Mermaid doesnt support commas withing generic, so use a similar looking character (﹐) - -%% namespace Expression_Building { - class ILibrarySetExpressionBuilderContext{ - } - - class LibrarySetExpressionBuilderContext { - } - - class LibrarySetExpressionBuilder{ - } - - class ILibraryExpressionBuilderContext{ - } - - class LibraryExpressionBuilderContext { - } - - class LibraryExpressionBuilder{ - } - - class ExpressionBuilderSettings { - } - - class ExpressionBuilder{ - } - - class OperatorsBinder { - } - - class CqlOperatorsBinder { - } - - class ContextBinder{ - } - - class CqlContextBinder{ - } - - class TypeConverter { - } - - class ModelInspector { - } - - class CqlCompilerFactory { - } -%% } - - namespace Cql_To_Resource_Pipeline { - class CqlToResourcePackagingPipeline { - } - } - -%% namespace Dependencies { - class TypeManager { - get_TypeResolver() TypeResolver - get_TupleTypes() IEnumerable~Type~ - } - - - class TypeResolver { - } -%% } - - %% Inheritance - - LibrarySetExpressionBuilderContext --> ILibrarySetExpressionBuilderContext : implements - LibraryExpressionBuilderContext --> ILibraryExpressionBuilderContext : implements - - CqlContextBinder --> ContextBinder : inherits - CqlOperatorsBinder --> OperatorsBinder : inherits - - %% Injected Dependencies + %% Dependencies LibraryExpressionBuilder ..> LibrarySetExpressionBuilder : injected - LibrarySetExpressionBuilder ..> LibrarySetExpressionBuilderContext : injected - DefinitionDictionary~LambdaExpression~ ..> LibrarySetExpressionBuilderContext : injected - LibrarySet ..> LibrarySetExpressionBuilderContext : injected - ExpressionBuilder ..> LibraryExpressionBuilder : injected - LibraryExpressionBuilder ..> LibraryExpressionBuilderContext : injected - Library ..> LibraryExpressionBuilderContext : injected - DefinitionDictionary~LambdaExpression~ ..> LibraryExpressionBuilderContext : injected - ILibraryExpressionBuilderContext ..> LibraryExpressionBuilderContext : injected (optional) - - OperatorsBinder ..> ExpressionBuilder : injected - TypeManager ..> ExpressionBuilder : injected - TypeConverter ..> ExpressionBuilder : injected TypeResolver ..> ExpressionBuilder : injected - ContextBinder ..> ExpressionBuilder : injected + CqlOperatorsBinder ..> ExpressionBuilder : injected + TupleBuilderCache ..> ExpressionBuilder : injected + CqlContextBinder ..> ExpressionBuilder : injected ExpressionBuilderSettings ..> ExpressionBuilder : injected - ILibraryExpressionBuilderContext ..> ExpressionBuilder : injected - - LibrarySetExpressionBuilder ..> CqlToResourcePackagingPipeline : injected TypeResolver ..> CqlOperatorsBinder : injected TypeConverter ..> CqlOperatorsBinder : injected ModelInspector ..> TypeConverter : injected - TypeResolver ..> TypeManager : injected -``` + TypeToCSharpConverter ..> CSharpLibrarySetToStreamsWriter : injected + + AssemblyDataPostProcessor ..> AssemblyCompiler : injected\n(optional) + CSharpCodeStreamPostProcessor ..> AssemblyCompiler : injected\n(optional) + CSharpLibrarySetToStreamsWriter ..> AssemblyCompiler : injected + TypeResolver ..> AssemblyCompiler : injected + + TypeResolver ..> CSharpLibrarySetToStreamsWriter : injected + + AssemblyCompiler ..> CqlToResourcePackagingPipeline : injected + ResourcePackager ..> CqlToResourcePackagingPipeline : injected + LibrarySetExpressionBuilder ..> CqlToResourcePackagingPipeline : injected + TypeResolver ..> ResourcePackager : injected + FhirResourcePostProcessor ..> ResourcePackager : injected\n(optional) +``` \ No newline at end of file