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 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--; } }