mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
- Implemented pooled slices for function arguments - Added specialized pooling for variable node and literal node objects - Modified array and hash node evaluation to reduce allocations - Optimized test and filter evaluation with pooled resources - Added comprehensive benchmarks to validate improvements - Updated node pool implementation to remove duplicate declarations - Fixed memory allocations in merge filter to correctly handle array manipulations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
182 lines
5.5 KiB
Go
182 lines
5.5 KiB
Go
package twig
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// parseFrom handles the from tag which imports macros from another template
|
|
// Example: {% from "macros.twig" import input, button %}
|
|
// Example: {% from "macros.twig" import input as field, button as btn %}
|
|
func (p *Parser) parseFrom(parser *Parser) (Node, error) {
|
|
// Get the line number of the from token
|
|
fromLine := parser.tokens[parser.tokenIndex-1].Line
|
|
|
|
// Debugging: Print out tokens for debugging purposes
|
|
if IsDebugEnabled() {
|
|
LogDebug("Parsing from tag. Next tokens (up to 10):")
|
|
for i := 0; i < 10 && i+parser.tokenIndex < len(parser.tokens); i++ {
|
|
if parser.tokenIndex+i < len(parser.tokens) {
|
|
token := parser.tokens[parser.tokenIndex+i]
|
|
LogDebug(" Token %d: Type=%d, Value=%q", i, token.Type, token.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
// First try to parse in token-by-token format (from zero-allocation tokenizer)
|
|
// Check if we have a proper sequence: [STRING/NAME, NAME="import", NAME="macroname", ...]
|
|
if parser.tokenIndex+1 < len(parser.tokens) {
|
|
// Check for (template path) followed by "import" keyword
|
|
firstToken := parser.tokens[parser.tokenIndex]
|
|
secondToken := parser.tokens[parser.tokenIndex+1]
|
|
|
|
isTemplatePath := firstToken.Type == TOKEN_STRING || firstToken.Type == TOKEN_NAME
|
|
isImportKeyword := secondToken.Type == TOKEN_NAME && secondToken.Value == "import"
|
|
|
|
if isTemplatePath && isImportKeyword {
|
|
LogDebug("Found tokenized from...import pattern")
|
|
|
|
// Get template path
|
|
templatePath := firstToken.Value
|
|
if firstToken.Type == TOKEN_NAME {
|
|
// For paths like ./file.twig, just use the value
|
|
templatePath = strings.Trim(templatePath, "\"'")
|
|
}
|
|
|
|
// Create template expression
|
|
templateExpr := &LiteralNode{
|
|
ExpressionNode: ExpressionNode{
|
|
exprType: ExprLiteral,
|
|
line: fromLine,
|
|
},
|
|
value: templatePath,
|
|
}
|
|
|
|
// Skip past the template path and import keyword
|
|
parser.tokenIndex += 2
|
|
|
|
// Parse macros and aliases
|
|
macros := []string{}
|
|
aliases := map[string]string{}
|
|
|
|
// Process tokens until end of block
|
|
for parser.tokenIndex < len(parser.tokens) {
|
|
token := parser.tokens[parser.tokenIndex]
|
|
|
|
// Stop at block end
|
|
if token.Type == TOKEN_BLOCK_END || token.Type == TOKEN_BLOCK_END_TRIM {
|
|
parser.tokenIndex++
|
|
break
|
|
}
|
|
|
|
// Skip punctuation (commas)
|
|
if token.Type == TOKEN_PUNCTUATION {
|
|
parser.tokenIndex++
|
|
continue
|
|
}
|
|
|
|
// Handle macro name
|
|
if token.Type == TOKEN_NAME {
|
|
macroName := token.Value
|
|
|
|
// Add to macros list
|
|
macros = append(macros, macroName)
|
|
|
|
// Check for alias
|
|
parser.tokenIndex++
|
|
if parser.tokenIndex < len(parser.tokens) &&
|
|
parser.tokens[parser.tokenIndex].Type == TOKEN_NAME &&
|
|
parser.tokens[parser.tokenIndex].Value == "as" {
|
|
|
|
// Skip 'as' keyword
|
|
parser.tokenIndex++
|
|
|
|
// Get alias
|
|
if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
|
aliases[macroName] = parser.tokens[parser.tokenIndex].Value
|
|
parser.tokenIndex++
|
|
}
|
|
}
|
|
} else {
|
|
// Skip any other token
|
|
parser.tokenIndex++
|
|
}
|
|
}
|
|
|
|
// If we found macros, return a FromImportNode
|
|
if len(macros) > 0 {
|
|
return NewFromImportNode(templateExpr, macros, aliases, fromLine), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fall back to the original approach (for backward compatibility)
|
|
// We need to extract the template path, import keyword, and macro(s) from
|
|
// the current token. The tokenizer may be combining them.
|
|
if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
|
// Extract parts from the combined token value
|
|
tokenValue := parser.tokens[parser.tokenIndex].Value
|
|
|
|
// Try to extract template path and remaining parts
|
|
matches := strings.Split(tokenValue, " import ")
|
|
if len(matches) == 2 {
|
|
// We found the import keyword in the token value
|
|
templatePath := strings.TrimSpace(matches[0])
|
|
// Remove quotes if present
|
|
templatePath = strings.Trim(templatePath, "\"'")
|
|
macrosList := strings.TrimSpace(matches[1])
|
|
|
|
// Create template expression
|
|
templateExpr := &LiteralNode{
|
|
ExpressionNode: ExpressionNode{
|
|
exprType: ExprLiteral,
|
|
line: fromLine,
|
|
},
|
|
value: templatePath,
|
|
}
|
|
|
|
// Parse macros list
|
|
macros := []string{}
|
|
aliases := map[string]string{}
|
|
|
|
// Split macros by comma if multiple
|
|
macroItems := strings.Split(macrosList, ",")
|
|
for _, item := range macroItems {
|
|
item = strings.TrimSpace(item)
|
|
|
|
// Check for "as" alias
|
|
asParts := strings.Split(item, " as ")
|
|
if len(asParts) == 2 {
|
|
// We have an alias
|
|
macroName := strings.TrimSpace(asParts[0])
|
|
aliasName := strings.TrimSpace(asParts[1])
|
|
aliases[macroName] = aliasName
|
|
// Still add the macro name to macros list, even with alias
|
|
macros = append(macros, macroName)
|
|
} else {
|
|
// No alias
|
|
macros = append(macros, item)
|
|
}
|
|
}
|
|
|
|
// Skip the current token
|
|
parser.tokenIndex++
|
|
|
|
// Skip to the block end token
|
|
for parser.tokenIndex < len(parser.tokens) {
|
|
if parser.tokens[parser.tokenIndex].Type == TOKEN_BLOCK_END ||
|
|
parser.tokens[parser.tokenIndex].Type == TOKEN_BLOCK_END_TRIM {
|
|
parser.tokenIndex++
|
|
break
|
|
}
|
|
parser.tokenIndex++
|
|
}
|
|
|
|
// Create and return the FromImportNode
|
|
return NewFromImportNode(templateExpr, macros, aliases, fromLine), nil
|
|
}
|
|
}
|
|
|
|
// If we're here, the standard parsing approach failed, so return an error
|
|
return nil, fmt.Errorf("expected 'import' after template path at line %d", fromLine)
|
|
}
|