Skip to content

Commit 6510776

Browse files
AllanGuigouCopilot
andauthored
Add support for case function (#4147)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c96dcd4 commit 6510776

File tree

18 files changed

+159
-17
lines changed

18 files changed

+159
-17
lines changed

src/Runner.Worker/ActionManifestManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ private TemplateContext CreateTemplateContext(
316316
Schema = _actionManifestSchema,
317317
// TODO: Switch to real tracewriter for cutover
318318
TraceWriter = new GitHub.Actions.WorkflowParser.ObjectTemplating.EmptyTraceWriter(),
319+
AllowCaseFunction = false,
319320
};
320321

321322
// Expression values from execution context

src/Runner.Worker/ActionManifestManagerLegacy.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ private TemplateContext CreateTemplateContext(
315315
maxBytes: 10 * 1024 * 1024),
316316
Schema = _actionManifestSchema,
317317
TraceWriter = executionContext.ToTemplateTraceWriter(),
318+
AllowCaseFunction = false,
318319
};
319320

320321
// Expression values from execution context

src/Sdk/DTExpressions2/Expressions2/ExpressionConstants.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ internal static class ExpressionConstants
99
{
1010
static ExpressionConstants()
1111
{
12+
AddFunction<Case>("case", 3, Byte.MaxValue);
1213
AddFunction<Contains>("contains", 2, 2);
1314
AddFunction<EndsWith>("endsWith", 2, 2);
1415
AddFunction<Format>("format", 1, Byte.MaxValue);

src/Sdk/DTExpressions2/Expressions2/ExpressionParser.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ public IExpressionNode CreateTree(
1717
String expression,
1818
ITraceWriter trace,
1919
IEnumerable<INamedValueInfo> namedValues,
20-
IEnumerable<IFunctionInfo> functions)
20+
IEnumerable<IFunctionInfo> functions,
21+
Boolean allowCaseFunction = true)
2122
{
22-
var context = new ParseContext(expression, trace, namedValues, functions);
23+
var context = new ParseContext(expression, trace, namedValues, functions, allowCaseFunction);
2324
context.Trace.Info($"Parsing expression: <{expression}>");
2425
return CreateTree(context);
2526
}
@@ -349,6 +350,10 @@ private static void FlushTopEndParameters(ParseContext context)
349350
{
350351
throw new ParseException(ParseExceptionKind.TooManyParameters, token: @operator, expression: context.Expression);
351352
}
353+
else if (functionInfo.Name.Equals("case", StringComparison.OrdinalIgnoreCase) && function.Parameters.Count % 2 == 0)
354+
{
355+
throw new ParseException(ParseExceptionKind.EvenParameters, token: @operator, expression: context.Expression);
356+
}
352357
}
353358

354359
/// <summary>
@@ -411,13 +416,20 @@ private static Boolean TryGetFunctionInfo(
411416
String name,
412417
out IFunctionInfo functionInfo)
413418
{
419+
if (String.Equals(name, "case", StringComparison.OrdinalIgnoreCase) && !context.AllowCaseFunction)
420+
{
421+
functionInfo = null;
422+
return false;
423+
}
424+
414425
return ExpressionConstants.WellKnownFunctions.TryGetValue(name, out functionInfo) ||
415426
context.ExtensionFunctions.TryGetValue(name, out functionInfo);
416427
}
417428

418429
private sealed class ParseContext
419430
{
420431
public Boolean AllowUnknownKeywords;
432+
public Boolean AllowCaseFunction;
421433
public readonly String Expression;
422434
public readonly Dictionary<String, IFunctionInfo> ExtensionFunctions = new Dictionary<String, IFunctionInfo>(StringComparer.OrdinalIgnoreCase);
423435
public readonly Dictionary<String, INamedValueInfo> ExtensionNamedValues = new Dictionary<String, INamedValueInfo>(StringComparer.OrdinalIgnoreCase);
@@ -433,7 +445,8 @@ public ParseContext(
433445
ITraceWriter trace,
434446
IEnumerable<INamedValueInfo> namedValues,
435447
IEnumerable<IFunctionInfo> functions,
436-
Boolean allowUnknownKeywords = false)
448+
Boolean allowUnknownKeywords = false,
449+
Boolean allowCaseFunction = true)
437450
{
438451
Expression = expression ?? String.Empty;
439452
if (Expression.Length > ExpressionConstants.MaxLength)
@@ -454,6 +467,7 @@ public ParseContext(
454467

455468
LexicalAnalyzer = new LexicalAnalyzer(Expression);
456469
AllowUnknownKeywords = allowUnknownKeywords;
470+
AllowCaseFunction = allowCaseFunction;
457471
}
458472

459473
private class NoOperationTraceWriter : ITraceWriter

src/Sdk/DTExpressions2/Expressions2/ParseException.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ internal ParseException(ParseExceptionKind kind, Token token, String expression)
2929
case ParseExceptionKind.TooManyParameters:
3030
description = "Too many parameters supplied";
3131
break;
32+
case ParseExceptionKind.EvenParameters:
33+
description = "Even number of parameters supplied, requires an odd number of parameters";
34+
break;
3235
case ParseExceptionKind.UnexpectedEndOfExpression:
3336
description = "Unexpected end of expression";
3437
break;

src/Sdk/DTExpressions2/Expressions2/ParseExceptionKind.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ internal enum ParseExceptionKind
66
ExceededMaxLength,
77
TooFewParameters,
88
TooManyParameters,
9+
EvenParameters,
910
UnexpectedEndOfExpression,
1011
UnexpectedSymbol,
1112
UnrecognizedFunction,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
2+
3+
using System;
4+
using GitHub.Actions.Expressions.Data;
5+
6+
namespace GitHub.DistributedTask.Expressions2.Sdk.Functions
7+
{
8+
internal sealed class Case : Function
9+
{
10+
protected sealed override Object EvaluateCore(
11+
EvaluationContext context,
12+
out ResultMemory resultMemory)
13+
{
14+
resultMemory = null;
15+
// Validate argument count - must be odd (pairs of predicate-result plus default)
16+
if (Parameters.Count % 2 == 0)
17+
{
18+
throw new InvalidOperationException("case requires an odd number of arguments");
19+
}
20+
21+
// Evaluate predicate-result pairs
22+
for (var i = 0; i < Parameters.Count - 1; i += 2)
23+
{
24+
var predicate = Parameters[i].Evaluate(context);
25+
26+
// Predicate must be a boolean
27+
if (predicate.Kind != ValueKind.Boolean)
28+
{
29+
throw new InvalidOperationException("case predicate must evaluate to a boolean value");
30+
}
31+
32+
// If predicate is true, return the corresponding result
33+
if ((Boolean)predicate.Value)
34+
{
35+
var result = Parameters[i + 1].Evaluate(context);
36+
return result.Value;
37+
}
38+
}
39+
40+
// No predicate matched, return default (last argument)
41+
var defaultResult = Parameters[Parameters.Count - 1].Evaluate(context);
42+
return defaultResult.Value;
43+
}
44+
}
45+
}

src/Sdk/DTObjectTemplating/ObjectTemplating/TemplateContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ internal IDictionary<String, Object> State
8686

8787
internal ITraceWriter TraceWriter { get; set; }
8888

89+
/// <summary>
90+
/// Gets or sets a value indicating whether the case expression function is allowed.
91+
/// Defaults to true. Set to false to disable the case function.
92+
/// </summary>
93+
internal Boolean AllowCaseFunction { get; set; } = true;
94+
8995
private IDictionary<String, Int32> FileIds
9096
{
9197
get

src/Sdk/DTObjectTemplating/ObjectTemplating/Tokens/TemplateToken.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ protected StringToken EvaluateStringToken(
5757
var originalBytes = context.Memory.CurrentBytes;
5858
try
5959
{
60-
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
60+
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
6161
var options = new EvaluationOptions
6262
{
6363
MaxMemory = context.Memory.MaxBytes,
@@ -94,7 +94,7 @@ protected SequenceToken EvaluateSequenceToken(
9494
var originalBytes = context.Memory.CurrentBytes;
9595
try
9696
{
97-
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
97+
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
9898
var options = new EvaluationOptions
9999
{
100100
MaxMemory = context.Memory.MaxBytes,
@@ -123,7 +123,7 @@ protected MappingToken EvaluateMappingToken(
123123
var originalBytes = context.Memory.CurrentBytes;
124124
try
125125
{
126-
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
126+
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
127127
var options = new EvaluationOptions
128128
{
129129
MaxMemory = context.Memory.MaxBytes,
@@ -152,7 +152,7 @@ protected TemplateToken EvaluateTemplateToken(
152152
var originalBytes = context.Memory.CurrentBytes;
153153
try
154154
{
155-
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions);
155+
var tree = new ExpressionParser().CreateTree(expression, null, context.GetExpressionNamedValues(), context.ExpressionFunctions, allowCaseFunction: context.AllowCaseFunction);
156156
var options = new EvaluationOptions
157157
{
158158
MaxMemory = context.Memory.MaxBytes,

src/Sdk/DTPipelines/Pipelines/ObjectTemplating/PipelineTemplateConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ private static String ConvertToIfCondition(
663663
var node = default(ExpressionNode);
664664
try
665665
{
666-
node = expressionParser.CreateTree(condition, null, namedValues, functions) as ExpressionNode;
666+
node = expressionParser.CreateTree(condition, null, namedValues, functions, allowCaseFunction: context.AllowCaseFunction) as ExpressionNode;
667667
}
668668
catch (Exception ex)
669669
{

0 commit comments

Comments
 (0)