Skip to content

Commit fd69ac3

Browse files
authored
Merge pull request #928 from FirelyTeam/copilot/fix-927
Add CqlFunctionParameterAttribute and consolidate IdentifierNormalizer with refined character mapping
2 parents fa2e057 + 20dd07b commit fd69ac3

File tree

152 files changed

+953
-291
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+953
-291
lines changed

Cql/CodeGeneration.NET/LibraryDefinitionCSharpCodeGenerator.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
using Hl7.Cql.Compiler.Expressions;
1010
using Hl7.Cql.Abstractions.Infrastructure;
11+
using Hl7.Cql.Compiler;
12+
using Hl7.Cql.Runtime;
1113

1214
namespace Hl7.Cql.CodeGeneration.NET
1315
{
@@ -111,8 +113,10 @@ private string GetTargetedMemberName(
111113
string targetName,
112114
string memberName)
113115
{
114-
var target = targetName == LibraryName ? "this" : $"{VariableNameGenerator.NormalizeIdentifier(targetName)}.Instance";
115-
var member = VariableNameGenerator.NormalizeIdentifier(memberName);
116+
var target = targetName == LibraryName
117+
? "this"
118+
: $"{IdentifierNormalizer.Normalize(targetName)}.Instance";
119+
var member = IdentifierNormalizer.Normalize(memberName);
116120
return $"{target}.{member}";
117121
}
118122

@@ -505,11 +509,26 @@ private string ConvertMemberExpression(
505509

506510
private string ConvertLambdaExpression(
507511
LambdaExpression lambda,
508-
bool functionMode = false)
512+
bool functionMode = false,
513+
IReadOnlyDictionary<string, string>? originalParameterNames = null)
509514
{
510515
var lambdaSb = new StringBuilder();
511516

512-
var parameters = lambda.Parameters.Select(p => $"{TypeToCSharpConverter.ToCSharp(p.Type)} {p.Name!.EscapeKeywords()}").ToList();
517+
var parameters = lambda.Parameters.Select(p =>
518+
{
519+
var typeDeclaration = TypeToCSharpConverter.ToCSharp(p.Type);
520+
var parameterName = p.Name!.EscapeKeywords();
521+
522+
// Add attribute if original name differs from normalized name
523+
var attributePrefix = "";
524+
if (originalParameterNames?.TryGetValue(p.Name!, out var originalName) == true)
525+
{
526+
attributePrefix = $"[CqlFunctionParameter({originalName.QuoteString()})] ";
527+
}
528+
529+
return $"{attributePrefix}{typeDeclaration} {parameterName}";
530+
}).ToList();
531+
513532
// inserts the context parameter in the start of the lambda expression
514533
if (Indent == 0)
515534
parameters.Insert(0, "CqlContext context");
@@ -555,15 +574,16 @@ private string ConvertLocalFunctionDefinition(
555574
public string ProcessDefinition(
556575
LambdaExpression function,
557576
string name,
558-
string specifiers)
577+
string specifiers,
578+
IReadOnlyDictionary<string, string>? originalParameterNames = null)
559579
{
560580
var funcSb = new StringBuilder();
561581

562582
funcSb.Append(specifiers + " ");
563583
funcSb.Append(TypeToCSharpConverter.ToCSharp(function.ReturnType) + " ");
564584
funcSb.Append(name);
565585

566-
var lambda = ConvertLambdaExpression(function, functionMode: true);
586+
var lambda = ConvertLambdaExpression(function, functionMode: true, originalParameterNames: originalParameterNames);
567587
funcSb.Append(lambda);
568588

569589
if (function.Body is not BlockExpression)

Cql/CodeGeneration.NET/LibrarySetCSharpCodeGenerator.cs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public LibraryWriter(
144144

145145
private CqlVersionedLibraryIdentifier LibraryVersionedIdentifier => Library.VersionedLibraryIdentifier!;
146146
public string LibraryName { get; } = Library.VersionedLibraryIdentifier;
147-
private string ClassName { get; } = VariableNameGenerator.NormalizeIdentifier(Library.VersionedLibraryIdentifier)!;
147+
private string ClassName { get; } = IdentifierNormalizer.Normalize(Library.VersionedLibraryIdentifier);
148148

149149
public LibraryWriter AddIndent(int addIndent = 1)
150150
{
@@ -206,7 +206,7 @@ private void WriteLibraryInterfaceImplementation()
206206
var dependencies =
207207
LibrarySetWriter.LibrarySet
208208
.GetLibraryDependencies(LibraryName, throwError: true)
209-
.Select(dep => VariableNameGenerator.NormalizeIdentifier(dep.VersionedLibraryIdentifier))
209+
.Select(dep => IdentifierNormalizer.Normalize(dep.VersionedLibraryIdentifier))
210210
.Select(typeName => $"{typeName}.Instance");
211211
IndentedTextWriter.WriteLine($"""
212212
public ILibrary[] Dependencies => [{string.Join(", ", dependencies)}];
@@ -364,8 +364,8 @@ public void WriteDefinition()
364364
{
365365
var name = CqlDefinition.Name;
366366
string quotedName = name.QuoteString();
367-
string methodName = VariableNameGenerator.NormalizeIdentifier(name)!;
368-
string fieldName = VariableNameGenerator.NormalizeIdentifier($"_{name}")!;
367+
string methodName = IdentifierNormalizer.Normalize(name);
368+
string fieldName = IdentifierNormalizer.Normalize($"_{name}");;
369369
var definitionAttributeTypeName = CqlDefinition.GetType().Name;
370370

371371
switch (CqlDefinition)
@@ -391,7 +391,7 @@ public void WriteDefinition()
391391
{
392392
var cqlCodeDefinition = LibraryWriter.CodeDefinitions.FirstOrDefault(codeDefinition => codeDefinition.Code == code);
393393
var codeField = cqlCodeDefinition is not null
394-
? VariableNameGenerator.NormalizeIdentifier($"_{cqlCodeDefinition.Name}")
394+
? IdentifierNormalizer.Normalize($"_{cqlCodeDefinition.Name}")
395395
: $"new CqlCode({code.code!.QuoteString()}, {code.system.QuoteOrNullString()})";
396396
return $"""
397397
@@ -418,7 +418,7 @@ public void WriteDefinition()
418418
{
419419
var cqlCodeDefinition = LibraryWriter.CodeDefinitions.FirstOrDefault(codeDefinition => codeDefinition.Code == code);
420420
var codeField = cqlCodeDefinition is not null
421-
? VariableNameGenerator.NormalizeIdentifier($"_{cqlCodeDefinition.Name}")
421+
? IdentifierNormalizer.Normalize($"_{cqlCodeDefinition.Name}")
422422
: $"new CqlCode({code.code!.QuoteString()}, {code.system.QuoteOrNullString()})";
423423
return $"""
424424
@@ -460,7 +460,7 @@ public void WriteDefinition()
460460
foreach (var tagValue in tag.Values)
461461
tw.WriteLine($"[CqlTag({tag.Name.QuoteString()}, {tagValue.QuoteString()})]");
462462

463-
VariableNameGenerator variableNameGenerator = new(Enumerable.Empty<string>(), postfix: "_");
463+
VariableNameGenerator variableNameGenerator = new([], postfix: "_");
464464

465465
var visitedBody = Transform(
466466
ld.LambdaExpression.Body,
@@ -479,7 +479,13 @@ public void WriteDefinition()
479479

480480
var parameters = ld.LambdaExpression.Parameters.Skip(1);
481481
var transformedLambda = Expression.Lambda(visitedBody, parameters);
482-
var definitionWithBody = definitionToCSharpCodeProcessor.ProcessDefinition(transformedLambda, methodName, specifiers: "public");
482+
483+
// Extract original parameter names if this is a CqlFunctionDefinition
484+
IReadOnlyDictionary<string, string>? originalParameterNames = CqlDefinition is CqlFunctionDefinition { OriginalParameterNames.Count: > 0 } functionDef
485+
? functionDef.OriginalParameterNames
486+
: null;
487+
488+
var definitionWithBody = definitionToCSharpCodeProcessor.ProcessDefinition(transformedLambda, methodName, specifiers: "public", originalParameterNames);
483489
tw.WriteLine(definitionWithBody);
484490
}
485491

Cql/CodeGeneration.NET/VariableNameGenerator.cs

Lines changed: 22 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88

99
namespace Hl7.Cql.CodeGeneration.NET
1010
{
11-
internal class VariableNameGenerator
11+
internal sealed class VariableNameGenerator
1212
{
13-
private readonly object SyncRoot = new();
14-
public string Postfix { get; }
13+
private readonly object _syncRoot = new();
14+
private readonly List<char> _letters = [(char)('a' - 1)];
15+
private readonly string _prefix = string.Empty;
1516

16-
private List<string> Reserved { get; }
17-
18-
private readonly List<char> Letters = [(char)('a' - 1)];
19-
private readonly string Prefix = string.Empty;
17+
private string Postfix { get; }
2018

19+
private List<string> Reserved { get; }
2120

2221
/// <summary>
2322
/// Create a new VariableNameGenerator with an (optional) set of extra reserved variable names
@@ -30,9 +29,9 @@ public VariableNameGenerator ForNewScope(IEnumerable<ParameterExpression>? scope
3029
}
3130

3231
/// <inheritdoc cref="ForNewScope(IEnumerable{ParameterExpression}?)"/>
33-
public VariableNameGenerator ForNewScope(IEnumerable<string>? scopeNames)
32+
private VariableNameGenerator ForNewScope(IEnumerable<string>? scopeNames)
3433
{
35-
var newGenerator = new VariableNameGenerator(Letters, Reserved.Concat(scopeNames ?? []), Postfix);
34+
var newGenerator = new VariableNameGenerator(_letters, Reserved.Concat(scopeNames ?? []), Postfix);
3635
return newGenerator;
3736
}
3837

@@ -42,109 +41,50 @@ public VariableNameGenerator(IEnumerable<string>? reserved = null, string postfi
4241
Postfix = postfix;
4342
}
4443

45-
public VariableNameGenerator(IEnumerable<ParameterExpression> reserved, string postfix = "") :
46-
this(reserved.Where(p => p.Name is not null).Select(p => p.Name!), postfix)
47-
{
48-
// Nothing
49-
}
50-
51-
internal VariableNameGenerator(List<char> state, IEnumerable<string>? reserved = null, string postfix = "")
44+
private VariableNameGenerator(List<char> state, IEnumerable<string>? reserved = null, string postfix = "")
5245
{
5346
Reserved = reserved?.ToList() ?? [];
5447
Postfix = postfix;
55-
Letters = state;
48+
_letters = state;
5649
}
5750

58-
public virtual string Next()
51+
public string Next()
5952
{
60-
lock (SyncRoot)
53+
lock (_syncRoot)
6154
{
6255
string vn = "";
6356
do
6457
{
65-
var lastIndex = Letters.Count - 1;
66-
var next = (char)(Letters[lastIndex] + 1);
58+
var lastIndex = _letters.Count - 1;
59+
var next = (char)(_letters[lastIndex] + 1);
6760
if (next > 'z')
6861
{
6962
next = 'a';
70-
Letters[lastIndex] = next;
71-
if (Letters.Count > 1)
63+
_letters[lastIndex] = next;
64+
if (_letters.Count > 1)
7265
{
73-
if (Letters[0] == 'z')
66+
if (_letters[0] == 'z')
7467
{
75-
Letters.Insert(0, 'a');
68+
_letters.Insert(0, 'a');
7669
}
7770
else
7871
{
79-
Letters[0] = (char)(Letters[0] + 1);
72+
_letters[0] = (char)(_letters[0] + 1);
8073
}
8174
}
82-
else Letters.Insert(0, 'a');
75+
else _letters.Insert(0, 'a');
8376
}
8477
else
8578
{
86-
Letters[lastIndex] = next;
79+
_letters[lastIndex] = next;
8780
}
88-
vn = $"{Prefix}{new string(Letters.ToArray())}{Postfix}";
81+
vn = $"{_prefix}{new string(_letters.ToArray())}{Postfix}";
8982
}
9083
while (Reserved.Contains(vn) || SyntaxFacts.GetKeywordKind(vn) != SyntaxKind.None);
9184

9285
return vn;
9386
}
9487
}
9588

96-
public static string? NormalizeIdentifier(string? identifier)
97-
{
98-
if (string.IsNullOrEmpty(identifier))
99-
return null;
100-
101-
ReadOnlySpan<char> span = identifier.AsSpan();
102-
103-
int leadingUnderscoreCount = 0;
104-
while (leadingUnderscoreCount < span.Length && span[leadingUnderscoreCount] == '_')
105-
leadingUnderscoreCount++;
106-
107-
if (leadingUnderscoreCount > 0)
108-
span = span[leadingUnderscoreCount..];
109-
110-
if (span.Length > 0 && span[0] == '$')
111-
span = span[1..];
112-
113-
Span<char> buffer = stackalloc char[span.Length+2];
114-
int bufferIndex = 0;
115-
116-
foreach (var c in span)
117-
{
118-
switch (c)
119-
{
120-
case '"':
121-
case '\'':
122-
continue;
123-
case '&':
124-
buffer[bufferIndex++] = 'a';
125-
buffer[bufferIndex++] = 'n';
126-
buffer[bufferIndex++] = 'd';
127-
continue;
128-
default:
129-
buffer[bufferIndex++] = SyntaxFacts.IsIdentifierPartCharacter(c) ? c : '_';
130-
break;
131-
}
132-
}
133-
134-
var normalized = buffer[..bufferIndex].ToString();
135-
136-
if (normalized.Length > 0 && !SyntaxFacts.IsIdentifierStartCharacter(normalized[0]))
137-
//normalized = "_" + normalized;
138-
leadingUnderscoreCount++;
139-
140-
if (leadingUnderscoreCount > 0)
141-
normalized = new string('_', leadingUnderscoreCount) + normalized;
142-
143-
if (SyntaxFacts.GetKeywordKind(normalized) != SyntaxKind.None)
144-
normalized = $"@{normalized}";
145-
146-
return normalized;
147-
}
148-
14989
}
15090
}

Cql/CodeGeneration.NET/_CODE GENERATOR VERSION_.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ internal partial class LibrarySetCSharpCodeGenerator
2222
/// Also, it is important to ensure the library invoker toolkit is updated to support the new version,
2323
/// for this you need to update the LibraryInvoker.SupportsVersion method, or create a new version of the LibraryInvoker.
2424
/// </remarks>
25-
internal const string GeneratorToolVersion = "3.0.0.0";
25+
internal const string GeneratorToolVersion = "3.1.0.0";
2626
}

Cql/CoreTests/CSharp/ConceptDefTest-1.0.0.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using Range = Hl7.Fhir.Model.Range;
1313
using Task = Hl7.Fhir.Model.Task;
1414

15-
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.0.0.0")]
15+
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.1.0.0")]
1616
[CqlLibrary("ConceptDefTest", "1.0.0")]
1717
public partial class ConceptDefTest_1_0_0 : ILibrary, ISingleton<ConceptDefTest_1_0_0>
1818
{

Cql/CoreTests/CSharp/CqlBooleanTest-1.0.000.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using Range = Hl7.Fhir.Model.Range;
1313
using Task = Hl7.Fhir.Model.Task;
1414

15-
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.0.0.0")]
15+
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.1.0.0")]
1616
[CqlLibrary("CqlBooleanTest", "1.0.000")]
1717
public partial class CqlBooleanTest_1_0_000 : ILibrary, ISingleton<CqlBooleanTest_1_0_000>
1818
{

Cql/CoreTests/CSharp/CqlNestedTupleTest-1.0.0.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using Range = Hl7.Fhir.Model.Range;
1313
using Task = Hl7.Fhir.Model.Task;
1414

15-
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.0.0.0")]
15+
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.1.0.0")]
1616
[CqlLibrary("CqlNestedTupleTest", "1.0.0")]
1717
public partial class CqlNestedTupleTest_1_0_0 : ILibrary, ISingleton<CqlNestedTupleTest_1_0_0>
1818
{

Cql/CoreTests/CSharp/FHIRConversionTest-2023.0.0.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using Range = Hl7.Fhir.Model.Range;
1313
using Task = Hl7.Fhir.Model.Task;
1414

15-
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.0.0.0")]
15+
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.1.0.0")]
1616
[CqlLibrary("FHIRConversionTest", "2023.0.0")]
1717
public partial class FHIRConversionTest_2023_0_0 : ILibrary, ISingleton<FHIRConversionTest_2023_0_0>
1818
{

Cql/CoreTests/CSharp/FHIRHelpers-4.0.1.g.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
using Range = Hl7.Fhir.Model.Range;
1313
using Task = Hl7.Fhir.Model.Task;
1414

15-
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.0.0.0")]
15+
[System.CodeDom.Compiler.GeneratedCode(".NET Code Generation", "3.1.0.0")]
1616
[CqlLibrary("FHIRHelpers", "4.0.1")]
1717
public partial class FHIRHelpers_4_0_1 : ILibrary, ISingleton<FHIRHelpers_4_0_1>
1818
{

0 commit comments

Comments
 (0)