public Expression <Action <StringBuilder, T, Y> > Generate <T, Y>(ExpressContext expressContext, ScriptBlockStatement scriptBlockStatement)
        {
            ParameterExpression StringBuilderParmeter = Expression.Parameter(typeof(StringBuilder));
            ParameterExpression inputParameter        = Expression.Parameter(typeof(T), "Model");
            ParameterExpression libraryParameter      = Expression.Parameter(typeof(Y), "Library");

            ParameterFinder parameterFinder = new ParameterFinder();

            parameterFinder.AddParameter(libraryParameter);
            parameterFinder.AddParameter(inputParameter);

            var blockExpression = GetStatementExpression(expressContext, StringBuilderParmeter, scriptBlockStatement, parameterFinder);

            return(Expression.Lambda <Action <StringBuilder, T, Y> >(blockExpression, StringBuilderParmeter, inputParameter, libraryParameter));
        }
        public Expression GetStatementExpression(ExpressContext expressContext, ParameterExpression stringBuilderParameter, ScriptStatement scriptStatement, ParameterFinder parameterFinder)
        {
            var appendMethodInfo = typeof(StringBuilder).GetMethod("Append", new[] { typeof(string) });

            switch (scriptStatement)
            {
            case ScriptRawStatement scriptRawStatement:
                var constant   = Expression.Constant(scriptRawStatement.ToString());
                var methodCall = Expression.Call(stringBuilderParameter, appendMethodInfo, constant);
                return(methodCall);

            case ScriptExpressionStatement scriptExpressionStatement:
                var expressionBody = expressionGenerator.GetExpressionBody(scriptExpressionStatement.Expression, parameterFinder, null);
                expressionBody = AddToString(expressionBody);
                var scriptmethodCall = Expression.Call(stringBuilderParameter, appendMethodInfo, expressionBody);
                return(scriptmethodCall);

            case ScriptIfStatement scriptIfStatement:
                var predicateExpression = expressionGenerator.GetExpressionBody(scriptIfStatement.Condition, parameterFinder, null);
                var trueStatementBlock  = GetStatementExpression(expressContext, stringBuilderParameter, scriptIfStatement.Then, parameterFinder);

                if (scriptIfStatement.Else != null)
                {
                    var elseStatment = GetStatementExpression(expressContext, stringBuilderParameter, scriptIfStatement.Else, parameterFinder);
                    ConditionalExpression ifThenElseExpr = Expression.IfThenElse(predicateExpression, trueStatementBlock, elseStatment);
                    return(ifThenElseExpr);
                }
                else
                {
                    ConditionalExpression ifThenExpr = Expression.IfThen(predicateExpression, trueStatementBlock);
                    return(ifThenExpr);
                }

            case ScriptElseStatement scriptElseStatement:
                var elseStatmentExpression = GetStatementExpression(expressContext, stringBuilderParameter, scriptElseStatement.Body, parameterFinder);
                return(elseStatmentExpression);

            case ScriptBlockStatement scriptBlockStatement:
                List <Expression> statements = new List <Expression>();
                foreach (var statement in scriptBlockStatement.Statements)
                {
                    try
                    {
                        statements.Add(GetStatementExpression(expressContext, stringBuilderParameter, statement, parameterFinder));
                    }
                    catch (SpanException ex)
                    {
                        logger.LogError(ex, "Failed to build: {statement}", statement);
                        expressContext.Messages.Add(new LogMessage(ParserMessageType.Error, ex.Span, ex.Message));
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, "Statement Failed to build: {statement}", statement);
                        expressContext.Messages.Add(new LogMessage(ParserMessageType.Error, scriptBlockStatement.Span, $"Statement Failed to build: {statement?.GetType()}"));
                    }
                }
                var blockExpression = Expression.Block(statements);
                return(blockExpression);

            case ScriptForStatement scriptForStatement:
                // foreach(item in items)
                var itemsExpression              = expressionGenerator.GetExpressionBody(scriptForStatement.Iterator, parameterFinder, null);
                var itemVariableName             = (scriptForStatement.Variable as ScriptVariableGlobal).Name;
                var itemType                     = itemsExpression.Type.GenericTypeArguments[0];
                ParameterExpression itemVariable = Expression.Parameter(itemType, itemVariableName);

                using (var scopedParameterFinder = parameterFinder.CreateScope())
                {
                    scopedParameterFinder.AddLocalVariable(itemVariable);
                    var body = GetStatementExpression(expressContext, stringBuilderParameter, scriptForStatement.Body, scopedParameterFinder);
                    var foreachExpression = ExpressionHelpers.ForEach(itemsExpression, itemVariable, body);
                    return(foreachExpression);
                }

            default:
                throw new NotImplementedException("Unknown ScriptStatement");
            }
        }
        public Expression GetExpressionBody(ScriptExpression scriptExpression, ParameterFinder parameterFinder, List <Expression> arguments)
        {
            switch (scriptExpression)
            {
            case ScriptVariableGlobal scriptVariableGlobal:
                var variable = parameterFinder.GetProperty(scriptVariableGlobal.Name);
                if (variable == null)
                {
                    throw new SpanException($"Variable Not Found: {scriptVariableGlobal.Name}", scriptVariableGlobal.Span);
                }
                return(variable);

            case ScriptLiteral scriptLiteral:
                return(Expression.Constant(scriptLiteral.Value, scriptLiteral.Value.GetType()));

            case ScriptUnaryExpression scriptUnaryExpression:
                switch (scriptUnaryExpression.Operator)
                {
                case ScriptUnaryOperator.Not:
                    var right = GetExpressionBody(scriptUnaryExpression.Right, parameterFinder, arguments);
                    return(Expression.Not(right));

                default:
                    throw new SpanException($"Unknown ScriptUnaryOperator: {scriptUnaryExpression.Operator}", scriptUnaryExpression.Span);
                }

            case ScriptMemberExpression scriptMemberExpression:
                // it's impossible to tell if we have a member or a method, so we check for both
                var memberTarget = GetExpressionBody(scriptMemberExpression.Target, parameterFinder, null);
                var memberName   = scriptMemberExpression.Member.Name;

                // TODO: we should remove the need to calculate a method with Args here, should not need to pass down info
                // still need the argument list as ScriptPipeCall still needs to pass the args in
                var argumentTypeList = arguments.ToNullSafe().Select(e => e.Type);

                var methodInfo = memberFinder.FindMember(memberTarget.Type, memberName, argumentTypeList);
                if (methodInfo != null)
                {
                    var convertedArgs = ConvertArgs(methodInfo as MethodInfo, arguments);
                    return(ExpressionHelpers.CallMember(memberTarget, methodInfo, convertedArgs));
                }
                throw new SpanException($"Member Not Found: {memberName}", scriptMemberExpression.Span);

            case ScriptPipeCall scriptPipeCall:
                // I'm not a huge fan of this because it requires pushing args down to sub nodes, could cause issues with multi funtions tree
                var fromExpression = GetExpressionBody(scriptPipeCall.From, parameterFinder, null);
                // prepare args (input type + ScriptFunctionCall args )
                var pipelineArgs = new List <Expression> {
                    fromExpression
                };

                switch (scriptPipeCall.To)
                {
                case ScriptFunctionCall scriptFunctionCall:
                    pipelineArgs.AddRange(scriptFunctionCall.Arguments.Select(arg => GetExpressionBody(arg, parameterFinder, null)));
                    return(GetExpressionBody(scriptFunctionCall.Target, parameterFinder, pipelineArgs));

                case ScriptMemberExpression scriptMemberExpression:
                    return(GetExpressionBody(scriptMemberExpression, parameterFinder, pipelineArgs));

                case ScriptPipeCall toScriptPipeCall:
                    var nestedfromExpression = GetExpressionBody(toScriptPipeCall.From, parameterFinder, pipelineArgs);
                    return(GetExpressionBody(toScriptPipeCall.To, parameterFinder, new List <Expression> {
                        nestedfromExpression
                    }));

                default:
                    throw new NotSupportedException("Pipeline Expression Not Supported");
                }

            case ScriptFunctionCall scriptFunctionCall:
                return(CalculateScriptFunctionCall(scriptFunctionCall, parameterFinder, arguments));

            case ScriptNestedExpression scriptNestedExpression:
                return(GetExpressionBody(scriptNestedExpression.Expression, parameterFinder, arguments));

            case ScriptBinaryExpression scriptBinaryExpression:
                var leftExpression  = GetExpressionBody(scriptBinaryExpression.Left, parameterFinder, null);
                var rightExpression = GetExpressionBody(scriptBinaryExpression.Right, parameterFinder, null);

                switch (scriptBinaryExpression.Operator)
                {
                case ScriptBinaryOperator.Add:
                    leftExpression  = ConvertIfNeeded(leftExpression, rightExpression.Type);
                    rightExpression = ConvertIfNeeded(rightExpression, leftExpression.Type);
                    return(Expression.Add(leftExpression, rightExpression));

                case ScriptBinaryOperator.EmptyCoalescing:
                    return(Expression.Coalesce(leftExpression, rightExpression));

                case ScriptBinaryOperator.CompareEqual:
                    return(Expression.Equal(leftExpression, rightExpression));

                default:
                    throw new SpanException($"Unknown ScriptBinaryExpression Operator: {scriptBinaryExpression.Operator}", scriptBinaryExpression.Span);
                }

            case ScriptIndexerExpression scriptIndexerExpression:
                //https://stackoverflow.com/questions/31924907/accessing-elements-of-types-with-indexers-using-expression-trees
                // TODO: consider wrapping an enumerable to an array
                var arrayTarget = GetExpressionBody(scriptIndexerExpression.Target, parameterFinder, null);
                var arrayIndex  = GetExpressionBody(scriptIndexerExpression.Index, parameterFinder, null);
                var indexed     = Expression.Property(arrayTarget, "Item", arrayIndex);
                return(indexed);

            default:
                throw new SpanException($"Unknown Expression Type: {scriptExpression?.GetType()}", scriptExpression.Span);
            }
        }
Example #4
0
 private ParameterFinder(ParameterFinder parent) : this()
 {
     this.parentFinder = parent;
 }
        public Expression CalculateScriptFunctionCall(ScriptFunctionCall scriptFunctionCall, ParameterFinder parameterFinder, List <Expression> arguments)
        {
            var args = scriptFunctionCall.Arguments.Select(arg => GetExpressionBody(arg, parameterFinder, null));

            //add first argument if it's being supplied (probably from ScriptPipeCall)
            if (arguments != null)
            {
                args = arguments.Union(args);
            }

            // we are attempting to pull the bottom target member up, so we know the method Name
            var toMember = scriptFunctionCall.Target as ScriptMemberExpression;

            if (toMember == null)
            {
                throw new NotSupportedException();
            }
            else
            {
                var            argumentTypes      = args.ToNullSafe().Select(e => e.Type);
                var            targetType         = GetExpressionBody(toMember.Target, parameterFinder, null);
                ScriptVariable functionNameScript = toMember.Member;
                var            methodInfo         = memberFinder.FindMember(targetType.Type, functionNameScript.Name, argumentTypes.ToArray());
                var            convertedArgs      = ConvertArgs(methodInfo as MethodInfo, args);
                return(ExpressionHelpers.CallMember(targetType, methodInfo, convertedArgs));
            }
        }