mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
- Move parsing functions for tags into dedicated files - Add comprehensive tests for the from tag - Fix the implementation of parseFrom to correctly handle imports - Improve test coverage for macros and imports 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
143 lines
4.7 KiB
Go
143 lines
4.7 KiB
Go
package twig
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
// parseFor parses a for loop construct in Twig templates
|
|
// Examples:
|
|
// {% for item in items %}...{% endfor %}
|
|
// {% for key, value in items %}...{% endfor %}
|
|
// {% for item in items %}...{% else %}...{% endfor %}
|
|
func (p *Parser) parseFor(parser *Parser) (Node, error) {
|
|
// Get the line number of the for token
|
|
forLine := parser.tokens[parser.tokenIndex-2].Line
|
|
|
|
// Parse the loop variable name(s)
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
|
return nil, fmt.Errorf("expected variable name after for at line %d", forLine)
|
|
}
|
|
|
|
// Get value variable name
|
|
valueVar := parser.tokens[parser.tokenIndex].Value
|
|
parser.tokenIndex++
|
|
|
|
var keyVar string
|
|
|
|
// Check for key, value syntax
|
|
if parser.tokenIndex < len(parser.tokens) &&
|
|
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
|
parser.tokens[parser.tokenIndex].Value == "," {
|
|
|
|
// Move past the comma
|
|
parser.tokenIndex++
|
|
|
|
// Now valueVar is actually the key, and we need to get the value
|
|
keyVar = valueVar
|
|
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
|
return nil, fmt.Errorf("expected value variable name after comma at line %d", forLine)
|
|
}
|
|
|
|
valueVar = parser.tokens[parser.tokenIndex].Value
|
|
parser.tokenIndex++
|
|
}
|
|
|
|
// Expect 'in' keyword
|
|
if parser.tokenIndex >= len(parser.tokens) ||
|
|
parser.tokens[parser.tokenIndex].Type != TOKEN_NAME ||
|
|
parser.tokens[parser.tokenIndex].Value != "in" {
|
|
return nil, fmt.Errorf("expected 'in' keyword after variable name at line %d", forLine)
|
|
}
|
|
parser.tokenIndex++
|
|
|
|
// Parse the sequence expression
|
|
sequence, err := parser.parseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check for filter operator (|) - needed for cases where filter detection might be missed
|
|
if IsDebugEnabled() {
|
|
LogDebug("For loop sequence expression type: %T", sequence)
|
|
}
|
|
|
|
// Expect the block end token (either regular or trim variant)
|
|
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
|
return nil, fmt.Errorf("expected block end after for statement at line %d", forLine)
|
|
}
|
|
parser.tokenIndex++
|
|
|
|
// Parse the for loop body
|
|
loopBody, err := parser.parseOuterTemplate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var elseBody []Node
|
|
|
|
// Check for else or endfor
|
|
if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_BLOCK_START {
|
|
parser.tokenIndex++
|
|
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
|
return nil, fmt.Errorf("expected block name at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
|
}
|
|
|
|
// Check if this is an else block
|
|
if parser.tokens[parser.tokenIndex].Value == "else" {
|
|
parser.tokenIndex++
|
|
|
|
// Expect the block end token
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END {
|
|
return nil, fmt.Errorf("expected block end after else at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
|
}
|
|
parser.tokenIndex++
|
|
|
|
// Parse the else body
|
|
elseBody, err = parser.parseOuterTemplate()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now expect the endfor
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START {
|
|
return nil, fmt.Errorf("expected endfor block at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
|
}
|
|
parser.tokenIndex++
|
|
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
|
return nil, fmt.Errorf("expected endfor at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
|
}
|
|
|
|
if parser.tokens[parser.tokenIndex].Value != "endfor" {
|
|
return nil, fmt.Errorf("expected endfor, got %s at line %d", parser.tokens[parser.tokenIndex].Value, parser.tokens[parser.tokenIndex].Line)
|
|
}
|
|
parser.tokenIndex++
|
|
} else if parser.tokens[parser.tokenIndex].Value == "endfor" {
|
|
parser.tokenIndex++
|
|
} else {
|
|
return nil, fmt.Errorf("expected else or endfor, got %s at line %d", parser.tokens[parser.tokenIndex].Value, parser.tokens[parser.tokenIndex].Line)
|
|
}
|
|
|
|
// Expect the final block end token
|
|
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END {
|
|
return nil, fmt.Errorf("expected block end after endfor at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
|
}
|
|
parser.tokenIndex++
|
|
} else {
|
|
return nil, fmt.Errorf("unexpected end of template, expected endfor at line %d", forLine)
|
|
}
|
|
|
|
// Create the for node
|
|
forNode := &ForNode{
|
|
keyVar: keyVar,
|
|
valueVar: valueVar,
|
|
sequence: sequence,
|
|
body: loopBody,
|
|
elseBranch: elseBody,
|
|
line: forLine,
|
|
}
|
|
|
|
return forNode, nil
|
|
}
|