protected ArgumentValue GetValueFromNamedArgument(TemplateContext context, ScriptNode callerContext, ScriptNamedArgument namedArg) { for (int j = 0; j < Parameters.Length; j++) { var arg = Parameters[j]; if (arg.Name == namedArg.Name) { return(new ArgumentValue(j, arg.ParameterType, context.Evaluate(namedArg))); } } throw new ScriptRuntimeException(callerContext.Span, $"Invalid argument `{namedArg.Name}` not found for function `{callerContext}`"); }
protected object GetNamedArgument(TemplateContext context, ScriptNode callerContext, ScriptNamedArgument namedArg, out int argIndex, out Type argType) { for (int j = 0; j < Parameters.Length; j++) { var arg = Parameters[j]; if (arg.Name == namedArg.Name) { argIndex = j; argType = arg.ParameterType; return(context.Evaluate(namedArg)); } } throw new ScriptRuntimeException(callerContext.Span, $"Invalid argument `{namedArg.Name}` not found for function `{callerContext}`"); }
protected object GetNamedArgument(TemplateContext context, ScriptNode callerContext, ScriptNamedArgument namedArg, out int argIndex, out Type argType) { for (int j = 0; j < Parameters.Length; j++) { ParameterInfo arg = Parameters[j]; if (arg.Name == namedArg.Name) { argIndex = j; argType = arg.ParameterType; return(context.Evaluate(namedArg)); } } throw new ScriptRuntimeException(callerContext.Span, string.Format(RS.ArgNotFoundForFunc, namedArg.Name, callerContext)); }
private ScriptExpressionStatement ParseLiquidCycleStatement() { ScriptExpressionStatement statement = Open <ScriptExpressionStatement>(); ScriptFunctionCall functionCall = Open <ScriptFunctionCall>(); statement.Expression = functionCall; functionCall.Target = ParseVariable(); if (Options.ConvertLiquidFunctions) { TransformFromLiquidFunctionCall(functionCall); } ScriptArrayInitializerExpression arrayInit = null; // Parse cycle without group: cycle "a", "b", "c" => transform to textscript: array.cycle ["a", "b", "c"] // Parse cycle with group: cycle "group1": "a", "b", "c" => transform to textscript: array.cycle ["a", "b", "c"] "group1" bool isFirst = true; while (IsVariableOrLiteral(Current)) { ScriptExpression value = ParseVariableOrLiteral(); if (isFirst && Current.Type == TokenType.Colon) { NextToken(); // Skip : ScriptNamedArgument namedArg = Open <ScriptNamedArgument>(); namedArg.Name = "group"; namedArg.Value = value; Close(namedArg); namedArg.Span = value.Span; isFirst = false; functionCall.Arguments.Add(namedArg); continue; } if (arrayInit == null) { arrayInit = Open <ScriptArrayInitializerExpression>(); functionCall.Arguments.Insert(0, arrayInit); arrayInit.Span.Start = value.Span.Start; } arrayInit.Values.Add(value); arrayInit.Span.End = value.Span.End; if (Current.Type == TokenType.Comma) { NextToken(); } else if (Current.Type == TokenType.LiquidTagExit) { break; } else { LogError(Current, string.Format(RS.UnexpectedTokenAfterCycle, GetAsText(Current), value)); NextToken(); break; } } Close(functionCall); ExpectEndOfStatement(statement); return(Close(statement)); }
private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0, ParseExpressionMode mode = ParseExpressionMode.Default) { int expressionCount = 0; _expressionLevel++; int expressionDepthBeforeEntering = _expressionDepth; EnterExpression(); try { ScriptFunctionCall functionCall = null; parseExpression: expressionCount++; ScriptExpression leftOperand = null; switch (Current.Type) { case TokenType.Identifier: case TokenType.IdentifierSpecial: leftOperand = ParseVariable(); // In case of liquid template, we accept the syntax colon after a tag if (_isLiquid && parentNode is ScriptPipeCall && Current.Type == TokenType.Colon) { NextToken(); } // Special handle of the $$ block delegate variable if (ScriptVariable.BlockDelegate.Equals(leftOperand)) { if (expressionCount != 1 || _expressionLevel > 1) { LogError(RS.DelegateBlockInNestedExpr); } if (!(parentNode is ScriptExpressionStatement)) { LogError(parentNode, RS.DelegateBlockOutsideExpr); } return(leftOperand); } break; case TokenType.Integer: leftOperand = ParseInteger(); break; case TokenType.Float: leftOperand = ParseFloat(); break; case TokenType.String: leftOperand = ParseString(); break; case TokenType.ImplicitString: leftOperand = ParseImplicitString(); break; case TokenType.VerbatimString: leftOperand = ParseVerbatimString(); break; case TokenType.OpenParent: leftOperand = ParseParenthesis(ref hasAnonymousFunction); break; case TokenType.OpenBrace: leftOperand = ParseObjectInitializer(); break; case TokenType.OpenBracket: leftOperand = ParseArrayInitializer(); break; case TokenType.Not: case TokenType.Minus: case TokenType.Arroba: case TokenType.Plus: case TokenType.Caret: leftOperand = ParseUnaryExpression(ref hasAnonymousFunction); break; } // Should not happen but in case if (leftOperand == null) { if (functionCall != null) { LogError(string.Format(RS.UnexpectedTokenInFunc, GetAsText(Current), functionCall)); } else { LogError(string.Format(RS.UnexpectedTokenInExpr, GetAsText(Current))); } return(null); } if (leftOperand is ScriptAnonymousFunction) { hasAnonymousFunction = true; } while (!hasAnonymousFunction) { if (_isLiquid && Current.Type == TokenType.Comma && functionCall != null) { NextToken(); // Skip the comma for arguments in a function call } // Parse Member expression are expected to be followed only by an identifier if (Current.Type == TokenType.Dot) { Token nextToken = PeekToken(); if (nextToken.Type == TokenType.Identifier) { NextToken(); if (GetAsText(Current) == "empty" && PeekToken().Type == TokenType.Question) { ScriptIsEmptyExpression memberExpression = Open <ScriptIsEmptyExpression>(); NextToken(); // skip empty NextToken(); // skip ? memberExpression.Target = leftOperand; leftOperand = Close(memberExpression); } else { ScriptMemberExpression memberExpression = Open <ScriptMemberExpression>(); memberExpression.Target = leftOperand; ScriptExpression member = ParseVariable(); if (!(member is ScriptVariable)) { LogError(string.Format(RS.UnexpectedLiteralMember, member)); return(null); } memberExpression.Member = (ScriptVariable)member; leftOperand = Close(memberExpression); } } else { LogError(nextToken, string.Format(RS.InvalidTokenAfterDot, nextToken.Type)); return(null); } continue; } // If we have a bracket but left operand is a (variable || member || indexer), then we consider next as an indexer // unit test: 130-indexer-accessor-accept1.txt if (Current.Type == TokenType.OpenBracket && leftOperand is IScriptVariablePath && !IsPreviousCharWhitespace()) { NextToken(); ScriptIndexerExpression indexerExpression = Open <ScriptIndexerExpression>(); indexerExpression.Target = leftOperand; // unit test: 130-indexer-accessor-error5.txt indexerExpression.Index = ExpectAndParseExpression(indexerExpression, ref hasAnonymousFunction, functionCall, 0, string.Format(RS.ExpectToken, "index_expression", Current.Type)); if (Current.Type != TokenType.CloseBracket) { LogError(string.Format(RS.ExpectToken, "]", Current.Type)); } else { NextToken(); } leftOperand = Close(indexerExpression); continue; } if (mode == ParseExpressionMode.BasicExpression) { break; } if (Current.Type == TokenType.Equal) { ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>(); if (_expressionLevel > 1) { // unit test: 101-assign-complex-error1.txt LogError(assignExpression, RS.ExprForTopLevelAssignmentOnly); } NextToken(); assignExpression.Target = TransformKeyword(leftOperand); // unit test: 105-assign-error3.txt assignExpression.Value = ExpectAndParseExpression(assignExpression, ref hasAnonymousFunction, parentExpression); leftOperand = Close(assignExpression); continue; } // Handle binary operators here ScriptBinaryOperator binaryOperatorType; if (BinaryOperators.TryGetValue(Current.Type, out binaryOperatorType) || (_isLiquid && TryLiquidBinaryOperator(out binaryOperatorType))) { int newPrecedence = GetOperatorPrecedence(binaryOperatorType); // Check precedence to see if we should "take" this operator here (Thanks TimJones for the tip code! ;) if (newPrecedence < precedence) { break; } // We fake entering an expression here to limit the number of expression EnterExpression(); ScriptBinaryExpression binaryExpression = Open <ScriptBinaryExpression>(); binaryExpression.Left = leftOperand; binaryExpression.Operator = binaryOperatorType; NextToken(); // skip the operator // unit test: 110-binary-simple-error1.txt binaryExpression.Right = ExpectAndParseExpression(binaryExpression, ref hasAnonymousFunction, functionCall ?? parentExpression, newPrecedence, string.Format(RS.ExpectTokenInRightOperator, "expression", Current.Type)); leftOperand = Close(binaryExpression); continue; } if (precedence > 0) { break; } if (StartAsExpression()) { // If we can parse a statement, we have a method call if (parentExpression != null) { break; } // Parse named parameters var paramContainer = parentNode as IScriptNamedArgumentContainer; if (Current.Type == TokenType.Identifier && (parentNode is IScriptNamedArgumentContainer || !_isLiquid && PeekToken().Type == TokenType.Colon)) { if (paramContainer == null) { if (functionCall == null) { functionCall = Open <ScriptFunctionCall>(); functionCall.Target = leftOperand; functionCall.Span.Start = leftOperand.Span.Start; } else { functionCall.Arguments.Add(leftOperand); } Close(leftOperand); } while (true) { if (Current.Type != TokenType.Identifier) { break; } ScriptNamedArgument parameter = Open <ScriptNamedArgument>(); string parameterName = GetAsText(Current); parameter.Name = parameterName; // Skip argument name NextToken(); if (paramContainer != null) { paramContainer.AddParameter(Close(parameter)); } else { functionCall.Arguments.Add(parameter); } // If we have a colon, we have a value // otherwise it is a boolean argument name if (Current.Type == TokenType.Colon) { NextToken(); parameter.Value = ExpectAndParseExpression(parentNode, mode: ParseExpressionMode.BasicExpression); parameter.Span.End = parameter.Value.Span.End; } if (functionCall != null) { functionCall.Span.End = parameter.Span.End; } } // As we have handled leftOperand here, we don't let the function out of this while to pick up the leftOperand if (functionCall != null) { leftOperand = functionCall; functionCall = null; } // We don't allow anything after named parameters break; } if (functionCall == null) { functionCall = Open <ScriptFunctionCall>(); functionCall.Target = leftOperand; // If we need to convert liquid to textscript functions: if (_isLiquid && Options.ConvertLiquidFunctions) { TransformFromLiquidFunctionCall(functionCall); } functionCall.Span.Start = leftOperand.Span.Start; } else { functionCall.Arguments.Add(leftOperand); } goto parseExpression; } if (Current.Type == TokenType.Pipe) { if (functionCall != null) { functionCall.Arguments.Add(leftOperand); leftOperand = functionCall; } ScriptPipeCall pipeCall = Open <ScriptPipeCall>(); pipeCall.From = leftOperand; NextToken(); // skip | // unit test: 310-func-pipe-error1.txt pipeCall.To = ExpectAndParseExpression(pipeCall, ref hasAnonymousFunction); return(Close(pipeCall)); } break; } if (functionCall != null) { functionCall.Arguments.Add(leftOperand); functionCall.Span.End = leftOperand.Span.End; return(functionCall); } return(Close(leftOperand)); } finally { LeaveExpression(); // Force to restore back to a level _expressionDepth = expressionDepthBeforeEntering; _expressionLevel--; } }