< Summary

Information
Class: Elsa.Dsl.ElsaScript.Compiler.ElsaScriptCompiler
Assembly: Elsa.Dsl.ElsaScript
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Compiler/ElsaScriptCompiler.cs
Line coverage
69%
Covered lines: 198
Uncovered lines: 86
Coverable lines: 284
Total lines: 601
Line coverage: 69.7%
Branch coverage
68%
Covered branches: 103
Total branches: 150
Branch coverage: 68.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Dsl.ElsaScript/Compiler/ElsaScriptCompiler.cs

#LineLine coverage
 1using Elsa.Dsl.ElsaScript.Ast;
 2using Elsa.Dsl.ElsaScript.Contracts;
 3using Elsa.Dsl.ElsaScript.Helpers;
 4using Elsa.Expressions.Models;
 5using Elsa.Workflows;
 6using Elsa.Workflows.Activities;
 7using Elsa.Workflows.Activities.Flowchart.Models;
 8using Elsa.Workflows.Memory;
 9using Elsa.Workflows.Models;
 10
 11namespace Elsa.Dsl.ElsaScript.Compiler;
 12
 13/// <summary>
 14/// Compiles an ElsaScript AST into Elsa workflows.
 15/// </summary>
 23516public class ElsaScriptCompiler(IActivityRegistryLookupService activityRegistryLookupService, IElsaScriptParser parser) 
 17{
 118    private static readonly Dictionary<string, string> LanguageMappings = new(StringComparer.OrdinalIgnoreCase)
 119    {
 120        ["js"] = "JavaScript",
 121        ["cs"] = "CSharp",
 122        ["py"] = "Python",
 123        ["liquid"] = "Liquid"
 124    };
 25
 23526    private string _defaultExpressionLanguage = "JavaScript";
 23527    private readonly Dictionary<string, Variable> _variables = new();
 28
 29    /// <inheritdoc />
 30    public async Task<Workflow> CompileAsync(string source, CancellationToken cancellationToken = default)
 31    {
 1232        var programNode = parser.Parse(source);
 1233        return await CompileAsync(programNode, cancellationToken);
 1234    }
 35
 36    /// <inheritdoc />
 37    public async Task<Workflow> CompileAsync(ProgramNode programNode, CancellationToken cancellationToken = default)
 38    {
 39        // Get the single workflow (enforced by parser)
 1240        var workflowNode = programNode.Workflows.First();
 41
 42        // Merge global use statements with workflow-level ones
 43        // Create a temporary workflow node with merged use statements
 1244        var mergedWorkflowNode = new WorkflowNode
 1245        {
 1246            Id = workflowNode.Id,
 1247            Metadata = workflowNode.Metadata,
 1248            UseStatements = [..programNode.GlobalUseStatements, ..workflowNode.UseStatements],
 1249            Body = workflowNode.Body
 1250        };
 51
 1252        return await CompileWorkflowNodeAsync(mergedWorkflowNode, cancellationToken);
 1253    }
 54
 55    private async Task<Workflow> CompileWorkflowNodeAsync(WorkflowNode workflowNode, CancellationToken cancellationToken
 56    {
 1257        _variables.Clear();
 1258        _defaultExpressionLanguage = "JavaScript";
 59
 60        // Process use statements (workflow-level overrides global)
 4261        foreach (var useNode in workflowNode.UseStatements)
 62        {
 963            if (useNode.Type == UseType.Expressions)
 64            {
 765                _defaultExpressionLanguage = MapLanguageName(useNode.Value);
 66            }
 67        }
 68
 69        // Compile body statements
 1270        var activities = new List<IActivity>();
 5871        foreach (var statement in workflowNode.Body)
 72        {
 1773            var activity = await CompileStatementAsync(statement, cancellationToken);
 1774            if (activity != null)
 75            {
 1376                activities.Add(activity);
 77            }
 78        }
 79
 80        // Create the root activity (Sequence containing all statements)
 1281        var root = activities.Count == 1
 1282            ? activities[0]
 1283            : new Sequence
 1284            {
 1285                Activities = activities
 1286            };
 87
 88        // Extract metadata with defaults
 1289        var definitionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionId") ?? workflowNode.Id;
 1290        var displayName = GetMetadataValue<string>(workflowNode.Metadata, "DisplayName") ?? workflowNode.Id;
 1291        var description = GetMetadataValue<string>(workflowNode.Metadata, "Description") ?? string.Empty;
 1292        var definitionVersionId = GetMetadataValue<string>(workflowNode.Metadata, "DefinitionVersionId") ?? $"{definitio
 1293        var version = GetMetadataValueOrDefault(workflowNode.Metadata, "Version", 1);
 1294        var usableAsActivity = GetMetadataValue<bool?>(workflowNode.Metadata, "UsableAsActivity");
 95
 96        // Create the workflow
 1297        var workflow = new Workflow
 1298        {
 1299            Name = displayName,
 12100            Identity = new WorkflowIdentity(definitionId, version, definitionVersionId, null),
 12101            WorkflowMetadata = new(displayName, description, ToolVersion: new("3.6.0")),
 12102            Root = root,
 12103            Variables = _variables.Values.ToList(),
 12104            Options = new()
 12105            {
 12106                UsableAsActivity = usableAsActivity
 12107            }
 12108        };
 109
 12110        return workflow;
 12111    }
 112
 113    private T? GetMetadataValue<T>(Dictionary<string, object> metadata, string key) =>
 60114        metadata.TryGetValue(key, out var value) ? ConvertValue<T>(value, default) : default;
 115
 116    private T GetMetadataValueOrDefault<T>(Dictionary<string, object> metadata, string key, T defaultValue) =>
 12117        metadata.TryGetValue(key, out var value) ? ConvertValue(value, defaultValue) : defaultValue;
 118
 119    private static T? ConvertValue<T>(object value, T? defaultValue)
 120    {
 121        // Handle direct type match
 6122        if (value is T typedValue)
 5123            return typedValue;
 124
 125        // Try to convert
 126        try
 127        {
 1128            var targetType = Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T);
 1129            return (T)Convert.ChangeType(value, targetType);
 130        }
 0131        catch (Exception)
 132        {
 133            // Return default value if conversion fails
 0134            return defaultValue;
 135        }
 1136    }
 137
 138    private async Task<IActivity?> CompileStatementAsync(StatementNode statement, CancellationToken cancellationToken = 
 139    {
 24140        return statement switch
 24141        {
 4142            VariableDeclarationNode varDecl => CompileVariableDeclaration(varDecl),
 13143            ActivityInvocationNode actInv => await CompileActivityInvocationAsync(actInv, cancellationToken),
 1144            BlockNode block => await CompileBlockAsync(block, cancellationToken),
 0145            IfNode ifNode => await CompileIfAsync(ifNode, cancellationToken),
 0146            ForEachNode forEach => await CompileForEachAsync(forEach, cancellationToken),
 2147            ForNode forNode => await CompileForAsync(forNode, cancellationToken),
 0148            WhileNode whileNode => await CompileWhileAsync(whileNode, cancellationToken),
 0149            SwitchNode switchNode => await CompileSwitchAsync(switchNode, cancellationToken),
 3150            FlowchartNode flowchart => await CompileFlowchartAsync(flowchart, cancellationToken),
 1151            ListenNode listen => await CompileListenAsync(listen, cancellationToken),
 0152            _ => throw new NotSupportedException($"Statement type {statement.GetType().Name} is not supported")
 24153        };
 24154    }
 155
 156    private IActivity? CompileVariableDeclaration(VariableDeclarationNode varDecl)
 157    {
 158        // Create and register the variable
 4159        var initialValue = varDecl.Value != null ? EvaluateConstantExpression(varDecl.Value) : null;
 4160        var variable = new Variable(varDecl.Name, initialValue);
 4161        _variables[varDecl.Name] = variable;
 162
 163        // Variable declarations don't produce activities themselves
 4164        return null;
 165    }
 166
 167    private async Task<IActivity> CompileActivityInvocationAsync(ActivityInvocationNode actInv, CancellationToken cancel
 168    {
 169        // Try to find the activity type by name - try several strategies
 14170        var activityDescriptor = await activityRegistryLookupService.FindAsync(actInv.ActivityName);
 171
 172        // If not found, try with "Elsa." prefix
 14173        if (activityDescriptor == null)
 174        {
 14175            activityDescriptor = await activityRegistryLookupService.FindAsync($"Elsa.{actInv.ActivityName}");
 176        }
 177
 178        // If still not found, search by descriptor name
 14179        if (activityDescriptor == null)
 180        {
 0181            activityDescriptor = await activityRegistryLookupService.FindAsync(d => d.Name == actInv.ActivityName);
 182        }
 183
 14184        if (activityDescriptor == null)
 185        {
 0186            throw new InvalidOperationException($"Activity '{actInv.ActivityName}' not found in registry");
 187        }
 188
 14189        var activityType = activityDescriptor.ClrType;
 190
 191        // Separate named and positional arguments
 192        var namedArgs = actInv.Arguments.Where(a => a.Name != null).ToList();
 193        var positionalArgs = actInv.Arguments.Where(a => a.Name == null).ToList();
 194
 195        IActivity activity;
 196
 197        // If we have positional arguments, try to find a matching constructor
 14198        if (positionalArgs.Any())
 199        {
 12200            activity = InstantiateActivityUsingConstructor(activityType, positionalArgs);
 201        }
 202        else
 203        {
 204            // No positional arguments, use default constructor
 205            var activityConstructorContext = new ActivityConstructorContext(activityDescriptor, (t) => new(ActivityActiv
 2206            var activityConstructionResult = activityDescriptor.Constructor(activityConstructorContext);
 2207            activity = activityConstructionResult.Activity;
 208        }
 209
 210        // Set named argument properties
 28211        foreach (var arg in namedArgs)
 212        {
 0213            var property = activityType.GetProperty(arg.Name!);
 214
 0215            if (property != null)
 216            {
 0217                var value = CompileExpression(arg.Value, property.PropertyType);
 0218                property.SetValue(activity, value);
 219            }
 220            else
 221            {
 0222                throw new InvalidOperationException($"Property '{arg.Name}' not found on activity type '{activityType.Na
 223            }
 224        }
 225
 14226        return activity;
 14227    }
 228
 229    private async Task<IActivity> CompileBlockAsync(BlockNode block, CancellationToken cancellationToken = default)
 230    {
 1231        var activities = new List<IActivity>();
 232
 6233        foreach (var statement in block.Statements)
 234        {
 2235            var activity = await CompileStatementAsync(statement, cancellationToken);
 2236            if (activity != null)
 237            {
 2238                activities.Add(activity);
 239            }
 240        }
 241
 1242        return new Sequence
 1243        {
 1244            Activities = activities
 1245        };
 1246    }
 247
 248    private async Task<IActivity> CompileIfAsync(IfNode ifNode, CancellationToken cancellationToken = default)
 249    {
 0250        var condition = CompileExpressionAsInput<bool>(ifNode.Condition);
 0251        var thenActivity = await CompileStatementAsync(ifNode.Then, cancellationToken);
 0252        var elseActivity = ifNode.Else != null ? await CompileStatementAsync(ifNode.Else, cancellationToken) : null;
 253
 0254        return new If(condition)
 0255        {
 0256            Then = thenActivity,
 0257            Else = elseActivity
 0258        };
 0259    }
 260
 261    private async Task<IActivity> CompileForEachAsync(ForEachNode forEach, CancellationToken cancellationToken = default
 262    {
 263        Variable loopVariable;
 264
 0265        if (forEach.DeclaresVariable)
 266        {
 267            // Create a new loop variable
 0268            loopVariable = new Variable<object>(forEach.VariableName, null!);
 0269            _variables[forEach.VariableName] = loopVariable;
 270        }
 271        else
 272        {
 273            // Reuse existing variable
 0274            if (!_variables.TryGetValue(forEach.VariableName, out loopVariable!))
 275            {
 0276                throw new InvalidOperationException($"Variable '{forEach.VariableName}' is not declared. Use 'var {forEa
 277            }
 278        }
 279
 0280        var items = CompileExpressionAsInput<ICollection<object>>(forEach.Collection);
 0281        var body = await CompileStatementAsync(forEach.Body, cancellationToken);
 282
 0283        var forEachActivity = new ForEach<object>(items)
 0284        {
 0285            CurrentValue = new(loopVariable),
 0286            Body = body
 0287        };
 288
 0289        return forEachActivity;
 0290    }
 291
 292    private async Task<IActivity> CompileForAsync(ForNode forNode, CancellationToken cancellationToken = default)
 293    {
 294        Variable loopVariable;
 295
 2296        if (forNode.DeclaresVariable)
 297        {
 298            // Create a new loop variable
 2299            loopVariable = new Variable<int>(forNode.VariableName, 0);
 2300            _variables[forNode.VariableName] = loopVariable;
 301        }
 302        else
 303        {
 304            // Reuse existing variable
 0305            if (!_variables.TryGetValue(forNode.VariableName, out loopVariable!))
 306            {
 0307                throw new InvalidOperationException($"Variable '{forNode.VariableName}' is not declared. Use 'var {forNo
 308            }
 309        }
 310
 2311        var start = CompileExpressionAsInput<int>(forNode.Start);
 2312        var end = CompileExpressionAsInput<int>(forNode.End);
 2313        var step = CompileExpressionAsInput<int>(forNode.Step);
 2314        var body = await CompileStatementAsync(forNode.Body, cancellationToken);
 315
 2316        var forActivity = new For
 2317        {
 2318            Start = start,
 2319            End = end,
 2320            Step = step,
 2321            OuterBoundInclusive = new Input<bool>(forNode.IsInclusive),
 2322            CurrentValue = new Output<object?>(loopVariable),
 2323            Body = body
 2324        };
 325
 2326        return forActivity;
 2327    }
 328
 329    private async Task<IActivity> CompileWhileAsync(WhileNode whileNode, CancellationToken cancellationToken = default)
 330    {
 0331        var condition = CompileExpressionAsInput<bool>(whileNode.Condition);
 0332        var body = await CompileStatementAsync(whileNode.Body, cancellationToken);
 333
 0334        return new While(condition)
 0335        {
 0336            Body = body
 0337        };
 0338    }
 339
 340    private async Task<IActivity> CompileSwitchAsync(SwitchNode switchNode, CancellationToken cancellationToken = defaul
 341    {
 0342        var cases = new List<SwitchCase>();
 343
 0344        foreach (var caseNode in switchNode.Cases)
 345        {
 0346            var caseExpression = CompileExpressionAsExpression(caseNode.Value);
 0347            var caseBody = await CompileStatementAsync(caseNode.Body, cancellationToken);
 0348            cases.Add(new("Case", caseExpression, caseBody!));
 0349        }
 350
 0351        var defaultActivity = switchNode.Default != null ? await CompileStatementAsync(switchNode.Default, cancellationT
 352
 0353        return new Switch
 0354        {
 0355            Cases = cases,
 0356            Default = defaultActivity
 0357        };
 0358    }
 359
 360    private async Task<IActivity> CompileFlowchartAsync(FlowchartNode flowchart, CancellationToken cancellationToken = d
 361    {
 362        // Register flowchart-scoped variables
 6363        foreach (var varDecl in flowchart.Variables)
 364        {
 0365            CompileVariableDeclaration(varDecl);
 366        }
 367
 368        // Compile all labeled activities and build a label-to-activity map
 3369        var labelToActivity = new Dictionary<string, IActivity>();
 12370        foreach (var labeledNode in flowchart.Activities)
 371        {
 3372            var activity = await CompileStatementAsync(labeledNode.Activity, cancellationToken);
 3373            if (activity != null)
 374            {
 3375                labelToActivity[labeledNode.Label] = activity;
 376            }
 3377        }
 378
 379        // Create connections
 3380        var connections = new List<Connection>();
 8381        foreach (var connNode in flowchart.Connections)
 382        {
 1383            if (!labelToActivity.TryGetValue(connNode.Source, out var sourceActivity))
 0384                throw new InvalidOperationException($"Source label '{connNode.Source}' not found in flowchart");
 385
 1386            if (!labelToActivity.TryGetValue(connNode.Target, out var targetActivity))
 0387                throw new InvalidOperationException($"Target label '{connNode.Target}' not found in flowchart");
 388
 1389            var source = new Endpoint(sourceActivity, connNode.Outcome);
 1390            var target = new Endpoint(targetActivity);
 1391            connections.Add(new Connection(source, target));
 392        }
 393
 394        // Create flowchart activity
 3395        var flowchartActivity = new Workflows.Activities.Flowchart.Activities.Flowchart
 3396        {
 3397            Activities = labelToActivity.Values.ToList(),
 3398            Connections = connections
 3399        };
 400
 401        // Set entry point if specified
 3402        if (!string.IsNullOrEmpty(flowchart.EntryPoint))
 403        {
 2404            if (!labelToActivity.TryGetValue(flowchart.EntryPoint, out var startActivity))
 0405                throw new InvalidOperationException($"Entry point label '{flowchart.EntryPoint}' not found in flowchart"
 406
 2407            flowchartActivity.Start = startActivity;
 408        }
 409
 3410        return flowchartActivity;
 3411    }
 412
 413    private async Task<IActivity> CompileListenAsync(ListenNode listen, CancellationToken cancellationToken = default)
 414    {
 415        // Listen is just a regular activity invocation that can start a workflow
 1416        var activity = await CompileActivityInvocationAsync(listen.Activity, cancellationToken);
 417
 418        // Try to set CanStartWorkflow if the activity supports it
 1419        var canStartWorkflowProp = activity.GetType().GetProperty("CanStartWorkflow");
 1420        if (canStartWorkflowProp != null && canStartWorkflowProp.PropertyType == typeof(bool))
 421        {
 1422            canStartWorkflowProp.SetValue(activity, true);
 423        }
 424
 1425        return activity;
 1426    }
 427
 428    private Input<T> CompileExpressionAsInput<T>(ExpressionNode exprNode)
 429    {
 18430        if (exprNode is LiteralNode literal)
 431        {
 12432            return new(new Literal(literal.Value!));
 433        }
 434
 6435        if (exprNode is IdentifierNode identifier)
 436        {
 437            // Reference to a variable
 1438            if (_variables.TryGetValue(identifier.Name, out var variable))
 439            {
 1440                return new(variable);
 441            }
 442
 443            // If not found, treat as a literal
 0444            return new(new Literal<T>(default!));
 445        }
 446
 5447        if (exprNode is ElsaExpressionNode elsaExpr)
 448        {
 5449            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 5450            var expression = new Expression(language, elsaExpr.Expression);
 5451            return new(expression);
 452        }
 453
 0454        if (exprNode is ArrayLiteralNode arrayLiteral)
 455        {
 456            // For array literals, evaluate to a constant array if all elements are literals
 0457            var elements = arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 0458            return new((T)(object)elements);
 459        }
 460
 0461        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported");
 462    }
 463
 464    private Expression CompileExpressionAsExpression(ExpressionNode exprNode)
 465    {
 0466        if (exprNode is LiteralNode literal)
 467        {
 0468            return Expression.LiteralExpression(literal.Value);
 469        }
 470
 0471        if (exprNode is ElsaExpressionNode elsaExpr)
 472        {
 0473            var language = elsaExpr.Language != null ? MapLanguageName(elsaExpr.Language) : _defaultExpressionLanguage;
 0474            return new(language, elsaExpr.Expression);
 475        }
 476
 0477        throw new NotSupportedException($"Expression type {exprNode.GetType().Name} is not supported as Expression");
 478    }
 479
 480    private object CompileExpression(ExpressionNode exprNode, Type targetType)
 481    {
 482        // Check if targetType is already Input<T>
 483        Type innerType;
 12484        if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Input<>))
 485        {
 486            // Extract the T from Input<T>
 12487            innerType = targetType.GetGenericArguments()[0];
 488        }
 489        else
 490        {
 0491            innerType = targetType;
 492        }
 493
 494        // Use reflection to call CompileExpressionAsInput<T>
 12495        var method = GetType().GetMethod(nameof(CompileExpressionAsInput),
 12496            System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
 12497        var genericMethod = method!.MakeGenericMethod(innerType);
 498
 12499        return genericMethod.Invoke(this, [exprNode])!;
 500    }
 501
 502    private object? EvaluateConstantExpression(ExpressionNode exprNode)
 503    {
 4504        if (exprNode is LiteralNode literal)
 505        {
 4506            return literal.Value;
 507        }
 508
 0509        if (exprNode is ArrayLiteralNode arrayLiteral)
 510        {
 0511            return arrayLiteral.Elements.Select(EvaluateConstantExpression).ToArray();
 512        }
 513
 514        // For non-constant expressions, return null
 0515        return null;
 516    }
 517
 518    private IActivity InstantiateActivityUsingConstructor(Type activityType, List<ArgumentNode> positionalArgs)
 519    {
 520        // Get all public constructors
 12521        var constructors = activityType.GetConstructors(System.Reflection.BindingFlags.Public | System.Reflection.Bindin
 522
 523        // Filter constructors that:
 524        // 1. Have the same number of required Input<T> parameters as positional arguments (excluding optional params)
 525        // 2. All non-optional parameters are Input<T> types
 12526        var matchingConstructors = new List<(System.Reflection.ConstructorInfo ctor, System.Reflection.ParameterInfo[] i
 527
 176528        foreach (var ctor in constructors)
 529        {
 76530            var parameters = ctor.GetParameters();
 531
 532            // Filter to only Input<T> parameters that are not optional (don't have default values or CallerMemberName a
 76533            var inputParams = parameters.Where(p =>
 228534                p.ParameterType.IsGenericType &&
 228535                p.ParameterType.GetGenericTypeDefinition() == typeof(Input<>) &&
 228536                !p.IsOptional &&
 228537                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerFilePathAttribute), false).Any() &&
 228538                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerLineNumberAttribute), false).Any() &
 228539                !p.GetCustomAttributes(typeof(System.Runtime.CompilerServices.CallerMemberNameAttribute), false).Any()
 76540            ).ToArray();
 541
 542            // Check if the number of required Input<T> params matches our positional args
 76543            if (inputParams.Length == positionalArgs.Count)
 544            {
 12545                matchingConstructors.Add((ctor, inputParams));
 546            }
 547        }
 548
 12549        if (!matchingConstructors.Any())
 550        {
 0551            throw new InvalidOperationException(
 0552                $"No matching constructor found for activity type '{activityType.Name}' with {positionalArgs.Count} posi
 0553                $"Constructors must have Input<T> parameters matching the number of positional arguments.");
 554        }
 555
 12556        if (matchingConstructors.Count > 1)
 557        {
 0558            throw new InvalidOperationException(
 0559                $"Multiple matching constructors found for activity type '{activityType.Name}' with {positionalArgs.Coun
 0560                $"Please use named arguments to disambiguate.");
 561        }
 562
 12563        var (selectedCtor, selectedInputParams) = matchingConstructors[0];
 564
 565        // Build the constructor arguments
 12566        var ctorArgs = new List<object?>();
 12567        var allParams = selectedCtor.GetParameters();
 568
 96569        foreach (var param in allParams)
 570        {
 571            // Check if this is one of our Input<T> parameters
 36572            var inputParamIndex = Array.IndexOf(selectedInputParams, param);
 573
 36574            if (inputParamIndex >= 0)
 575            {
 576                // This is an Input<T> parameter - compile the corresponding positional argument
 12577                var arg = positionalArgs[inputParamIndex];
 12578                var value = CompileExpression(arg.Value, param.ParameterType);
 12579                ctorArgs.Add(value);
 580            }
 24581            else if (param.IsOptional)
 582            {
 583                // This is an optional parameter (like CallerFilePath) - use its default value
 24584                ctorArgs.Add(param.DefaultValue);
 585            }
 586            else
 587            {
 588                // This shouldn't happen if our filtering is correct
 0589                throw new InvalidOperationException(
 0590                    $"Unexpected non-optional, non-Input<T> parameter '{param.Name}' in constructor for '{activityType.N
 591            }
 592        }
 593
 594        // Instantiate the activity using the constructor
 12595        var activity = (IActivity)selectedCtor.Invoke(ctorArgs.ToArray());
 12596        return activity;
 597    }
 598
 599    private static string MapLanguageName(string dslLanguage) =>
 12600        LanguageMappings.TryGetValue(dslLanguage, out var mapped) ? mapped : dslLanguage;
 601}