private ScriptExpression ParseVariable() { Token currentToken = Current; SourceSpan currentSpan = CurrentSpan; SourceSpan endSpan = currentSpan; string text = GetAsText(currentToken); // Return ScriptLiteral for null, true, false // Return ScriptAnonymousFunction switch (text) { case "null": ScriptLiteral nullValue = Open <ScriptLiteral>(); NextToken(); return(Close(nullValue)); case "true": ScriptLiteral trueValue = Open <ScriptLiteral>(); trueValue.Value = true; NextToken(); return(Close(trueValue)); case "false": ScriptLiteral falseValue = Open <ScriptLiteral>(); falseValue.Value = false; NextToken(); return(Close(falseValue)); case "do": ScriptAnonymousFunction functionExp = Open <ScriptAnonymousFunction>(); functionExp.Function = ParseFunctionStatement(true); ScriptAnonymousFunction func = Close(functionExp); return(func); case "this": if (!_isLiquid) { ScriptThisExpression 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(); ScriptVariableScope 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, NumberStyles.Integer, CultureInfo.InvariantCulture, out index)) { ScriptIndexerExpression 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; string 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"; ScriptNestedExpression 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, string.Format(RS.InvalidLoopVariable, text + ".col")); } text = ScriptVariable.TableRowCol.Name; break; default: text = text + "." + loopVariableText; LogError(currentToken, string.Format(RS.InvalidLiquidLoopVariable, text)); 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, string.Format(RS.InvalidLoopVariable, "while.last")); } text = ScriptVariable.LoopLast.Name; break; case "changed": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, string.Format(RS.InvalidLoopVariable, "while.changed")); } text = ScriptVariable.LoopChanged.Name; break; case "length": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, string.Format(RS.InvalidLoopVariable, "while.length")); } 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, string.Format(RS.InvalidLoopVariable, "while.rindex")); } text = ScriptVariable.LoopRIndex.Name; break; case "col": if (text != "tablerow") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, string.Format(RS.InvalidLoopVariable, text + ".col")); } text = ScriptVariable.TableRowCol.Name; break; default: text = text + "." + loopVariableText; // unit test: 108-variable-loop-error1.txt LogError(currentToken, string.Format(RS.InvalidLoopVariable, text)); 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, string.Format(RS.InvalidLoopVariableOutsideLoop, text)); //} } else { LogError(currentToken, string.Format(RS.LoopVariableDotRequireIdentifier, Current.Type, text)); } } } else if (_isLiquid && text == "continue") { scope = ScriptVariableScope.Local; } ScriptVariable 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) { ScriptIndexerExpression 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); }