go-twig/parse_from.go
semihalev 435bb12ac3 Optimize expression evaluation to reduce allocations
- 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>
2025-03-12 03:04:36 +03:00

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)
}