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)); } }
/// <summary> /// The evaluates a string as a scriban expression or evaluate the passed function or return the passed value. /// </summary> /// <param name="context">The template context</param> /// <param name="span">The source span</param> /// <param name="value">The input value, either a scriban template in a string, or an alias function or directly a value.</param> /// <returns>The evaluation of the input value.</returns> /// <remarks> /// ```scriban-html /// {{ "1 + 2" | object.eval }} /// ``` /// ```html /// 3 /// ``` /// </remarks> public static object Eval(TemplateContext context, SourceSpan span, object value) { if (value == null) { return(null); } if (value is string templateStr) { try { var template = Template.Parse(templateStr, lexerOptions: new LexerOptions() { Lang = context.Language, Mode = ScriptMode.ScriptOnly }); return(context.Evaluate(template.Page)); } catch (Exception ex) { throw new ArgumentException(ex.Message, nameof(value)); } } if (value is IScriptCustomFunction function) { return(ScriptFunctionCall.Call(context, context.CurrentNode, function, false, null)); } return(value); }
private void TransformLiquidFunctionCallToScriban(ScriptFunctionCall functionCall) { var liquidTarget = functionCall.Target as ScriptVariable; string targetName; string memberName; // In case of cycle we transform it to array.cycle at runtime if (liquidTarget != null && LiquidBuiltinsFunctions.TryLiquidToScriban(liquidTarget.Name, out targetName, out memberName)) { var arrayCycle = new ScriptMemberExpression { Span = liquidTarget.Span, Target = new ScriptVariableGlobal(targetName) { Span = liquidTarget.Span }, Member = new ScriptVariableGlobal(memberName) { Span = liquidTarget.Span }, }; // Transfer trivias accordingly to target (trivias before) and member (trivias after) if (_isKeepTrivia && liquidTarget.Trivias != null) { arrayCycle.Target.AddTrivias(liquidTarget.Trivias.Before, true); arrayCycle.Member.AddTrivias(liquidTarget.Trivias.After, false); } functionCall.Target = arrayCycle; } }
private static IEnumerable EachInternal(TemplateContext context, ScriptNode callerContext, SourceSpan span, IEnumerable list, IScriptCustomFunction function, Type destType) { var arg = new ScriptArray(1); foreach (var item in list) { var itemToTransform = context.ToObject(span, item, destType); arg[0] = itemToTransform; var itemTransformed = ScriptFunctionCall.Call(context, callerContext, function, arg); yield return(itemTransformed); } }
public object Invoke(TemplateContext context, ScriptNode callerContext, ScriptArray arguments, ScriptBlockStatement blockStatement) { if (arguments == null || arguments.Count == 0) { throw new ScriptRuntimeException(callerContext.Span, string.Format(RS.BadFunctionInvokeArgEmpty, "where")); } if (arguments.Count == 1) { return(arguments[0]); } // the second argument is a condition ScriptFunctionCall funcCall = (ScriptFunctionCall)callerContext; ScriptExpression condition = funcCall.Arguments[0]; // hold the result in an array ScriptArray result = new ScriptArray(); // the first argument doesn't have to be an array try { // string is enumerable but we want to treat it as a whole if (arguments[0] is string) { throw new InvalidCastException(); } IEnumerable array = (IEnumerable)arguments[0]; foreach (object item in array) { if ((bool)ReplaceCondition(condition, item).Evaluate(context)) { result.Add(item); } } } catch (InvalidCastException) { if ((bool)ReplaceCondition(condition, arguments[0]).Evaluate(context)) { return(arguments[0]); } } catch (Exception ex) { throw new ScriptParserRuntimeException(callerContext.Span, string.Format("RS.FilterFunctionException"), null, ex); } return(result); }
static IEnumerable FilterInternal(TemplateContext context, SourceSpan span, IEnumerable list, IScriptCustomFunction function, Type destType) { var arg = new ScriptArray(1); foreach (var item in list) { var itemToTransform = context.ToObject(span, item, destType); arg[0] = itemToTransform; var itemTransformed = ScriptFunctionCall.Call(context, context.CurrentNode, function, arg); if (context.ToBool(span, itemTransformed)) { yield return(itemToTransform); } } }
/// <summary> /// Parses the referenced include templates from a template /// </summary> /// <param name="template">Template to parse</param> /// <returns>List of referenced include</returns> private async Task <List <IncludeExportTemplateReference> > ParseIncludeTemplates(ExportTemplate template) { Template parsedTemplate = Template.Parse(template.Code); List <IncludeExportTemplateReference> includeTemplateRefs = new List <IncludeExportTemplateReference>(); List <ScriptStatement> statementsToCheck = ScribanStatementExtractor.ExtractPlainNonTextStatements(parsedTemplate); foreach (ScriptStatement curStatement in statementsToCheck) { if (!(curStatement is ScriptExpressionStatement)) { continue; } ScriptExpressionStatement expressionStatement = (ScriptExpressionStatement)curStatement; if (expressionStatement.Expression == null || !(expressionStatement.Expression is ScriptFunctionCall)) { continue; } ScriptFunctionCall functionCall = (ScriptFunctionCall)expressionStatement.Expression; if (functionCall.Target == null || functionCall.Target.ToString() != "include") { continue; } if (functionCall.Arguments.Count < 1) { continue; } string templateName = functionCall.Arguments[0].ToString().Trim('\"').Trim('\''); IncludeExportTemplate existingTemplate = await _cachedDbAccess.GetIncludeTemplateByName(template.ProjectId, templateName); if (existingTemplate != null && !includeTemplateRefs.Any(e => e.IncludeTemplateId == existingTemplate.Id)) { includeTemplateRefs.Add(new IncludeExportTemplateReference { IncludeTemplateId = existingTemplate.Id, Name = existingTemplate.Name }); } } return(includeTemplateRefs); }
/// <summary> /// Evaluates the specified expression /// </summary> /// <param name="targetExpression">The expression to evaluate</param> /// <param name="valueToSet">A value to set in case of a setter</param> /// <param name="setter">true if this a setter</param> /// <returns>The value of the targetExpression</returns> private object GetOrSetValue(ScriptExpression targetExpression, object valueToSet, bool setter) { object value = null; try { if (targetExpression is IScriptVariablePath nextPath) { if (setter) { nextPath.SetValue(this, valueToSet); } else { value = nextPath.GetValue(this); } } else if (!setter) { value = Evaluate(targetExpression); } else { throw new ScriptRuntimeException(targetExpression.Span, $"Unsupported target expression for assignment."); // unit test: 105-assign-error1.txt } } catch (Exception readonlyException) when(_getOrSetValueLevel == 1 && !(readonlyException is ScriptRuntimeException)) { throw new ScriptRuntimeException(targetExpression.Span, $"Unexpected exception while accessing target expression: {readonlyException.Message}", readonlyException); } // If the variable being returned is a function, we need to evaluate it // If function call is disabled, it will be only when returning the final object (level 0 of recursion) var allowFunctionCall = (_isFunctionCallDisabled && _getOrSetValueLevel > 1) || !_isFunctionCallDisabled; if (allowFunctionCall && ScriptFunctionCall.IsFunction(value)) { // Allow to pipe arguments only for top level returned function value = ScriptFunctionCall.Call(this, targetExpression, value, _getOrSetValueLevel == 1, null); } return(value); }
/// <summary> /// Returns a language key /// </summary> /// <param name="templateContext">Template context</param> /// <param name="callerContext">Call context</param> /// <param name="arguments">Arguments</param> /// <returns>Language key</returns> private async ValueTask <object> GetLanguageKey(TemplateContext templateContext, ScriptNode callerContext, ScriptArray arguments) { ScriptFunctionCall functionCall = callerContext as ScriptFunctionCall; if (functionCall == null) { _errorCollection.AddCanNotGenerateLanguageKey(ScribanErrorUtil.FormatScribanSpan(callerContext.Span)); return("<<USE NON PIPE NOTATION FOR LANGKEY>>"); } if (functionCall.Arguments.Count != 1 || arguments.Count != 1) { _errorCollection.AddCanNotGenerateLanguageKey(ScribanErrorUtil.FormatScribanSpan(callerContext.Span)); return("<<PASS IN EXACTLY ONE ARGUMENT FOR LANGKEY>>"); } List <string> callHierarchy = ScribanParsingUtil.GetCallerHierarchy(functionCall.Arguments[0]); return(await GenerateLanguageKeyFromCallHierarchy(templateContext, callerContext, callHierarchy)); }
/// <summary> /// The evaluates a string as a scriban template or evaluate the passed function or return the passed value. /// </summary> /// <param name="context">The template context</param> /// <param name="span">The source span</param> /// <param name="value">The input value, either a scriban template in a string, or an alias function or directly a value.</param> /// <returns>The evaluation of the input value.</returns> /// <remarks> /// ```scriban-html /// {{ "This is a template text {{ 1 + 2 }}" | object.eval_template }} /// ``` /// ```html /// This is a template text 3 /// ``` /// </remarks> public static object EvalTemplate(TemplateContext context, SourceSpan span, object value) { if (value == null) { return(null); } if (value is string templateStr) { try { var template = Template.Parse(templateStr, lexerOptions: new LexerOptions() { Lang = context.Language, Mode = ScriptMode.Default }); var output = new StringBuilderOutput(); context.PushOutput(output); try { context.Evaluate(template.Page); } finally { context.PopOutput(); } return(output.ToString()); } catch (Exception ex) { throw new ArgumentException(ex.Message, nameof(value)); } } if (value is IScriptCustomFunction function) { return(ScriptFunctionCall.Call(context, context.CurrentNode, function, false, null)); } return(value); }
public static string Join(TemplateContext context, SourceSpan span, IEnumerable list, string delimiter, object function = null) { if (list == null) { return(string.Empty); } var scriptingFunction = function as IScriptCustomFunction; if (function != null && scriptingFunction == null) { throw new ArgumentException($"The parameter `{function}` is not a function. Maybe prefix it with @?", nameof(function)); } var text = new StringBuilder(); bool afterFirst = false; var arg = new ScriptArray(1); foreach (var obj in list) { if (afterFirst) { text.Append(delimiter); } var item = context.ObjectToString(obj); if (scriptingFunction != null) { arg[0] = item; var result = ScriptFunctionCall.Call(context, context.CurrentNode, scriptingFunction, arg); item = context.ObjectToString(result); } text.Append(item); afterFirst = true; } return(text.ToString()); }
public override void Evaluate(TemplateContext context) { // Check that the Target is actually a function var functionCall = Target as ScriptFunctionCall; if (functionCall == null) { var parameterLessFunction = context.Evaluate(Target, true); if (!(parameterLessFunction is IScriptCustomFunction)) { var targetPrettyname = ScriptSyntaxAttribute.Get(Target); throw new ScriptRuntimeException(Target.Span, $"Expecting a direct function instead of the expression [{Target}/{targetPrettyname.Name}]"); } context.BlockDelegates.Push(Body); context.Result = ScriptFunctionCall.Call(context, this, parameterLessFunction); } else { context.BlockDelegates.Push(Body); context.Result = context.Evaluate(functionCall); } }
private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0) { int expressionCount = 0; expressionLevel++; try { ScriptFunctionCall functionCall = null; parseExpression: expressionCount++; ScriptExpression leftOperand = null; switch (Current.Type) { case TokenType.Identifier: case TokenType.IdentifierSpecial: leftOperand = ParseVariableOrLiteral(); // Special handle of the $$ block delegate variable if (ScriptVariable.BlockDelegate.Equals(leftOperand)) { if (expressionCount != 1 || expressionLevel > 1) { LogError("Cannot use block delegate $$ in a nested expression"); } if (!(parentNode is ScriptExpressionStatement)) { LogError(parentNode, "Cannot use block delegate $$ outside an expression statement"); } return(leftOperand); } break; case TokenType.Integer: leftOperand = ParseInteger(); break; case TokenType.Float: leftOperand = ParseFloat(); break; case TokenType.String: leftOperand = ParseString(); 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) { LogError($"Unexpected token [{Current.Type}] for expression"); return(null); } if (leftOperand is ScriptAnonymousFunction) { hasAnonymousFunction = true; } while (!hasAnonymousFunction) { // Parse Member expression are expected to be followed only by an identifier if (Current.Type == TokenType.Dot) { var nextToken = PeekToken(); if (nextToken.Type == TokenType.Identifier) { NextToken(); var memberExpression = Open <ScriptMemberExpression>(); memberExpression.Target = leftOperand; var member = ParseVariableOrLiteral(); if (!(member is ScriptVariable)) { LogError("Unexpected literal member [{member}]"); return(null); } memberExpression.Member = (ScriptVariable)member; leftOperand = Close(memberExpression); } else { LogError(nextToken, $"Invalid token [{nextToken.Type}]. The dot operator is expected to be followed by a plain identifier"); 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 ScriptVariablePath && !IsPreviousCharWhitespace()) { NextToken(); var indexerExpression = Open <ScriptIndexerExpression>(); indexerExpression.Target = leftOperand; // unit test: 130-indexer-accessor-error5.txt indexerExpression.Index = ExpectAndParseExpression(indexerExpression, ref hasAnonymousFunction, functionCall, 0, $"Expecting <index_expression> instead of [{Current.Type}]"); if (Current.Type != TokenType.CloseBracket) { LogError($"Unexpected [{Current.Type}]. Expecting ']'"); } else { NextToken(); } leftOperand = Close(indexerExpression); continue; } if (Current.Type == TokenType.Equal) { var assignExpression = Open <ScriptAssignExpression>(); if (expressionLevel > 1) { // unit test: 101-assign-complex-error1.txt LogError(assignExpression, $"Expression is only allowed for a top level assignment"); } NextToken(); assignExpression.Target = 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)) { var newPrecedence = GetOperatorPrecedence(binaryOperatorType); // Check precedence to see if we should "take" this operator here (Thanks TimJones for the tip code! ;) if (newPrecedence < precedence) { break; } var 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, $"Expecting an <expression> to the right of the operator instead of [{Current.Type}]"); leftOperand = Close(binaryExpression); continue; } if (precedence > 0) { break; } // If we can parse a statement, we have a method call if (StartAsExpression()) { if (parentExpression != null) { break; } if (functionCall == null) { functionCall = Open <ScriptFunctionCall>(); functionCall.Target = leftOperand; } else { functionCall.Arguments.Add(leftOperand); } goto parseExpression; } if (Current.Type == TokenType.Pipe) { if (functionCall != null) { functionCall.Arguments.Add(leftOperand); leftOperand = functionCall; } var 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); return(functionCall); } return(Close(leftOperand)); } finally { expressionLevel--; } }
private ScriptExpression ParseExpression(ScriptNode parentNode, ref bool hasAnonymousFunction, ScriptExpression parentExpression = null, int precedence = 0, ParseExpressionMode mode = ParseExpressionMode.Default) { int expressionCount = 0; _expressionLevel++; var 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("Cannot use block delegate $$ in a nested expression"); } if (!(parentNode is ScriptExpressionStatement)) { LogError(parentNode, "Cannot use block delegate $$ outside an expression statement"); } 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($"Unexpected token `{GetAsText(Current)}` while parsing function call `{functionCall}`"); } else { LogError($"Unexpected token `{GetAsText(Current)}` while parsing expression"); } 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) { var nextToken = PeekToken(); if (nextToken.Type == TokenType.Identifier) { NextToken(); if (GetAsText(Current) == "empty" && PeekToken().Type == TokenType.Question) { var memberExpression = Open <ScriptIsEmptyExpression>(); NextToken(); // skip empty NextToken(); // skip ? memberExpression.Target = leftOperand; leftOperand = Close(memberExpression); } else { var memberExpression = Open <ScriptMemberExpression>(); memberExpression.Target = leftOperand; var member = ParseVariable(); if (!(member is ScriptVariable)) { LogError("Unexpected literal member `{member}`"); return(null); } memberExpression.Member = (ScriptVariable)member; leftOperand = Close(memberExpression); } } else { LogError(nextToken, $"Invalid token `{nextToken.Type}`. The dot operator is expected to be followed by a plain identifier"); 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(); var indexerExpression = Open <ScriptIndexerExpression>(); indexerExpression.Target = leftOperand; // unit test: 130-indexer-accessor-error5.txt indexerExpression.Index = ExpectAndParseExpression(indexerExpression, ref hasAnonymousFunction, functionCall, 0, $"Expecting <index_expression> instead of `{Current.Type}`"); if (Current.Type != TokenType.CloseBracket) { LogError($"Unexpected `{Current.Type}`. Expecting ']'"); } else { NextToken(); } leftOperand = Close(indexerExpression); continue; } if (mode == ParseExpressionMode.BasicExpression) { break; } if (Current.Type == TokenType.Equal) { var assignExpression = Open <ScriptAssignExpression>(); if (_expressionLevel > 1) { // unit test: 101-assign-complex-error1.txt LogError(assignExpression, $"Expression is only allowed for a top level assignment"); } 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))) { var 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(); var 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, $"Expecting an <expression> to the right of the operator instead of `{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; } var parameter = Open <ScriptNamedArgument>(); var 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 scriban functions: if (_isLiquid && Options.LiquidFunctionsToScriban) { TransformLiquidFunctionCallToScriban(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; } var 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--; } }
private ScriptStatement ParseLiquidIncludeStatement() { ScriptFunctionCall include = Open <ScriptFunctionCall>(); include.Target = ParseVariable(); Token templateNameToken = Current; ScriptExpression templateName = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression); if (templateName != null) { var literal = templateName as ScriptLiteral; if (!(literal?.Value is string || templateName is IScriptVariablePath)) { LogError(templateNameToken, string.Format(RS.UnexpectedIncludeTemplateName, templateName)); } include.Arguments.Add(templateName); } Close(include); ScriptExpressionStatement includeStatement = new ScriptExpressionStatement() { Span = include.Span, Expression = include }; ScriptForStatement forStatement = null; ScriptBlockStatement block = null; if (Current.Type == TokenType.Identifier) { string next = GetAsText(Current); // Parse with <value> // Create a block statement equivalent: // this[target] = value; // include target if (next == "with") { NextToken(); // skip with ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>(); assignExpression.Target = new ScriptIndexerExpression() { Target = new ScriptThisExpression { Span = CurrentSpan }, Index = templateName }; assignExpression.Value = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression); Close(assignExpression); block = new ScriptBlockStatement { Span = include.Span }; block.Statements.Add(new ScriptExpressionStatement() { Span = assignExpression.Span, Expression = assignExpression }); block.Statements.Add(includeStatement); Close(block); } else if (next == "for") { // Create a block statement equivalent: // for this[target] in value // include target // end NextToken(); // skip for forStatement = Open <ScriptForStatement>(); forStatement.Variable = new ScriptIndexerExpression() { Target = new ScriptThisExpression { Span = CurrentSpan }, Index = templateName }; forStatement.Iterator = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression); forStatement.Body = new ScriptBlockStatement() { Span = include.Span }; forStatement.Body.Statements.Add(includeStatement); Close(forStatement); } // For following variable separated by colon, add them as assignment before the include while (Current.Type == TokenType.Identifier) { Token variableToken = Current; ScriptExpression variableObject = ParseVariable(); var variable = variableObject as ScriptVariable; if (variable == null) { LogError(variableToken, string.Format(RS.UnexpectedVariableInInclude, GetAsText(variableToken))); } if (Current.Type == TokenType.Colon) { NextToken(); // skip : } else { LogError(Current, string.Format(RS.UnexpectedTokenAfterVariable, GetAsText(Current), variable, ":")); } if (block == null) { block = new ScriptBlockStatement { Span = include.Span }; block.Statements.Add(includeStatement); } ScriptAssignExpression assignExpression = Open <ScriptAssignExpression>(); assignExpression.Target = variable; assignExpression.Value = ExpectAndParseExpression(include, mode: ParseExpressionMode.BasicExpression); block.Statements.Insert(0, new ScriptExpressionStatement() { Span = assignExpression.Span, Expression = assignExpression }); if (Current.Type == TokenType.Comma) { NextToken(); } } ExpectEndOfStatement(includeStatement); // If we only have an include for, return it directly if (forStatement != null) { if (block == null) { return(Close(forStatement)); } block.Statements.Add(forStatement); } // Else we have a block if (block != null) { Close(block); return(block); } } ExpectEndOfStatement(includeStatement); return(Close(includeStatement)); }
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)); }
/// <summary> /// Evaluates the specified expression /// </summary> /// <param name="targetExpression">The expression to evaluate</param> /// <param name="valueToSet">A value to set in case of a setter</param> /// <param name="setter">true if this a setter</param> /// <param name="level">The indirection level (0 before entering the expression)</param> /// <returns>The value of the targetExpression</returns> private object GetOrSetValue(ScriptExpression targetExpression, object valueToSet, bool setter, int level) { object value = null; try { var nextVariable = targetExpression as ScriptVariable; if (nextVariable != null) { if (setter) { SetValue(nextVariable, valueToSet, false); } else { value = GetValueFromVariable(nextVariable); } } else { if (targetExpression is ScriptMemberExpression nextDot) { var targetObject = GetOrSetValue(nextDot.Target, valueToSet, false, level + 1); if (targetObject == null) { throw new ScriptRuntimeException(nextDot.Span, $"Object [{nextDot.Target}] is null. Cannot access member: {nextDot}"); // unit test: 131-member-accessor-error1.txt } if (targetObject is string || targetObject.GetType().GetTypeInfo().IsPrimitive) { throw new ScriptRuntimeException(nextDot.Span, $"Cannot get or set a member on the primitive [{targetObject}/{targetObject.GetType()}] when accessing member: {nextDot}"); // unit test: 132-member-accessor-error2.txt } var accessor = GetMemberAccessor(targetObject); var memberName = nextDot.Member.Name; if (setter) { if (!accessor.TrySetValue(this, targetExpression.Span, targetObject, memberName, valueToSet)) { throw new ScriptRuntimeException(nextDot.Member.Span, $"Cannot set a value for the readonly member: {nextDot}"); // unit test: 132-member-accessor-error3.txt } } else { if (!accessor.TryGetValue(this, targetExpression.Span, targetObject, memberName, out value)) { TryGetMember?.Invoke(this, targetExpression.Span, targetObject, memberName, out value); } } } else { if (targetExpression is ScriptIndexerExpression nextIndexer) { var targetObject = GetOrSetValue(nextIndexer.Target, valueToSet, false, level + 1); if (targetObject == null) { throw new ScriptRuntimeException(nextIndexer.Target.Span, $"Object [{nextIndexer.Target}] is null. Cannot access indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error1.txt } else { var index = Evaluate(nextIndexer.Index); if (index == null) { throw new ScriptRuntimeException(nextIndexer.Index.Span, $"Cannot access target [{nextIndexer.Target}] with a null indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error2.txt } else { if (targetObject is IDictionary || targetObject is ScriptObject) { var accessor = GetMemberAccessor(targetObject); var indexAsString = ToString(nextIndexer.Index.Span, index); if (setter) { if (!accessor.TrySetValue(this, targetExpression.Span, targetObject, indexAsString, valueToSet)) { throw new ScriptRuntimeException(nextIndexer.Index.Span, $"Cannot set a value for the readonly member [{indexAsString}] in the indexer: {nextIndexer.Target}['{indexAsString}']"); // unit test: 130-indexer-accessor-error3.txt } } else { if (!accessor.TryGetValue(this, targetExpression.Span, targetObject, indexAsString, out value)) { TryGetMember?.Invoke(this, targetExpression.Span, targetObject, indexAsString, out value); } } } else { var accessor = GetListAccessor(targetObject); if (accessor == null) { throw new ScriptRuntimeException(nextIndexer.Target.Span, $"Expecting a list. Invalid value [{targetObject}/{targetObject.GetType().Name}] for the target [{nextIndexer.Target}] for the indexer: {nextIndexer}"); // unit test: 130-indexer-accessor-error4.txt } else { int i = ToInt(nextIndexer.Index.Span, index); // Allow negative index from the end of the array if (i < 0) { i = accessor.GetLength(this, targetExpression.Span, targetObject) + i; } if (i >= 0) { if (setter) { accessor.SetValue(this, targetExpression.Span, targetObject, i, valueToSet); } else { value = accessor.GetValue(this, targetExpression.Span, targetObject, i); } } } } } } } else if (!setter) { value = Evaluate(targetExpression); } else { throw new ScriptRuntimeException(targetExpression.Span, $"Unsupported expression for target for assignment: {targetExpression} = ..."); // unit test: 105-assign-error1.txt } } } } catch (Exception readonlyException) when(level == 0 && !(readonlyException is ScriptRuntimeException)) { throw new ScriptRuntimeException(targetExpression.Span, $"Unexpected exception while accessing `{targetExpression}`", readonlyException); } // If the variable being returned is a function, we need to evaluate it // If function call is disabled, it will be only when returning the final object (level 0 of recursion) if ((!_isFunctionCallDisabled || level > 0) && ScriptFunctionCall.IsFunction(value)) { value = ScriptFunctionCall.Call(this, targetExpression, value); } return(value); }
public ScriptExpression ReplaceCondition(ScriptExpression condition, object replaceValue) { if (condition is ScriptVariableLocal localVar) { if (localVar.Name == string.Empty) { return(new ScriptLiteral(replaceValue)); } else { return(localVar); } } else if (condition is ScriptMemberExpression objExpr) { return(new ScriptMemberExpression() { Member = objExpr.Member, Trivias = objExpr.Trivias, Target = ReplaceCondition(objExpr.Target, replaceValue) }); } else if (condition is ScriptNestedExpression nestedExpr) { return(new ScriptNestedExpression() { Expression = ReplaceCondition(nestedExpr.Expression, replaceValue), Trivias = nestedExpr.Trivias }); } else if (condition is ScriptFunctionCall funcCall) { ScriptFunctionCall funcResult = new ScriptFunctionCall() { Target = funcCall.Target, Trivias = funcCall.Trivias }; foreach (ScriptExpression expr in funcCall.Arguments) { funcResult.Arguments.Add(ReplaceCondition(expr, replaceValue)); } return(funcResult); } else if (condition is ScriptPipeCall pipeCall) { ScriptPipeCall pipeResult = new ScriptPipeCall() { From = ReplaceCondition(pipeCall.From, replaceValue), To = ReplaceCondition(pipeCall.To, replaceValue), Trivias = pipeCall.Trivias }; return(pipeResult); } else if (condition is ScriptBinaryExpression binCondition) { ScriptBinaryExpression binExprResult = new ScriptBinaryExpression() { Left = ReplaceCondition(binCondition.Left, replaceValue), Right = ReplaceCondition(binCondition.Right, replaceValue), Operator = binCondition.Operator, Trivias = binCondition.Trivias }; return(binExprResult); } else { return(condition); } }