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