mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
Split parser functions into separate files and add from tag tests
- 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>
This commit is contained in:
parent
c50999e3ee
commit
ec37652bc1
14 changed files with 1675 additions and 1460 deletions
194
from_tag_test.go
Normal file
194
from_tag_test.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestFromTagBasic tests the most basic from tag use case
|
||||
func TestFromTagBasic(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template with a simple macro
|
||||
macroLib := `{% macro hello(name) %}Hello, {{ name }}!{% endmacro %}`
|
||||
|
||||
// Simple template using the from tag
|
||||
mainTemplate := `{% from "macros.twig" import hello %}
|
||||
{{ hello('World') }}`
|
||||
|
||||
// Register templates
|
||||
err := engine.RegisterString("macros.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("main.twig", mainTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering main.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the template
|
||||
result, err := engine.Render("main.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expected := "Hello, World!"
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromTagWithAlias tests the from tag with an alias
|
||||
func TestFromTagWithAlias(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template
|
||||
macroLib := `{% macro greet(name) %}Hello, {{ name }}!{% endmacro %}
|
||||
{% macro farewell(name) %}Goodbye, {{ name }}!{% endmacro %}`
|
||||
|
||||
// Template using from import with aliases
|
||||
template := `{% from "macros.twig" import greet as hello, farewell as bye %}
|
||||
{{ hello('John') }}
|
||||
{{ bye('Jane') }}`
|
||||
|
||||
// Register templates
|
||||
err := engine.RegisterString("macros.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("template.twig", template)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering template.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the template
|
||||
result, err := engine.Render("template.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedHello := "Hello, John!"
|
||||
expectedBye := "Goodbye, Jane!"
|
||||
|
||||
if !strings.Contains(result, expectedHello) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expectedHello, result)
|
||||
}
|
||||
|
||||
if !strings.Contains(result, expectedBye) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expectedBye, result)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromTagMultipleImports tests importing multiple macros from a template
|
||||
func TestFromTagMultipleImports(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template with multiple macros
|
||||
macroLib := `{% macro input(name, value) %}
|
||||
<input name="{{ name }}" value="{{ value }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label(text) %}
|
||||
<label>{{ text }}</label>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button(text) %}
|
||||
<button>{{ text }}</button>
|
||||
{% endmacro %}`
|
||||
|
||||
// Template importing multiple macros
|
||||
template := `{% from "form_macros.twig" import input, label, button %}
|
||||
<form>
|
||||
{{ label('Username') }}
|
||||
{{ input('username', 'john') }}
|
||||
{{ button('Submit') }}
|
||||
</form>`
|
||||
|
||||
// Register templates
|
||||
err := engine.RegisterString("form_macros.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering form_macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("form.twig", template)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering form.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the template
|
||||
result, err := engine.Render("form.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedElements := []string{
|
||||
`<label>Username</label>`,
|
||||
`<input name="username" value="john">`,
|
||||
`<button>Submit</button>`,
|
||||
}
|
||||
|
||||
for _, expected := range expectedElements {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFromTagMixedAliases tests importing some macros with aliases and some without
|
||||
func TestFromTagMixedAliases(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template
|
||||
macroLib := `{% macro header(text) %}
|
||||
<h1>{{ text }}</h1>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro paragraph(text) %}
|
||||
<p>{{ text }}</p>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro link(href, text) %}
|
||||
<a href="{{ href }}">{{ text }}</a>
|
||||
{% endmacro %}`
|
||||
|
||||
// Template with mixed alias usage
|
||||
template := `{% from "content_macros.twig" import header, paragraph as p, link as a %}
|
||||
{{ header('Title') }}
|
||||
{{ p('This is a paragraph.') }}
|
||||
{{ a('#', 'Click here') }}`
|
||||
|
||||
// Register templates
|
||||
err := engine.RegisterString("content_macros.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering content_macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("content.twig", template)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering content.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the template
|
||||
result, err := engine.Render("content.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedElements := []string{
|
||||
`<h1>Title</h1>`,
|
||||
`<p>This is a paragraph.</p>`,
|
||||
`<a href="#">Click here</a>`,
|
||||
}
|
||||
|
||||
for _, expected := range expectedElements {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
213
macros_test.go
213
macros_test.go
|
|
@ -125,7 +125,66 @@ func TestMacrosImport(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestMacrosFromImport tests selective importing macros
|
||||
// TestMacrosImportAs tests importing macros using the import as syntax
|
||||
func TestMacrosImportAs(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template
|
||||
macroLib := `
|
||||
{% macro input(name, value = '', type = 'text') %}
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro textarea(name, value = '') %}
|
||||
<textarea name="{{ name }}">{{ value }}</textarea>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button(name, value) %}
|
||||
<button name="{{ name }}">{{ value }}</button>
|
||||
{% endmacro %}
|
||||
`
|
||||
|
||||
// Main template that imports macros using import as syntax
|
||||
mainTemplate := `
|
||||
{% import "macro_lib.twig" as lib %}
|
||||
|
||||
<form>
|
||||
{{ lib.input('username', 'john') }}
|
||||
{{ lib.button('submit', 'Submit Form') }}
|
||||
</form>
|
||||
`
|
||||
|
||||
// Register both templates
|
||||
err := engine.RegisterString("macro_lib.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering macro_lib.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("import_as.twig", mainTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering import_as.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the main template
|
||||
result, err := engine.Render("import_as.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing/rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedHtml := []string{
|
||||
`<input type="text" name="username" value="john">`,
|
||||
`<button name="submit">Submit Form</button>`,
|
||||
}
|
||||
|
||||
for _, expected := range expectedHtml {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMacrosFromImport tests selective importing macros using the from import syntax
|
||||
func TestMacrosFromImport(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
|
|
@ -144,20 +203,25 @@ func TestMacrosFromImport(t *testing.T) {
|
|||
{% endmacro %}
|
||||
`
|
||||
|
||||
// Main template that selectively imports macros
|
||||
// Using import as syntax which has better support
|
||||
mainTemplate := `
|
||||
{% import "macro_lib.twig" as lib %}
|
||||
// Main template that selectively imports macros using from import syntax
|
||||
mainTemplate := `{% from "macro_lib.twig" import input, button %}
|
||||
|
||||
<form>
|
||||
{{ lib.input('username', 'john') }}
|
||||
{{ lib.button('submit', 'Submit Form') }}
|
||||
{{ input('username', 'john') }}
|
||||
{{ button('submit', 'Submit Form') }}
|
||||
</form>
|
||||
`
|
||||
|
||||
// Register both templates
|
||||
engine.RegisterString("macro_lib.twig", macroLib)
|
||||
engine.RegisterString("from_import.twig", mainTemplate)
|
||||
err := engine.RegisterString("macro_lib.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering macro_lib.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("from_import.twig", mainTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering from_import.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the main template
|
||||
result, err := engine.Render("from_import.twig", nil)
|
||||
|
|
@ -178,6 +242,137 @@ func TestMacrosFromImport(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestMacrosFromImportWithAliases tests importing macros with aliases using the from import syntax
|
||||
func TestMacrosFromImportWithAliases(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// Macro library template
|
||||
macroLib := `
|
||||
{% macro input(name, value = '', type = 'text') %}
|
||||
<input type="{{ type }}" name="{{ name }}" value="{{ value }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro textarea(name, value = '') %}
|
||||
<textarea name="{{ name }}">{{ value }}</textarea>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro button(name, value) %}
|
||||
<button name="{{ name }}">{{ value }}</button>
|
||||
{% endmacro %}
|
||||
`
|
||||
|
||||
// Main template that imports macros with aliases using from import syntax
|
||||
mainTemplate := `{% from "macro_lib.twig" import input as field, button as btn %}
|
||||
|
||||
<form>
|
||||
{{ field('username', 'john') }}
|
||||
{{ btn('submit', 'Submit Form') }}
|
||||
</form>
|
||||
`
|
||||
|
||||
// Register both templates
|
||||
err := engine.RegisterString("macro_lib.twig", macroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering macro_lib.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("from_import_aliases.twig", mainTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering from_import_aliases.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the main template
|
||||
result, err := engine.Render("from_import_aliases.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing/rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedHtml := []string{
|
||||
`<input type="text" name="username" value="john">`,
|
||||
`<button name="submit">Submit Form</button>`,
|
||||
}
|
||||
|
||||
for _, expected := range expectedHtml {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMixedImportApproaches tests using both import and from import syntax in the same template
|
||||
func TestMixedImportApproaches(t *testing.T) {
|
||||
engine := New()
|
||||
|
||||
// First macro library template
|
||||
formsMacroLib := `
|
||||
{% macro input(name, value = '') %}
|
||||
<input name="{{ name }}" value="{{ value }}">
|
||||
{% endmacro %}
|
||||
|
||||
{% macro bold(text) %}
|
||||
<b>{{ text }}</b>
|
||||
{% endmacro %}
|
||||
`
|
||||
|
||||
// Second macro library template
|
||||
layoutMacroLib := `
|
||||
{% macro header(text) %}
|
||||
<h1>{{ text }}</h1>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro box(content) %}
|
||||
<div class="box">{{ content }}</div>
|
||||
{% endmacro %}
|
||||
`
|
||||
|
||||
// Main template that uses both import approaches
|
||||
mainTemplate := `{% import "forms_macros.twig" as forms %}
|
||||
{% from "layout_macros.twig" import header %}
|
||||
|
||||
<div>
|
||||
{{ header('Hello') }}
|
||||
{{ forms.input('username', 'john') }}
|
||||
{{ forms.bold('Welcome') }}
|
||||
</div>
|
||||
`
|
||||
|
||||
// Register templates
|
||||
err := engine.RegisterString("forms_macros.twig", formsMacroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering forms_macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("layout_macros.twig", layoutMacroLib)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering layout_macros.twig: %v", err)
|
||||
}
|
||||
|
||||
err = engine.RegisterString("mixed_imports.twig", mainTemplate)
|
||||
if err != nil {
|
||||
t.Fatalf("Error registering mixed_imports.twig: %v", err)
|
||||
}
|
||||
|
||||
// Render the main template
|
||||
result, err := engine.Render("mixed_imports.twig", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error parsing/rendering template: %v", err)
|
||||
}
|
||||
|
||||
// Check the output
|
||||
expectedElements := []string{
|
||||
`<h1>Hello</h1>`,
|
||||
`<input name="username" value="john">`,
|
||||
`<b>Welcome</b>`,
|
||||
}
|
||||
|
||||
for _, expected := range expectedElements {
|
||||
if !strings.Contains(result, expected) {
|
||||
t.Errorf("Expected %q in result, but got: %s", expected, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMacrosWithContext tests macros with context variables
|
||||
func TestMacrosWithContext(t *testing.T) {
|
||||
engine := New()
|
||||
|
|
|
|||
65
parse_block.go
Normal file
65
parse_block.go
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package twig
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (p *Parser) parseBlock(parser *Parser) (Node, error) {
|
||||
// Get the line number of the block token
|
||||
blockLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Get the block name
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
||||
return nil, fmt.Errorf("expected block name at line %d", blockLine)
|
||||
}
|
||||
|
||||
blockName := parser.tokens[parser.tokenIndex].Value
|
||||
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 token after block name at line %d", blockLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the block body
|
||||
blockBody, err := parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect endblock tag
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START {
|
||||
return nil, fmt.Errorf("expected endblock tag at line %d", blockLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME ||
|
||||
parser.tokens[parser.tokenIndex].Value != "endblock" {
|
||||
return nil, fmt.Errorf("expected endblock at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Check for optional block name in endblock
|
||||
if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
||||
endBlockName := parser.tokens[parser.tokenIndex].Value
|
||||
if endBlockName != blockName {
|
||||
return nil, fmt.Errorf("mismatched block name, expected %s but got %s at line %d",
|
||||
blockName, endBlockName, parser.tokens[parser.tokenIndex].Line)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
}
|
||||
|
||||
// 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 token after endblock at line %d", parser.tokens[parser.tokenIndex-1].Line)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the block node
|
||||
blockNode := &BlockNode{
|
||||
name: blockName,
|
||||
body: blockBody,
|
||||
line: blockLine,
|
||||
}
|
||||
|
||||
return blockNode, nil
|
||||
}
|
||||
70
parse_do.go
Normal file
70
parse_do.go
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (p *Parser) parseDo(parser *Parser) (Node, error) {
|
||||
// Get the line number for error reporting
|
||||
doLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Check for special case: assignment expressions
|
||||
// These need to be handled specially since they're not normal expressions
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
||||
|
||||
varName := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_OPERATOR &&
|
||||
parser.tokens[parser.tokenIndex].Value == "=" {
|
||||
|
||||
// Skip the equals sign
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the right side expression
|
||||
expr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing expression in do assignment at line %d: %w", doLine, err)
|
||||
}
|
||||
|
||||
// Make sure we have the closing tag
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END {
|
||||
return nil, fmt.Errorf("expecting end of do tag at line %d", doLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Validate the variable name - it should not be a numeric literal
|
||||
if _, err := strconv.Atoi(varName); err == nil {
|
||||
return nil, fmt.Errorf("invalid variable name %q in do tag assignment at line %d", varName, doLine)
|
||||
}
|
||||
|
||||
// Create a SetNode instead of DoNode for assignments
|
||||
return &SetNode{
|
||||
name: varName,
|
||||
value: expr,
|
||||
line: doLine,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// If it wasn't an assignment, backtrack to parse it as a normal expression
|
||||
parser.tokenIndex -= 1
|
||||
}
|
||||
|
||||
// Parse the expression to be executed
|
||||
expr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing expression in do tag at line %d: %w", doLine, err)
|
||||
}
|
||||
|
||||
// Make sure we have the closing tag
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END {
|
||||
return nil, fmt.Errorf("expecting end of do tag at line %d", doLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create and return the DoNode
|
||||
return NewDoNode(expr, doLine), nil
|
||||
}
|
||||
28
parse_extends.go
Normal file
28
parse_extends.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package twig
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (p *Parser) parseExtends(parser *Parser) (Node, error) {
|
||||
// Get the line number of the extends token
|
||||
extendsLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Get the parent template expression
|
||||
parentExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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 token after extends at line %d", extendsLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the extends node
|
||||
extendsNode := &ExtendsNode{
|
||||
parent: parentExpr,
|
||||
line: extendsLine,
|
||||
}
|
||||
|
||||
return extendsNode, nil
|
||||
}
|
||||
143
parse_for.go
Normal file
143
parse_for.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
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
|
||||
}
|
||||
83
parse_from.go
Normal file
83
parse_from.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
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
|
||||
|
||||
// We need to manually extract the template path, import keyword, and macro(s) from
|
||||
// the current token. The tokenizer seems to 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)
|
||||
}
|
||||
147
parse_if.go
Normal file
147
parse_if.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// parseIf parses if/elseif/else/endif block structure
|
||||
// Examples:
|
||||
// {% if condition %}...{% endif %}
|
||||
// {% if condition %}...{% else %}...{% endif %}
|
||||
// {% if condition %}...{% elseif condition2 %}...{% else %}...{% endif %}
|
||||
func (p *Parser) parseIf(parser *Parser) (Node, error) {
|
||||
// Get the line number of the if token
|
||||
ifLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Parse the condition expression
|
||||
condition, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect the block end token (either regular or whitespace-trimming variant)
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end after if condition at line %d", ifLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the if body (statements between if and endif/else/elseif)
|
||||
ifBody, err := parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize conditions and bodies arrays with the initial if condition and body
|
||||
conditions := []Node{condition}
|
||||
bodies := [][]Node{ifBody}
|
||||
var elseBody []Node
|
||||
|
||||
// Keep track of whether we've seen an else block
|
||||
var hasElseBlock bool
|
||||
|
||||
// Process subsequent tags (elseif, else, endif)
|
||||
for {
|
||||
// We expect a block start token for elseif, else, or endif
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START {
|
||||
return nil, fmt.Errorf("unexpected end of template, expected endif at line %d", ifLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// We expect a name token (elseif, else, or endif)
|
||||
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)
|
||||
}
|
||||
|
||||
// Get the tag name
|
||||
blockName := parser.tokens[parser.tokenIndex].Value
|
||||
blockLine := parser.tokens[parser.tokenIndex].Line
|
||||
parser.tokenIndex++
|
||||
|
||||
// Process based on the tag type
|
||||
if blockName == "elseif" {
|
||||
// Check if we've already seen an else block - elseif can't come after else
|
||||
if hasElseBlock {
|
||||
return nil, fmt.Errorf("unexpected elseif after else at line %d", blockLine)
|
||||
}
|
||||
|
||||
// Handle elseif condition
|
||||
elseifCondition, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
||||
return nil, fmt.Errorf("expected block end after elseif condition at line %d", blockLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the elseif body
|
||||
elseifBody, err := parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add condition and body to our arrays
|
||||
conditions = append(conditions, elseifCondition)
|
||||
bodies = append(bodies, elseifBody)
|
||||
|
||||
// Continue checking for more elseif/else/endif tags
|
||||
} else if blockName == "else" {
|
||||
// Check if we've already seen an else block - can't have multiple else blocks
|
||||
if hasElseBlock {
|
||||
return nil, fmt.Errorf("multiple else blocks found at line %d", blockLine)
|
||||
}
|
||||
|
||||
// Mark that we've seen an else block
|
||||
hasElseBlock = true
|
||||
|
||||
// Expect block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
||||
return nil, fmt.Errorf("expected block end after else tag at line %d", blockLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the else body
|
||||
elseBody, err = parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// After else, we need to find endif next (handled in next iteration)
|
||||
} else if blockName == "endif" {
|
||||
// Expect block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
||||
return nil, fmt.Errorf("expected block end after endif at line %d", blockLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// We found the endif, we're done
|
||||
break
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected elseif, else, or endif, got %s at line %d", blockName, blockLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Create and return the if node
|
||||
ifNode := &IfNode{
|
||||
conditions: conditions,
|
||||
bodies: bodies,
|
||||
elseBranch: elseBody,
|
||||
line: ifLine,
|
||||
}
|
||||
|
||||
return ifNode, nil
|
||||
}
|
||||
|
||||
// Helper function to check if a token type is a block end token
|
||||
func isBlockEndToken(tokenType int) bool {
|
||||
return tokenType == TOKEN_BLOCK_END || tokenType == TOKEN_BLOCK_END_TRIM
|
||||
}
|
||||
|
||||
// Helper function to check if a token is any kind of variable end token (regular or trim variant)
|
||||
func isVarEndToken(tokenType int) bool {
|
||||
return tokenType == TOKEN_VAR_END || tokenType == TOKEN_VAR_END_TRIM
|
||||
}
|
||||
96
parse_import.go
Normal file
96
parse_import.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseImport(parser *Parser) (Node, error) {
|
||||
// Use debug logging if enabled
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
tokenIndex := parser.tokenIndex - 2
|
||||
LogVerbose("Parsing import, tokens available:")
|
||||
for i := 0; i < 10 && tokenIndex+i < len(parser.tokens); i++ {
|
||||
token := parser.tokens[tokenIndex+i]
|
||||
LogVerbose(" Token %d: Type=%d, Value=%q, Line=%d", i, token.Type, token.Value, token.Line)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the line number of the import token
|
||||
importLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Check for incorrectly tokenized import syntax
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_NAME &&
|
||||
strings.Contains(parser.tokens[parser.tokenIndex].Value, " as ") {
|
||||
|
||||
// Special handling for combined syntax like "path.twig as alias"
|
||||
parts := strings.SplitN(parser.tokens[parser.tokenIndex].Value, " as ", 2)
|
||||
if len(parts) == 2 {
|
||||
templatePath := strings.TrimSpace(parts[0])
|
||||
alias := strings.TrimSpace(parts[1])
|
||||
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Found combined import syntax: template=%q, alias=%q", templatePath, alias)
|
||||
}
|
||||
|
||||
// Create an expression node for the template path
|
||||
var templateExpr Node
|
||||
if strings.HasPrefix(templatePath, "\"") && strings.HasSuffix(templatePath, "\"") {
|
||||
// It's already a quoted string
|
||||
templateExpr = NewLiteralNode(templatePath[1:len(templatePath)-1], importLine)
|
||||
} else {
|
||||
// Create a string literal node
|
||||
templateExpr = NewLiteralNode(templatePath, importLine)
|
||||
}
|
||||
|
||||
// Skip to end of token
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after import statement at line %d", importLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create import node
|
||||
return NewImportNode(templateExpr, alias, importLine), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Standard parsing path
|
||||
// Get the template to import
|
||||
templateExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect 'as' keyword
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_NAME ||
|
||||
parser.tokens[parser.tokenIndex].Value != "as" {
|
||||
return nil, fmt.Errorf("expected 'as' after template path at line %d", importLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Get the alias name
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
||||
return nil, fmt.Errorf("expected identifier after 'as' at line %d", importLine)
|
||||
}
|
||||
|
||||
alias := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after import statement at line %d", importLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create import node
|
||||
return NewImportNode(templateExpr, alias, importLine), nil
|
||||
}
|
||||
245
parse_macro.go
Normal file
245
parse_macro.go
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseMacro(parser *Parser) (Node, error) {
|
||||
// Use debug logging if enabled
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
tokenIndex := parser.tokenIndex - 2
|
||||
LogVerbose("Parsing macro, tokens available:")
|
||||
for i := 0; i < 10 && tokenIndex+i < len(parser.tokens); i++ {
|
||||
token := parser.tokens[tokenIndex+i]
|
||||
LogVerbose(" Token %d: Type=%d, Value=%q, Line=%d", i, token.Type, token.Value, token.Line)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the line number of the macro token
|
||||
macroLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Get the macro name
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
||||
return nil, fmt.Errorf("expected macro name after macro keyword at line %d", macroLine)
|
||||
}
|
||||
|
||||
// Special handling for incorrectly tokenized macro declarations
|
||||
macroNameRaw := parser.tokens[parser.tokenIndex].Value
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Raw macro name: %s", macroNameRaw)
|
||||
}
|
||||
|
||||
// Check if the name contains parentheses (incorrectly tokenized)
|
||||
if strings.Contains(macroNameRaw, "(") {
|
||||
// Extract the actual name before the parenthesis
|
||||
parts := strings.SplitN(macroNameRaw, "(", 2)
|
||||
if len(parts) == 2 {
|
||||
macroName := parts[0]
|
||||
paramStr := "(" + parts[1]
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Fixed macro name: %s", macroName)
|
||||
LogVerbose("Parameter string: %s", paramStr)
|
||||
}
|
||||
|
||||
// Parse parameters
|
||||
var params []string
|
||||
defaults := make(map[string]Node)
|
||||
|
||||
// Simple parameter parsing - split by comma
|
||||
paramList := strings.TrimRight(paramStr[1:], ")")
|
||||
if paramList != "" {
|
||||
paramItems := strings.Split(paramList, ",")
|
||||
|
||||
for _, param := range paramItems {
|
||||
param = strings.TrimSpace(param)
|
||||
|
||||
// Check for default value
|
||||
if strings.Contains(param, "=") {
|
||||
parts := strings.SplitN(param, "=", 2)
|
||||
paramName := strings.TrimSpace(parts[0])
|
||||
defaultValue := strings.TrimSpace(parts[1])
|
||||
|
||||
params = append(params, paramName)
|
||||
|
||||
// Handle quoted strings in default values
|
||||
if (strings.HasPrefix(defaultValue, "'") && strings.HasSuffix(defaultValue, "'")) ||
|
||||
(strings.HasPrefix(defaultValue, "\"") && strings.HasSuffix(defaultValue, "\"")) {
|
||||
// Remove quotes
|
||||
strValue := defaultValue[1 : len(defaultValue)-1]
|
||||
defaults[paramName] = NewLiteralNode(strValue, macroLine)
|
||||
} else if defaultValue == "true" {
|
||||
defaults[paramName] = NewLiteralNode(true, macroLine)
|
||||
} else if defaultValue == "false" {
|
||||
defaults[paramName] = NewLiteralNode(false, macroLine)
|
||||
} else if i, err := strconv.Atoi(defaultValue); err == nil {
|
||||
defaults[paramName] = NewLiteralNode(i, macroLine)
|
||||
} else {
|
||||
// Fallback to string
|
||||
defaults[paramName] = NewLiteralNode(defaultValue, macroLine)
|
||||
}
|
||||
} else {
|
||||
params = append(params, param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip to the end of the token
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after macro declaration at line %d", macroLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the macro body
|
||||
bodyNodes, err := parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect endmacro tag
|
||||
if parser.tokenIndex+1 >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START_TRIM) ||
|
||||
parser.tokens[parser.tokenIndex+1].Type != TOKEN_NAME ||
|
||||
parser.tokens[parser.tokenIndex+1].Value != "endmacro" {
|
||||
return nil, fmt.Errorf("missing endmacro tag for macro '%s' at line %d",
|
||||
macroName, macroLine)
|
||||
}
|
||||
|
||||
// Skip {% endmacro %}
|
||||
parser.tokenIndex += 2 // Skip {% endmacro
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after endmacro at line %d", parser.tokens[parser.tokenIndex].Line)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the macro node
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Creating MacroNode with %d parameters and %d defaults", len(params), len(defaults))
|
||||
}
|
||||
return NewMacroNode(macroName, params, defaults, bodyNodes, macroLine), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Regular parsing path
|
||||
macroName := parser.tokens[parser.tokenIndex].Value
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Macro name: %s", macroName)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect opening parenthesis for parameters
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_PUNCTUATION ||
|
||||
parser.tokens[parser.tokenIndex].Value != "(" {
|
||||
return nil, fmt.Errorf("expected '(' after macro name at line %d", macroLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse parameters
|
||||
var params []string
|
||||
defaults := make(map[string]Node)
|
||||
|
||||
// If we don't have a closing parenthesis immediately, we have parameters
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_PUNCTUATION ||
|
||||
parser.tokens[parser.tokenIndex].Value != ")") {
|
||||
|
||||
for {
|
||||
// Get parameter name
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
||||
return nil, fmt.Errorf("expected parameter name at line %d", macroLine)
|
||||
}
|
||||
|
||||
paramName := parser.tokens[parser.tokenIndex].Value
|
||||
fmt.Println("DEBUG: Parameter name:", paramName)
|
||||
params = append(params, paramName)
|
||||
parser.tokenIndex++
|
||||
|
||||
// Check for default value
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_OPERATOR &&
|
||||
parser.tokens[parser.tokenIndex].Value == "=" {
|
||||
parser.tokenIndex++ // Skip =
|
||||
|
||||
// Parse default value expression
|
||||
defaultExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
fmt.Println("DEBUG: Error parsing default value:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaults[paramName] = defaultExpr
|
||||
}
|
||||
|
||||
// Check if we have more parameters
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value == "," {
|
||||
parser.tokenIndex++ // Skip comma
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Expect closing parenthesis
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_PUNCTUATION ||
|
||||
parser.tokens[parser.tokenIndex].Value != ")" {
|
||||
return nil, fmt.Errorf("expected ')' after macro parameters at line %d", macroLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after macro declaration at line %d", macroLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the macro body
|
||||
bodyNodes, err := parser.parseOuterTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Expect endmacro tag
|
||||
if parser.tokenIndex+1 >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_START_TRIM) ||
|
||||
parser.tokens[parser.tokenIndex+1].Type != TOKEN_NAME ||
|
||||
parser.tokens[parser.tokenIndex+1].Value != "endmacro" {
|
||||
return nil, fmt.Errorf("missing endmacro tag for macro '%s' at line %d",
|
||||
macroName, macroLine)
|
||||
}
|
||||
|
||||
// Skip {% endmacro %}
|
||||
parser.tokenIndex += 2 // Skip {% endmacro
|
||||
|
||||
// Expect block end
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after endmacro at line %d", parser.tokens[parser.tokenIndex].Line)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the macro node
|
||||
if IsDebugEnabled() && debugger.level >= DebugVerbose {
|
||||
LogVerbose("Creating MacroNode with %d parameters and %d defaults", len(params), len(defaults))
|
||||
}
|
||||
return NewMacroNode(macroName, params, defaults, bodyNodes, macroLine), nil
|
||||
}
|
||||
61
parse_set.go
Normal file
61
parse_set.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
package twig
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (p *Parser) parseSet(parser *Parser) (Node, error) {
|
||||
// Get the line number of the set token
|
||||
setLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Get the variable name
|
||||
if parser.tokenIndex >= len(parser.tokens) || parser.tokens[parser.tokenIndex].Type != TOKEN_NAME {
|
||||
return nil, fmt.Errorf("expected variable name after set at line %d", setLine)
|
||||
}
|
||||
|
||||
varName := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect '='
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_OPERATOR ||
|
||||
parser.tokens[parser.tokenIndex].Value != "=" {
|
||||
return nil, fmt.Errorf("expected '=' after variable name at line %d", setLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the value expression
|
||||
valueExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For expressions like 5 + 10, we need to parse both sides and make a binary node
|
||||
// Check if there's an operator after the first token
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_OPERATOR &&
|
||||
parser.tokens[parser.tokenIndex].Value != "=" {
|
||||
|
||||
// Get the operator
|
||||
operator := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the right side
|
||||
rightExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a binary node
|
||||
valueExpr = NewBinaryNode(operator, valueExpr, rightExpr, setLine)
|
||||
}
|
||||
|
||||
// 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 token after set expression at line %d", setLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the set node
|
||||
setNode := NewSetNode(varName, valueExpr, setLine)
|
||||
|
||||
return setNode, nil
|
||||
}
|
||||
132
parse_verbatim.go
Normal file
132
parse_verbatim.go
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseVerbatim parses a verbatim tag and its content
|
||||
func (p *Parser) parseVerbatim(parser *Parser) (Node, error) {
|
||||
// Get the line number of the verbatim token
|
||||
verbatimLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Expect the block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
||||
return nil, fmt.Errorf("expected block end after verbatim tag at line %d", verbatimLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Collect all content until we find the endverbatim tag
|
||||
var contentBuilder strings.Builder
|
||||
|
||||
for parser.tokenIndex < len(parser.tokens) {
|
||||
token := parser.tokens[parser.tokenIndex]
|
||||
|
||||
// Look for the endverbatim tag
|
||||
if token.Type == TOKEN_BLOCK_START || token.Type == TOKEN_BLOCK_START_TRIM {
|
||||
// Check if this is the endverbatim tag
|
||||
if parser.tokenIndex+1 < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex+1].Type == TOKEN_NAME &&
|
||||
parser.tokens[parser.tokenIndex+1].Value == "endverbatim" {
|
||||
|
||||
// Skip the block start and endverbatim name
|
||||
parser.tokenIndex += 2 // Now at the endverbatim token
|
||||
|
||||
// Expect the block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) || !isBlockEndToken(parser.tokens[parser.tokenIndex].Type) {
|
||||
return nil, fmt.Errorf("expected block end after endverbatim at line %d", token.Line)
|
||||
}
|
||||
parser.tokenIndex++ // Skip the block end token
|
||||
|
||||
// Create a verbatim node with the collected content
|
||||
return NewVerbatimNode(contentBuilder.String(), verbatimLine), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add this token's content to our verbatim content
|
||||
if token.Type == TOKEN_TEXT {
|
||||
contentBuilder.WriteString(token.Value)
|
||||
} else if token.Type == TOKEN_VAR_START || token.Type == TOKEN_VAR_START_TRIM {
|
||||
// For variable tags, preserve them as literal text
|
||||
contentBuilder.WriteString("{{")
|
||||
|
||||
// Skip variable start token and process until variable end
|
||||
parser.tokenIndex++
|
||||
|
||||
// Process tokens until variable end
|
||||
for parser.tokenIndex < len(parser.tokens) {
|
||||
innerToken := parser.tokens[parser.tokenIndex]
|
||||
|
||||
if innerToken.Type == TOKEN_VAR_END || innerToken.Type == TOKEN_VAR_END_TRIM {
|
||||
contentBuilder.WriteString("}}")
|
||||
break
|
||||
} else if innerToken.Type == TOKEN_NAME || innerToken.Type == TOKEN_STRING ||
|
||||
innerToken.Type == TOKEN_NUMBER || innerToken.Type == TOKEN_OPERATOR ||
|
||||
innerToken.Type == TOKEN_PUNCTUATION {
|
||||
contentBuilder.WriteString(innerToken.Value)
|
||||
}
|
||||
|
||||
parser.tokenIndex++
|
||||
}
|
||||
} else if token.Type == TOKEN_BLOCK_START || token.Type == TOKEN_BLOCK_START_TRIM {
|
||||
// For block tags, preserve them as literal text
|
||||
contentBuilder.WriteString("{%")
|
||||
|
||||
// Skip block start token and process until block end
|
||||
parser.tokenIndex++
|
||||
|
||||
// Process tokens until block end
|
||||
for parser.tokenIndex < len(parser.tokens) {
|
||||
innerToken := parser.tokens[parser.tokenIndex]
|
||||
|
||||
if innerToken.Type == TOKEN_BLOCK_END || innerToken.Type == TOKEN_BLOCK_END_TRIM {
|
||||
contentBuilder.WriteString("%}")
|
||||
break
|
||||
} else if innerToken.Type == TOKEN_NAME || innerToken.Type == TOKEN_STRING ||
|
||||
innerToken.Type == TOKEN_NUMBER || innerToken.Type == TOKEN_OPERATOR ||
|
||||
innerToken.Type == TOKEN_PUNCTUATION {
|
||||
// If this is the first TOKEN_NAME in a block, add a space after it
|
||||
if innerToken.Type == TOKEN_NAME && parser.tokenIndex > 0 &&
|
||||
(parser.tokens[parser.tokenIndex-1].Type == TOKEN_BLOCK_START ||
|
||||
parser.tokens[parser.tokenIndex-1].Type == TOKEN_BLOCK_START_TRIM) {
|
||||
contentBuilder.WriteString(innerToken.Value + " ")
|
||||
} else {
|
||||
contentBuilder.WriteString(innerToken.Value)
|
||||
}
|
||||
}
|
||||
|
||||
parser.tokenIndex++
|
||||
}
|
||||
} else if token.Type == TOKEN_COMMENT_START {
|
||||
// For comment tags, preserve them as literal text
|
||||
contentBuilder.WriteString("{#")
|
||||
|
||||
// Skip comment start token and process until comment end
|
||||
parser.tokenIndex++
|
||||
|
||||
// Process tokens until comment end
|
||||
for parser.tokenIndex < len(parser.tokens) {
|
||||
innerToken := parser.tokens[parser.tokenIndex]
|
||||
|
||||
if innerToken.Type == TOKEN_COMMENT_END {
|
||||
contentBuilder.WriteString("#}")
|
||||
break
|
||||
} else if innerToken.Type == TOKEN_TEXT {
|
||||
contentBuilder.WriteString(innerToken.Value)
|
||||
}
|
||||
|
||||
parser.tokenIndex++
|
||||
}
|
||||
}
|
||||
|
||||
parser.tokenIndex++
|
||||
|
||||
// Check for end of tokens
|
||||
if parser.tokenIndex >= len(parser.tokens) {
|
||||
return nil, fmt.Errorf("unexpected end of template, unclosed verbatim tag at line %d", verbatimLine)
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we never found the endverbatim tag
|
||||
return nil, fmt.Errorf("unclosed verbatim tag at line %d", verbatimLine)
|
||||
}
|
||||
177
parser_include.go
Normal file
177
parser_include.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package twig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (p *Parser) parseInclude(parser *Parser) (Node, error) {
|
||||
// Get the line number of the include token
|
||||
includeLine := parser.tokens[parser.tokenIndex-2].Line
|
||||
|
||||
// Get the template expression
|
||||
templateExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check for optional parameters
|
||||
var variables map[string]Node
|
||||
var ignoreMissing bool
|
||||
var onlyContext bool
|
||||
|
||||
// Look for 'with', 'ignore missing', or 'only'
|
||||
for parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
||||
|
||||
keyword := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
switch keyword {
|
||||
case "with":
|
||||
// Parse variables as a hash
|
||||
if variables == nil {
|
||||
variables = make(map[string]Node)
|
||||
}
|
||||
|
||||
// Check for opening brace
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value == "{" {
|
||||
parser.tokenIndex++ // Skip opening brace
|
||||
|
||||
// Parse key-value pairs
|
||||
for {
|
||||
// If we see a closing brace, we're done
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value == "}" {
|
||||
parser.tokenIndex++ // Skip closing brace
|
||||
break
|
||||
}
|
||||
|
||||
// Get the variable name - can be string literal or name token
|
||||
var varName string
|
||||
if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_STRING {
|
||||
// It's a quoted string key
|
||||
varName = parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
} else if parser.tokenIndex < len(parser.tokens) && parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
||||
// It's an unquoted key
|
||||
varName = parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected variable name or string at line %d", includeLine)
|
||||
}
|
||||
|
||||
// Expect colon or equals
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
((parser.tokens[parser.tokenIndex].Type != TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value != ":") &&
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_OPERATOR &&
|
||||
parser.tokens[parser.tokenIndex].Value != "=")) {
|
||||
return nil, fmt.Errorf("expected ':' or '=' after variable name at line %d", includeLine)
|
||||
}
|
||||
parser.tokenIndex++ // Skip : or =
|
||||
|
||||
// Parse the value expression
|
||||
varExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add to variables map
|
||||
variables[varName] = varExpr
|
||||
|
||||
// If there's a comma, skip it
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value == "," {
|
||||
parser.tokenIndex++
|
||||
}
|
||||
|
||||
// If we see whitespace, skip it
|
||||
for parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_TEXT &&
|
||||
strings.TrimSpace(parser.tokens[parser.tokenIndex].Value) == "" {
|
||||
parser.tokenIndex++
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If there's no opening brace, expect name-value pairs in the old format
|
||||
for parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_NAME {
|
||||
|
||||
// Get the variable name
|
||||
varName := parser.tokens[parser.tokenIndex].Value
|
||||
parser.tokenIndex++
|
||||
|
||||
// Expect '='
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_OPERATOR ||
|
||||
parser.tokens[parser.tokenIndex].Value != "=" {
|
||||
return nil, fmt.Errorf("expected '=' after variable name at line %d", includeLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Parse the value expression
|
||||
varExpr, err := parser.parseExpression()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add to variables map
|
||||
variables[varName] = varExpr
|
||||
|
||||
// If there's a comma, skip it
|
||||
if parser.tokenIndex < len(parser.tokens) &&
|
||||
parser.tokens[parser.tokenIndex].Type == TOKEN_PUNCTUATION &&
|
||||
parser.tokens[parser.tokenIndex].Value == "," {
|
||||
parser.tokenIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "ignore":
|
||||
// Check for 'missing' keyword
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_NAME ||
|
||||
parser.tokens[parser.tokenIndex].Value != "missing" {
|
||||
return nil, fmt.Errorf("expected 'missing' after 'ignore' at line %d", includeLine)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
ignoreMissing = true
|
||||
|
||||
case "only":
|
||||
onlyContext = true
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected keyword '%s' in include at line %d", keyword, includeLine)
|
||||
}
|
||||
}
|
||||
|
||||
// Expect the block end token
|
||||
if parser.tokenIndex >= len(parser.tokens) ||
|
||||
(parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END &&
|
||||
parser.tokens[parser.tokenIndex].Type != TOKEN_BLOCK_END_TRIM) {
|
||||
return nil, fmt.Errorf("expected block end token after include at line %d, found token type %d with value '%s'",
|
||||
includeLine,
|
||||
parser.tokens[parser.tokenIndex].Type,
|
||||
parser.tokens[parser.tokenIndex].Value)
|
||||
}
|
||||
parser.tokenIndex++
|
||||
|
||||
// Create the include node
|
||||
includeNode := &IncludeNode{
|
||||
template: templateExpr,
|
||||
variables: variables,
|
||||
ignoreMissing: ignoreMissing,
|
||||
only: onlyContext,
|
||||
line: includeLine,
|
||||
}
|
||||
|
||||
return includeNode, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue