private ScriptExpression TransformKeyword(ScriptExpression leftOperand) { // In case we are in liquid and we are assigning to a textscript keyword, we escape the variable with a nested expression if (_isLiquid && leftOperand is IScriptVariablePath && IsTextScriptKeyword(((IScriptVariablePath)leftOperand).GetFirstPath()) && !(leftOperand is ScriptNestedExpression)) { ScriptNestedExpression nestedExpression = new ScriptNestedExpression { Expression = leftOperand, Span = leftOperand.Span }; // If the variable has any trivia, we copy them to the NestedExpression instead if (_isKeepTrivia && leftOperand.Trivias != null) { nestedExpression.Trivias = leftOperand.Trivias; leftOperand.Trivias = null; } return(nestedExpression); } return(leftOperand); }
private ScriptExpression ParseParenthesis(ref bool hasAnonymousFunction) { // unit test: 106-parenthesis.txt ScriptNestedExpression expression = Open <ScriptNestedExpression>(); NextToken(); // Skip ( expression.Expression = ExpectAndParseExpression(expression, ref hasAnonymousFunction); if (Current.Type == TokenType.CloseParent) { NextToken(); } else { // unit test: 106-parenthesis-error1.txt LogError(Current, string.Format(RS.SpanCloseBracketMismatch, expression.Span.Start, Current.Type)); } return(Close(expression)); }
private ScriptExpression ParseVariable() { var currentToken = Current; var currentSpan = CurrentSpan; var endSpan = currentSpan; var text = GetAsText(currentToken); // Return ScriptLiteral for null, true, false // Return ScriptAnonymousFunction switch (text) { case "null": var nullValue = Open <ScriptLiteral>(); NextToken(); return(Close(nullValue)); case "true": var trueValue = Open <ScriptLiteral>(); trueValue.Value = true; NextToken(); return(Close(trueValue)); case "false": var falseValue = Open <ScriptLiteral>(); falseValue.Value = false; NextToken(); return(Close(falseValue)); case "do": var functionExp = Open <ScriptAnonymousFunction>(); functionExp.Function = ParseFunctionStatement(true); var func = Close(functionExp); return(func); case "this": if (!_isLiquid) { var thisExp = Open <ScriptThisExpression>(); NextToken(); return(Close(thisExp)); } break; } // Keeps trivia before this token List <ScriptTrivia> triviasBefore = null; if (_isKeepTrivia && _trivias.Count > 0) { triviasBefore = new List <ScriptTrivia>(); triviasBefore.AddRange(_trivias); _trivias.Clear(); } NextToken(); var scope = ScriptVariableScope.Global; if (text.StartsWith("$")) { scope = ScriptVariableScope.Local; text = text.Substring(1); // Convert $0, $1... $n variable into $[0] $[1]...$[n] variables int index; if (int.TryParse(text, out index)) { var indexerExpression = new ScriptIndexerExpression { Span = currentSpan, Target = new ScriptVariableLocal(ScriptVariable.Arguments.Name) { Span = currentSpan }, Index = new ScriptLiteral() { Span = currentSpan, Value = index } }; if (_isKeepTrivia) { if (triviasBefore != null) { indexerExpression.Target.AddTrivias(triviasBefore, true); } FlushTrivias(indexerExpression.Index, false); } return(indexerExpression); } } else if (text == "for" || text == "while" || text == "tablerow" || (_isLiquid && (text == "forloop" || text == "tablerowloop"))) { if (Current.Type == TokenType.Dot) { NextToken(); if (Current.Type == TokenType.Identifier) { endSpan = CurrentSpan; var loopVariableText = GetAsText(Current); NextToken(); scope = ScriptVariableScope.Loop; if (_isLiquid) { switch (loopVariableText) { case "first": text = ScriptVariable.LoopFirst.Name; break; case "last": text = ScriptVariable.LoopLast.Name; break; case "index0": text = ScriptVariable.LoopIndex.Name; break; case "rindex0": text = ScriptVariable.LoopRIndex.Name; break; case "rindex": case "index": // Because forloop.index is 1 based index, we need to create a binary expression // to support it here bool isrindex = loopVariableText == "rindex"; var nested = new ScriptNestedExpression() { Expression = new ScriptBinaryExpression() { Operator = ScriptBinaryOperator.Add, Left = new ScriptVariableLoop(isrindex ? ScriptVariable.LoopRIndex.Name : ScriptVariable.LoopIndex.Name) { Span = currentSpan }, Right = new ScriptLiteral(1) { Span = currentSpan }, Span = currentSpan }, Span = currentSpan }; if (_isKeepTrivia) { if (triviasBefore != null) { nested.AddTrivias(triviasBefore, true); } FlushTrivias(nested, false); } return(nested); case "length": text = ScriptVariable.LoopLength.Name; break; case "col": if (text != "tablerowloop") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, $"The loop variable <{text}.col> is invalid"); } text = ScriptVariable.TableRowCol.Name; break; default: text = text + "." + loopVariableText; LogError(currentToken, $"The liquid loop variable <{text}> is not supported"); break; } } else { switch (loopVariableText) { case "first": text = ScriptVariable.LoopFirst.Name; break; case "last": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, "The loop variable <while.last> is invalid"); } text = ScriptVariable.LoopLast.Name; break; case "changed": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, "The loop variable <while.changed> is invalid"); } text = ScriptVariable.LoopChanged.Name; break; case "length": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, "The loop variable <while.length> is invalid"); } text = ScriptVariable.LoopLength.Name; break; case "even": text = ScriptVariable.LoopEven.Name; break; case "odd": text = ScriptVariable.LoopOdd.Name; break; case "index": text = ScriptVariable.LoopIndex.Name; break; case "rindex": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, "The loop variable <while.rindex> is invalid"); } text = ScriptVariable.LoopRIndex.Name; break; case "col": if (text != "tablerow") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, $"The loop variable <{text}.col> is invalid"); } text = ScriptVariable.TableRowCol.Name; break; default: text = text + "." + loopVariableText; // unit test: 108-variable-loop-error1.txt LogError(currentToken, $"The loop variable <{text}> is not supported"); break; } } // We no longer checks at parse time usage of loop variables, as they can be used in a wrap context //if (!IsInLoop()) //{ // LogError(currentToken, $"Unexpected variable <{text}> outside of a loop"); //} } else { LogError(currentToken, $"Invalid token `{Current.Type}`. The loop variable <{text}> dot must be followed by an identifier"); } } } else if (_isLiquid && text == "continue") { scope = ScriptVariableScope.Local; } var result = ScriptVariable.Create(text, scope); result.Span = new SourceSpan { FileName = currentSpan.FileName, Start = currentSpan.Start, End = endSpan.End }; // A liquid variable can have `-` in its identifier // If this is the case, we need to translate it to `this["this"]` instead if (_isLiquid && text.IndexOf('-') >= 0) { var newExp = new ScriptIndexerExpression { Target = new ScriptThisExpression() { Span = result.Span }, Index = new ScriptLiteral(text) { Span = result.Span }, Span = result.Span }; // Flush any trivias after if (_isKeepTrivia) { if (triviasBefore != null) { newExp.Target.AddTrivias(triviasBefore, true); } FlushTrivias(newExp, false); } // Return the expression return(newExp); } if (_isKeepTrivia) { // Flush any trivias after if (triviasBefore != null) { result.AddTrivias(triviasBefore, true); } FlushTrivias(result, false); } return(result); }
private ScriptIfStatement ParseIfStatement(bool invert, ScriptKeyword elseKeyword = null) { // unit test: 200-if-else-statement.txt var ifStatement = Open <ScriptIfStatement>(); ifStatement.ElseKeyword = elseKeyword; if (_isLiquid && elseKeyword != null) { // Parse elseif Open(ifStatement.IfKeyword); NextToken(); Close(ifStatement.IfKeyword); } else { if (_isLiquid && invert) // we have an unless { Open(ifStatement.IfKeyword); // still transfer trivias to IfKeyword NextToken(); Close(ifStatement.IfKeyword); } else { ExpectAndParseKeywordTo(ifStatement.IfKeyword); // Parse if keyword } } var condition = ExpectAndParseExpression(ifStatement, allowAssignment: false); // Transform a `if condition` to `if !(condition)` if (invert) { var invertCondition = ScriptUnaryExpression.Wrap(ScriptUnaryOperator.Not, ScriptToken.Exclamation(), ScriptNestedExpression.Wrap(condition, _isKeepTrivia), _isKeepTrivia); condition = invertCondition; } ifStatement.Condition = condition; if (ExpectEndOfStatement()) { ifStatement.Then = ParseBlockStatement(ifStatement); } return(Close(ifStatement)); }