private ScriptStatement ParseLiquidIfChanged() { var statement = Open <ScriptIfStatement>(); NextToken(); // skip ifchanged token statement.Condition = new ScriptMemberExpression() { Target = ScriptVariable.Create(ScriptVariable.ForObject.Name, ScriptVariableScope.Loop), Member = ScriptVariable.Create("changed", ScriptVariableScope.Global) }; statement.Then = ParseBlockStatement(statement); Close(statement); statement.Condition.Span = statement.Span; return(statement); }
private T ParseForStatement <T>() where T : ScriptForStatement, new() { var forStatement = Open <T>(); NextToken(); // skip for // unit test: 211-for-error1.txt forStatement.Variable = ExpectAndParseExpression(forStatement, mode: ParseExpressionMode.BasicExpression); if (forStatement.Variable != null) { if (!(forStatement.Variable is IScriptVariablePath)) { LogError(forStatement, $"Expecting a variable instead of `{forStatement.Variable}`"); } // A global variable used in a for should always be a loop only variable if (forStatement.Variable is ScriptVariableGlobal previousVar) { var loopVar = ScriptVariable.Create(previousVar.Name, ScriptVariableScope.Loop); loopVar.Span = previousVar.Span; loopVar.Trivias = previousVar.Trivias; forStatement.Variable = loopVar; } // in if (Current.Type != TokenType.Identifier || GetAsText(Current) != "in") { // unit test: 211-for-error2.txt LogError(forStatement, $"Expecting 'in' word instead of `{Current.Type} {GetAsText(Current)}`"); } else { NextToken(); // skip in } // unit test: 211-for-error3.txt forStatement.Iterator = ExpectAndParseExpression(forStatement); if (ExpectEndOfStatement(forStatement)) { FlushTrivias(forStatement.IteratorOrLastParameter, false); forStatement.Body = ParseBlockStatement(forStatement); } } return(Close(forStatement)); }
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 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>(); ExpectAndParseKeywordTo(thisExp.ThisKeyword); 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, NumberStyles.Integer, CultureInfo.InvariantCulture, out index)) { var target = new ScriptVariableLocal(ScriptVariable.Arguments.BaseName) { Span = currentSpan }; var indexLiteral = new ScriptLiteral() { Span = currentSpan, Value = index }; var indexerExpression = new ScriptIndexerExpression { Span = currentSpan, Target = target, Index = indexLiteral }; if (_isKeepTrivia) { if (triviasBefore != null) { target.AddTrivias(triviasBefore, true); } // Special case, we add the trivias to the index as the [] brackets // won't be output-ed FlushTrivias(indexLiteral, false); _lastTerminalWithTrivias = indexLiteral; } return(indexerExpression); } } else if (text == "for" || text == "while" || text == "tablerow" || (_isLiquid && (text == "forloop" || text == "tablerowloop"))) { if (Current.Type == TokenType.Dot) { scope = ScriptVariableScope.Loop; var token = PeekToken(); if (token.Type == TokenType.Identifier) { //endSpan = GetSpanForToken(token); var loopVariableText = GetAsText(token); if (_isLiquid) { switch (loopVariableText) { case "first": case "last": case "index0": case "rindex0": case "index": case "rindex": case "length": break; case "col": if (text != "tablerowloop") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, $"The loop variable <{text}.col> is invalid"); } break; default: LogError(currentToken, $"The liquid loop variable <{text}.{loopVariableText}> is not supported"); break; } if (text == "forloop") { text = "for"; } else if (text == "tablerowloop") { text = "tablerow"; } } else { switch (loopVariableText) { // supported by both for and while case "first": case "even": case "odd": case "index": break; case "last": case "changed": case "length": case "rindex": if (text == "while") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, $"The loop variable <while.{loopVariableText}> is invalid"); } break; case "col": if (text != "tablerow") { // unit test: 108-variable-loop-error2.txt LogError(currentToken, $"The loop variable <{text}.col> is invalid"); } break; default: // unit test: 108-variable-loop-error1.txt LogError(currentToken, $"The loop variable <{text}.{loopVariableText}> is not supported"); break; } } } else { LogError(currentToken, $"Invalid token `{GetAsText(Current)}`. 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 target = new ScriptThisExpression() { Span = result.Span }; var newExp = new ScriptIndexerExpression { Target = target, Index = new ScriptLiteral(text) { Span = result.Span }, Span = result.Span }; // Flush any trivias after if (_isKeepTrivia) { if (triviasBefore != null) { target.ThisKeyword.AddTrivias(triviasBefore, true); } FlushTrivias(newExp.CloseBracket, false); _lastTerminalWithTrivias = newExp.CloseBracket; } // Return the expression return(newExp); } if (_isKeepTrivia) { // Flush any trivias after if (triviasBefore != null) { result.AddTrivias(triviasBefore, true); } FlushTrivias(result, false); _lastTerminalWithTrivias = result; } return(result); }