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
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
61 items := exec.NewDict()
62
63
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
70
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
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
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
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
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
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
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
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