...

Source file src/github.com/noirbizarre/gonja/builtins/statements/for.go

Documentation: github.com/noirbizarre/gonja/builtins/statements

     1  package statements
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  
     7  	"github.com/noirbizarre/gonja/exec"
     8  	"github.com/noirbizarre/gonja/nodes"
     9  	"github.com/noirbizarre/gonja/parser"
    10  	"github.com/noirbizarre/gonja/tokens"
    11  )
    12  
    13  type ForStmt struct {
    14  	key             string
    15  	value           string // only for maps: for key, value in map
    16  	objectEvaluator nodes.Expression
    17  	ifCondition     nodes.Expression
    18  
    19  	bodyWrapper  *nodes.Wrapper
    20  	emptyWrapper *nodes.Wrapper
    21  }
    22  
    23  func (stmt *ForStmt) Position() *tokens.Token { return stmt.bodyWrapper.Position() }
    24  func (stmt *ForStmt) String() string {
    25  	t := stmt.Position()
    26  	return fmt.Sprintf("ForStmt(Line=%d Col=%d)", t.Line, t.Col)
    27  }
    28  
    29  type LoopInfos struct {
    30  	index      int
    31  	index0     int
    32  	revindex   int
    33  	revindex0  int
    34  	first      bool
    35  	last       bool
    36  	length     int
    37  	depth      int
    38  	depth0     int
    39  	PrevItem   *exec.Value
    40  	NextItem   *exec.Value
    41  	_lastValue *exec.Value
    42  }
    43  
    44  func (li *LoopInfos) Cycle(va *exec.VarArgs) *exec.Value {
    45  	return va.Args[int(math.Mod(float64(li.index0), float64(len(va.Args))))]
    46  }
    47  
    48  func (li *LoopInfos) Changed(value *exec.Value) bool {
    49  	same := li._lastValue != nil && value.EqualValueTo(li._lastValue)
    50  	li._lastValue = value
    51  	return !same
    52  }
    53  
    54  func (node *ForStmt) Execute(r *exec.Renderer, tag *nodes.StatementBlock) (forError error) {
    55  	obj := r.Eval(node.objectEvaluator)
    56  	if obj.IsError() {
    57  		return obj
    58  	}
    59  
    60  	// Create loop struct
    61  	items := exec.NewDict()
    62  
    63  	// First iteration: filter values to ensure proper LoopInfos
    64  	obj.Iterate(func(idx, count int, key, value *exec.Value) bool {
    65  		sub := r.Inherit()
    66  		ctx := sub.Ctx
    67  		pair := &exec.Pair{}
    68  
    69  		// There's something to iterate over (correct type and at least 1 item)
    70  		// Update loop infos and public context
    71  		if node.value != "" && !key.IsString() && key.Len() == 2 {
    72  			key.Iterate(func(idx, count int, key, value *exec.Value) bool {
    73  				switch idx {
    74  				case 0:
    75  					ctx.Set(node.key, key)
    76  					pair.Key = key
    77  				case 1:
    78  					ctx.Set(node.value, key)
    79  					pair.Value = key
    80  				}
    81  				return true
    82  			}, func() {})
    83  		} else {
    84  			ctx.Set(node.key, key)
    85  			pair.Key = key
    86  			if value != nil {
    87  				ctx.Set(node.value, value)
    88  				pair.Value = value
    89  			}
    90  		}
    91  
    92  		if node.ifCondition != nil {
    93  			if !sub.Eval(node.ifCondition).IsTrue() {
    94  				return true
    95  			}
    96  		}
    97  		items.Pairs = append(items.Pairs, pair)
    98  		return true
    99  	}, func() {
   100  		// Nothing to iterate over (maybe wrong type or no items)
   101  		if node.emptyWrapper != nil {
   102  			sub := r.Inherit()
   103  			err := sub.ExecuteWrapper(node.emptyWrapper)
   104  			if err != nil {
   105  				forError = err
   106  			}
   107  		}
   108  	})
   109  
   110  	// 2nd pass: all values are defined, render
   111  	length := len(items.Pairs)
   112  	loop := &LoopInfos{
   113  		first:  true,
   114  		index0: -1,
   115  	}
   116  	for idx, pair := range items.Pairs {
   117  		r.EndTag(tag.Trim)
   118  		sub := r.Inherit()
   119  		ctx := sub.Ctx
   120  
   121  		ctx.Set(node.key, pair.Key)
   122  		if pair.Value != nil {
   123  			ctx.Set(node.value, pair.Value)
   124  		}
   125  
   126  		ctx.Set("loop", loop)
   127  		loop.index0 = idx
   128  		loop.index = loop.index0 + 1
   129  		if idx == 1 {
   130  			loop.first = false
   131  		}
   132  		if idx+1 == length {
   133  			loop.last = true
   134  		}
   135  		loop.revindex = length - idx
   136  		loop.revindex0 = length - (idx + 1)
   137  
   138  		if idx == 0 {
   139  			loop.PrevItem = exec.AsValue(nil)
   140  		} else {
   141  			pp := items.Pairs[idx-1]
   142  			if pp.Value != nil {
   143  				loop.PrevItem = exec.AsValue([2]*exec.Value{pp.Key, pp.Value})
   144  			} else {
   145  				loop.PrevItem = pp.Key
   146  			}
   147  		}
   148  
   149  		if idx == length-1 {
   150  			loop.NextItem = exec.AsValue(nil)
   151  		} else {
   152  			np := items.Pairs[idx+1]
   153  			if np.Value != nil {
   154  				loop.NextItem = exec.AsValue([2]*exec.Value{np.Key, np.Value})
   155  			} else {
   156  				loop.NextItem = np.Key
   157  			}
   158  		}
   159  
   160  		// Render elements with updated context
   161  		err := sub.ExecuteWrapper(node.bodyWrapper)
   162  		if err != nil {
   163  			return err
   164  		}
   165  	}
   166  
   167  	return forError
   168  }
   169  
   170  func forParser(p *parser.Parser, args *parser.Parser) (nodes.Statement, error) {
   171  	stmt := &ForStmt{}
   172  
   173  	// Arguments parsing
   174  	var valueToken *tokens.Token
   175  	keyToken := args.Match(tokens.Name)
   176  	if keyToken == nil {
   177  		return nil, args.Error("Expected an key identifier as first argument for 'for'-tag", nil)
   178  	}
   179  
   180  	if args.Match(tokens.Comma) != nil {
   181  		// Value name is provided
   182  		valueToken = args.Match(tokens.Name)
   183  		if valueToken == nil {
   184  			return nil, args.Error("Value name must be an identifier.", nil)
   185  		}
   186  	}
   187  
   188  	if args.MatchName("in") == nil {
   189  		return nil, args.Error("Expected keyword 'in'.", nil)
   190  	}
   191  
   192  	objectEvaluator, err := args.ParseExpression()
   193  	if err != nil {
   194  		return nil, err
   195  	}
   196  	stmt.objectEvaluator = objectEvaluator
   197  	stmt.key = keyToken.Val
   198  	if valueToken != nil {
   199  		stmt.value = valueToken.Val
   200  	}
   201  
   202  	if args.MatchName("if") != nil {
   203  		ifCondition, err := args.ParseExpression()
   204  		if err != nil {
   205  			return nil, err
   206  		}
   207  		stmt.ifCondition = ifCondition
   208  	}
   209  
   210  	if !args.End() {
   211  		return nil, args.Error("Malformed for-loop args.", nil)
   212  	}
   213  
   214  	// Body wrapping
   215  	wrapper, endargs, err := p.WrapUntil("else", "endfor")
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	stmt.bodyWrapper = wrapper
   220  
   221  	if !endargs.End() {
   222  		return nil, endargs.Error("Arguments not allowed here.", nil)
   223  	}
   224  
   225  	if wrapper.EndTag == "else" {
   226  		// if there's an else in the if-statement, we need the else-Block as well
   227  		wrapper, endargs, err = p.WrapUntil("endfor")
   228  		if err != nil {
   229  			return nil, err
   230  		}
   231  		stmt.emptyWrapper = wrapper
   232  
   233  		if !endargs.End() {
   234  			return nil, endargs.Error("Arguments not allowed here.", nil)
   235  		}
   236  	}
   237  
   238  	return stmt, nil
   239  }
   240  
   241  func init() {
   242  	All.Register("for", forParser)
   243  }
   244  

View as plain text