~tapaal-ltl/verifypn/ltl-stubborn-set

« back to all changes in this revision

Viewing changes to src/PetriEngine/Colored/ColoredPetriNetBuilder.cpp

  • Committer: Nikolaj Jensen Ulrik
  • Date: 2021-04-06 13:21:53 UTC
  • mfrom: (226.1.27 ltl-trunk)
  • Revision ID: nikolaj@njulrik.dk-20210406132153-m11korhsc3m7mxfv
Merge ltl-trunk@253

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*
2
 
 * File:   ColoredPetriNetBuilder.cpp
3
 
 * Author: Klostergaard
4
 
 * 
5
 
 * Created on 17. februar 2018, 16:25
 
1
/* Copyright (C) 2020  Alexander Bilgram <alexander@bilgram.dk>,
 
2
 *                     Peter Haar Taankvist <ptaankvist@gmail.com>,
 
3
 *                     Thomas Pedersen <thomas.pedersen@stofanet.dk>
 
4
 *                     Andreas H. Klostergaard
 
5
 *
 
6
 * This program is free software: you can redistribute it and/or modify
 
7
 * it under the terms of the GNU General Public License as published by
 
8
 * the Free Software Foundation, either version 3 of the License, or
 
9
 * (at your option) any later version.
 
10
 *
 
11
 * This program is distributed in the hope that it will be useful,
 
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
 * GNU General Public License for more details.
 
15
 *
 
16
 * You should have received a copy of the GNU General Public License
 
17
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
6
18
 */
7
19
 
8
20
#include "PetriEngine/Colored/ColoredPetriNetBuilder.h"
30
42
            uint32_t next = _placenames.size();
31
43
            _places.emplace_back(Colored::Place {name, type, tokens});
32
44
            _placenames[name] = next;
 
45
 
 
46
            //set up place color fix points and initialize queue
 
47
            if (!tokens.empty()) {
 
48
                _placeFixpointQueue.emplace_back(next);
 
49
            }
 
50
 
 
51
            Colored::intervalTuple_t placeConstraints;
 
52
            Colored::ColorFixpoint colorFixpoint = {placeConstraints, !tokens.empty(), (uint32_t) type->productSize()};
 
53
 
 
54
            if(tokens.size() == type->size()){
 
55
                colorFixpoint.constraints.addInterval(type->getFullInterval());
 
56
            } else {
 
57
                uint32_t index = 0;
 
58
                for (auto colorPair : tokens) {
 
59
                    Colored::interval_t tokenConstraints;
 
60
                    colorPair.first->getColorConstraints(&tokenConstraints, &index);
 
61
 
 
62
                    colorFixpoint.constraints.addInterval(tokenConstraints);
 
63
                    index = 0;
 
64
                }
 
65
            }
 
66
         
 
67
            _placeColorFixpoints.push_back(colorFixpoint);            
33
68
        }
34
69
    }
35
70
 
85
120
        assert(t < _transitions.size());
86
121
        assert(p < _places.size());
87
122
 
 
123
        if (input) _placePostTransitionMap[p].emplace_back(t);
 
124
 
88
125
        Colored::Arc arc;
89
126
        arc.place = p;
90
127
        arc.transition = t;
91
128
        assert(expr != nullptr);
92
129
        arc.expr = std::move(expr);
93
130
        arc.input = input;
94
 
        _transitions[t].arcs.push_back(std::move(arc));
 
131
        input? _transitions[t].input_arcs.push_back(std::move(arc)): _transitions[t].output_arcs.push_back(std::move(arc));
95
132
    }
96
133
 
97
134
    void ColoredPetriNetBuilder::addColorType(const std::string& id, Colored::ColorType* type) {
99
136
    }
100
137
 
101
138
    void ColoredPetriNetBuilder::sort() {
102
 
 
103
 
    }
 
139
    }
 
140
 
 
141
    //----------------------- Color fixpoint -----------------------//
 
142
 
 
143
    void ColoredPetriNetBuilder::printPlaceTable() {
 
144
        for (auto place: _places) {
 
145
            auto placeID = _placenames[place.name];
 
146
            auto placeColorFixpoint = _placeColorFixpoints[placeID];
 
147
            std::cout << "Place: " << place.name << " in queue: " << placeColorFixpoint.inQueue  << " with colortype " << place.type->getName() << std::endl;
 
148
 
 
149
            for(auto fixpointPair : placeColorFixpoint.constraints._intervals) {
 
150
                std::cout << "[";
 
151
                for(auto range : fixpointPair._ranges) {
 
152
                    std::cout << range._lower << "-" << range._upper << ", ";
 
153
                }
 
154
                std::cout << "]"<< std::endl;                    
 
155
            }
 
156
            std::cout << std::endl;
 
157
        }
 
158
    }
 
159
 
 
160
    void ColoredPetriNetBuilder::computePlaceColorFixpoint(uint32_t maxIntervals, uint32_t maxIntervalsReduced, int32_t timeout) {
 
161
        //Start timers for timing color fixpoint creation and max interval reduction steps
 
162
        auto start = std::chrono::high_resolution_clock::now();
 
163
        std::chrono::_V2::system_clock::time_point end = std::chrono::high_resolution_clock::now();
 
164
        auto reduceTimer = std::chrono::high_resolution_clock::now();        
 
165
        while(!_placeFixpointQueue.empty()){
 
166
            uint32_t currentPlaceId = _placeFixpointQueue.back();
 
167
            //Reduce max interval once timeout passes
 
168
            if(timeout > 0 && std::chrono::duration_cast<std::chrono::seconds>(end - reduceTimer).count() >= timeout){
 
169
                maxIntervals = maxIntervalsReduced; 
 
170
                reduceTimer = std::chrono::high_resolution_clock::now();
 
171
            }
 
172
            
 
173
            _placeFixpointQueue.pop_back();
 
174
            _placeColorFixpoints[currentPlaceId].inQueue = false;
 
175
            std::vector<uint32_t> connectedTransitions = _placePostTransitionMap[currentPlaceId];
 
176
 
 
177
            for (uint32_t transitionId : connectedTransitions) {                
 
178
                Colored::Transition& transition = _transitions[transitionId];
 
179
                // Skip transitions that cannot add anything new,
 
180
                // such as transitions with only constants on their arcs that have been processed once 
 
181
                if (transition.considered) continue;
 
182
                bool transitionActivated = true;
 
183
                transition.variableMaps.clear();
 
184
 
 
185
                if(!_arcIntervals.count(transitionId)){
 
186
                    _arcIntervals[transitionId] = setupTransitionVars(transition);
 
187
                }           
 
188
                processInputArcs(transition, currentPlaceId, transitionId, transitionActivated, maxIntervals);
 
189
         
 
190
                //If there were colors which activated the transitions, compute the intervals produced
 
191
                if (transitionActivated) {
 
192
                    processOutputArcs(transition);
 
193
                }          
 
194
            }
 
195
            end = std::chrono::high_resolution_clock::now();
 
196
        }
 
197
 
 
198
        _fixPointCreationTime = (std::chrono::duration_cast<std::chrono::microseconds>(end - start).count())*0.000001;
 
199
 
 
200
        //printPlaceTable();
 
201
        _placeColorFixpoints.clear();
 
202
    }
 
203
 
 
204
    //Create Arc interval structures for the transition
 
205
    std::unordered_map<uint32_t, Colored::ArcIntervals> ColoredPetriNetBuilder::setupTransitionVars(Colored::Transition transition){
 
206
        std::unordered_map<uint32_t, Colored::ArcIntervals> res;
 
207
        for(auto arc : transition.input_arcs){
 
208
            std::set<const Colored::Variable *> variables;
 
209
            std::unordered_map<uint32_t, const Colored::Variable *> varPositions;
 
210
            std::unordered_map<const Colored::Variable *, std::vector<std::unordered_map<uint32_t, int32_t>>> varModifiersMap;
 
211
            arc.expr->getVariables(variables, varPositions, varModifiersMap);
 
212
 
 
213
            Colored::ArcIntervals newArcInterval(&_placeColorFixpoints[arc.place], varModifiersMap);
 
214
            res[arc.place] = newArcInterval;               
 
215
        }
 
216
        return res;
 
217
    }
 
218
 
 
219
    //Retrieve color intervals for the input arcs based on their places
 
220
    void ColoredPetriNetBuilder::getArcIntervals(Colored::Transition& transition, bool &transitionActivated, uint32_t max_intervals, uint32_t transitionId){
 
221
        for (auto arc : transition.input_arcs) {
 
222
            PetriEngine::Colored::ColorFixpoint& curCFP = _placeColorFixpoints[arc.place];   
 
223
            
 
224
            curCFP.constraints.restrict(max_intervals);
 
225
            _maxIntervals = std::max(_maxIntervals, (uint32_t) curCFP.constraints.size());
 
226
           
 
227
            Colored::ArcIntervals& arcInterval = _arcIntervals[transitionId][arc.place];
 
228
            uint32_t index = 0;
 
229
            arcInterval._intervalTupleVec.clear();
 
230
 
 
231
            if(!arc.expr->getArcIntervals(arcInterval, curCFP, &index, 0)){
 
232
                transitionActivated = false;
 
233
                return;
 
234
            }             
 
235
        }
 
236
    }
 
237
 
 
238
    void removeInvalidVarmaps(Colored::Transition& transition){
 
239
        std::vector<std::unordered_map<const Colored::Variable *, Colored::intervalTuple_t>> newVarmaps;
 
240
        for(auto& varMap : transition.variableMaps){
 
241
            bool validVarMap = true;      
 
242
            for(auto& varPair : varMap){
 
243
                if(!varPair.second.hasValidIntervals()){
 
244
                    validVarMap = false;
 
245
                    break;
 
246
                } else {
 
247
                    varPair.second.simplify();
 
248
                }
 
249
            } 
 
250
            if(validVarMap){
 
251
                newVarmaps.push_back(std::move(varMap));
 
252
            }                   
 
253
        }
 
254
        transition.variableMaps = std::move(newVarmaps);
 
255
    }
 
256
 
 
257
    //Retreive interval colors from the input arcs restricted by the transition guard
 
258
    void ColoredPetriNetBuilder::processInputArcs(Colored::Transition& transition, uint32_t currentPlaceId, uint32_t transitionId, bool &transitionActivated, uint32_t max_intervals) {     
 
259
        
 
260
        
 
261
        getArcIntervals(transition, transitionActivated, max_intervals, transitionId);  
 
262
        
 
263
 
 
264
        if(!transitionActivated){
 
265
            return;
 
266
        }
 
267
        if(intervalGenerator.getVarIntervals(transition.variableMaps, _arcIntervals[transitionId])){              
 
268
            if(transition.guard != nullptr) {
 
269
                transition.guard->restrictVars(transition.variableMaps);              
 
270
                removeInvalidVarmaps(transition);
 
271
 
 
272
                if(transition.variableMaps.empty()){
 
273
                    //Guard restrictions removed all valid intervals
 
274
                    transitionActivated = false;
 
275
                    return;
 
276
                }
 
277
            }
 
278
        } else {
 
279
            //Retrieving variable intervals failed
 
280
            transitionActivated = false;
 
281
        }                                            
 
282
    }
 
283
 
 
284
    void ColoredPetriNetBuilder::processOutputArcs(Colored::Transition& transition) {
 
285
        bool transitionHasVarOutArcs = false;
 
286
 
 
287
        for (auto& arc : transition.output_arcs) {
 
288
            Colored::ColorFixpoint& placeFixpoint = _placeColorFixpoints[arc.place];
 
289
            //used to check if colors are added to the place. The total distance between upper and
 
290
            //lower bounds should grow when more colors are added and as we cannot remove colors this
 
291
            //can be checked by summing the differences
 
292
            uint32_t colorsBefore = placeFixpoint.constraints.getContainedColors();
 
293
                
 
294
            std::set<const Colored::Variable *> variables;
 
295
            arc.expr->getVariables(variables);           
 
296
 
 
297
            if (!variables.empty()) {
 
298
                transitionHasVarOutArcs = true;
 
299
            }
 
300
 
 
301
            auto intervals = arc.expr->getOutputIntervals(transition.variableMaps);
 
302
            intervals.simplify();
 
303
 
 
304
            for(auto& interval : intervals._intervals){
 
305
                placeFixpoint.constraints.addInterval(std::move(interval));    
 
306
            }
 
307
 
 
308
            //Check if the place should be added to the queue
 
309
            if (!placeFixpoint.inQueue) {
 
310
                uint32_t colorsAfter = placeFixpoint.constraints.getContainedColors();
 
311
                if (colorsAfter > colorsBefore) {
 
312
                    _placeFixpointQueue.push_back(arc.place);
 
313
                    placeFixpoint.inQueue = true;
 
314
                }
 
315
            }                   
 
316
        }
 
317
        //If there are no variables among the out arcs of a transition 
 
318
        // and it has been activated, there is no reason to cosider it again
 
319
        if(!transitionHasVarOutArcs) {
 
320
            transition.considered = true;
 
321
        }
 
322
    }
 
323
 
 
324
    //----------------------- Unfolding -----------------------//
104
325
 
105
326
    PetriNetBuilder& ColoredPetriNetBuilder::unfold() {
106
327
        if (_stripped) assert(false);
107
328
        if (_isColored && !_unfolded) {
108
329
            auto start = std::chrono::high_resolution_clock::now();
109
 
            for (auto& place : _places) {
110
 
                unfoldPlace(place);
111
 
            }
112
330
 
113
331
            for (auto& transition : _transitions) {
114
332
                unfoldTransition(transition);
115
333
            }
116
 
 
 
334
            auto unfoldedPlaceMap = _ptBuilder.getPlaceNames();
 
335
            for (auto& place : _places) {
 
336
               handleOrphanPlace(place, unfoldedPlaceMap);
 
337
            }
117
338
            _unfolded = true;
118
339
            auto end = std::chrono::high_resolution_clock::now();
119
340
            _time = (std::chrono::duration_cast<std::chrono::microseconds>(end - start).count())*0.000001;
120
341
        }
121
 
 
122
342
        return _ptBuilder;
123
343
    }
124
344
 
125
 
    void ColoredPetriNetBuilder::unfoldPlace(Colored::Place& place) {
126
 
        for (size_t i = 0; i < place.type->size(); ++i) {
127
 
            std::string name = place.name + ";" + std::to_string(i);
128
 
            const Colored::Color* color = &place.type->operator[](i);
129
 
            _ptBuilder.addPlace(name, place.marking[color], 0.0, 0.0);
130
 
            _ptplacenames[place.name][color->getId()] = std::move(name);
131
 
            ++_nptplaces;
 
345
    //Due to the way we unfold places, we only unfold palces connected to an arc (which makes sense)
 
346
    //However, in queries asking about orphan places it cannot find these, as they have not been unfolded
 
347
    //so we make a placeholder place which just has tokens equal to the number of colored tokens
 
348
    //Ideally, orphan places should just be translated to a constant in the query
 
349
    void ColoredPetriNetBuilder::handleOrphanPlace(Colored::Place& place, std::unordered_map<std::string, uint32_t> unfoldedPlaceMap) {
 
350
        if(_ptplacenames.count(place.name) <= 0){
 
351
            
 
352
            std::string name = place.name + "_orphan";
 
353
            _ptBuilder.addPlace(name, place.marking.size(), 0.0, 0.0);
 
354
            _ptplacenames[place.name][0] = std::move(name);
 
355
        } else {
 
356
            uint32_t usedTokens = 0;
 
357
            
 
358
            for(std::pair<const uint32_t, std::string> unfoldedPlace : _ptplacenames[place.name]){                
 
359
                auto unfoldedMarking = _ptBuilder.initMarking();
 
360
                
 
361
                auto unfoldedPlaceId = unfoldedPlaceMap[unfoldedPlace.second];
 
362
                usedTokens += unfoldedMarking[unfoldedPlaceId];
 
363
            }
 
364
            
 
365
            if(place.marking.size() > usedTokens){
 
366
                std::string name = place.name + "_orphan";
 
367
                _ptBuilder.addPlace(name, place.marking.size() - usedTokens, 0.0, 0.0);
 
368
                _ptplacenames[place.name][UINT32_MAX] = std::move(name);
 
369
            }
132
370
        }
 
371
        
 
372
        //++_nptplaces;        
 
373
    }
 
374
    
 
375
    void ColoredPetriNetBuilder::unfoldPlace(const Colored::Place* place, const PetriEngine::Colored::Color *color) {        
 
376
        std::string name = place->name + "_" + std::to_string(color->getId());
 
377
        _ptBuilder.addPlace(name, place->marking[color], 0.0, 0.0);
 
378
        _ptplacenames[place->name][color->getId()] = std::move(name);
 
379
        ++_nptplaces; 
133
380
    }
134
381
 
135
382
    void ColoredPetriNetBuilder::unfoldTransition(Colored::Transition& transition) {
136
 
        BindingGenerator gen(transition, _arcs, _colors);
 
383
        FixpointBindingGenerator gen(transition, _colors);
137
384
        size_t i = 0;
138
 
        for (auto& b : gen) {
139
 
            std::string name = transition.name + ";" + std::to_string(i++);
 
385
        for (auto b : gen) { 
 
386
            std::string name = transition.name + "_" + std::to_string(i++);
140
387
            _ptBuilder.addTransition(name, 0.0, 0.0);
141
388
            _pttransitionnames[transition.name].push_back(name);
142
389
            ++_npttransitions;
143
 
            for (auto& arc : transition.arcs) {
144
 
                unfoldArc(arc, b, name);
145
 
            }
146
 
        }
 
390
            for (auto& arc : transition.input_arcs) {
 
391
                unfoldArc(arc, b, name, true );
 
392
            }
 
393
            for (auto& arc : transition.output_arcs) {
 
394
                unfoldArc(arc, b, name, false);
 
395
            }
 
396
        }    
147
397
    }
148
398
 
149
 
    void ColoredPetriNetBuilder::unfoldArc(Colored::Arc& arc, Colored::ExpressionContext::BindingMap& binding, std::string& tName) {
 
399
    void ColoredPetriNetBuilder::unfoldArc(Colored::Arc& arc, Colored::ExpressionContext::BindingMap& binding, std::string& tName, bool input) {
150
400
        Colored::ExpressionContext context {binding, _colors};
151
 
        auto ms = arc.expr->eval(context);
 
401
        auto ms = arc.expr->eval(context);       
152
402
 
153
403
        for (const auto& color : ms) {
154
404
            if (color.second == 0) {
155
405
                continue;
156
406
            }
157
 
            const std::string& pName = _ptplacenames[_places[arc.place].name][color.first->getId()];
 
407
            const PetriEngine::Colored::Place& place = _places[arc.place];
 
408
            const std::string& pName = _ptplacenames[place.name][color.first->getId()];
 
409
            if (pName.empty()) {
 
410
                unfoldPlace(&place, color.first);               
 
411
            }
158
412
            if (arc.input) {
159
413
                _ptBuilder.addInputArc(pName, tName, false, color.second);
160
414
            } else {
164
418
        }
165
419
    }
166
420
 
 
421
    //----------------------- Strip Colors -----------------------//
 
422
 
167
423
    PetriNetBuilder& ColoredPetriNetBuilder::stripColors() {
168
424
        if (_unfolded) assert(false);
169
425
        if (_isColored && !_stripped) {
173
429
 
174
430
            for (auto& transition : _transitions) {
175
431
                _ptBuilder.addTransition(transition.name, 0.0, 0.0);
176
 
                for (auto& arc : transition.arcs) {
177
 
                    try {
178
 
                        if (arc.input) {
179
 
                            _ptBuilder.addInputArc(_places[arc.place].name, _transitions[arc.transition].name, false,
180
 
                                                   arc.expr->weight());
181
 
                        } else {
182
 
                            _ptBuilder.addOutputArc(_transitions[arc.transition].name, _places[arc.place].name,
183
 
                                                    arc.expr->weight());
184
 
                        }
185
 
                    } catch (Colored::WeightException& e) {
186
 
                        std::cerr << "Exception on arc: " << arcToString(arc) << std::endl;
 
432
                for (auto& arc : transition.input_arcs) {
 
433
                    try {
 
434
                        _ptBuilder.addInputArc(_places[arc.place].name, _transitions[arc.transition].name, false,
 
435
                                                arc.expr->weight());
 
436
                    } catch (Colored::WeightException& e) {
 
437
                        std::cerr << "Exception on input arc: " << arcToString(arc) << std::endl;
 
438
                        std::cerr << "In expression: " << arc.expr->toString() << std::endl;
 
439
                        std::cerr << e.what() << std::endl;
 
440
                        exit(ErrorCode);
 
441
                    }
 
442
                }
 
443
                for (auto& arc : transition.output_arcs) {
 
444
                    try {
 
445
                        _ptBuilder.addOutputArc(_transitions[arc.transition].name, _places[arc.place].name,
 
446
                                                arc.expr->weight());
 
447
                    } catch (Colored::WeightException& e) {
 
448
                        std::cerr << "Exception on output arc: " << arcToString(arc) << std::endl;
187
449
                        std::cerr << "In expression: " << arc.expr->toString() << std::endl;
188
450
                        std::cerr << e.what() << std::endl;
189
451
                        exit(ErrorCode);
202
464
        return !arc.input ? "(" + _transitions[arc.transition].name + ", " + _places[arc.place].name + ")" :
203
465
               "(" + _places[arc.place].name + ", " + _transitions[arc.transition].name + ")";
204
466
    }
205
 
 
206
 
    BindingGenerator::Iterator::Iterator(BindingGenerator* generator)
207
 
            : _generator(generator)
208
 
    {
209
 
    }
210
 
 
211
 
    bool BindingGenerator::Iterator::operator==(Iterator& other) {
212
 
        return _generator == other._generator;
213
 
    }
214
 
 
215
 
    bool BindingGenerator::Iterator::operator!=(Iterator& other) {
216
 
        return _generator != other._generator;
217
 
    }
218
 
 
219
 
    BindingGenerator::Iterator& BindingGenerator::Iterator::operator++() {
220
 
        _generator->nextBinding();
221
 
        if (_generator->isInitial()) _generator = nullptr;
222
 
        return *this;
223
 
    }
224
 
 
225
 
    const Colored::ExpressionContext::BindingMap BindingGenerator::Iterator::operator++(int) {
226
 
        auto prev = _generator->currentBinding();
227
 
        ++*this;
228
 
        return prev;
229
 
    }
230
 
 
231
 
    Colored::ExpressionContext::BindingMap& BindingGenerator::Iterator::operator*() {
232
 
        return _generator->currentBinding();
233
 
    }
234
 
 
235
 
    BindingGenerator::BindingGenerator(Colored::Transition& transition,
236
 
            const std::vector<Colored::Arc>& arcs,
237
 
            ColoredPetriNetBuilder::ColorTypeMap& colorTypes)
238
 
        : _colorTypes(colorTypes)
239
 
    {
240
 
        _expr = transition.guard;
241
 
        std::set<Colored::Variable*> variables;
242
 
        if (_expr != nullptr) {
243
 
            _expr->getVariables(variables);
244
 
        }
245
 
        for (auto arc : transition.arcs) {
246
 
            assert(arc.expr != nullptr);
247
 
            arc.expr->getVariables(variables);
248
 
        }
249
 
        for (auto var : variables) {
250
 
            _bindings[var->name] = &var->colorType->operator[](0);
251
 
        }
252
 
        
253
 
        if (!eval())
254
 
            nextBinding();
255
 
    }
256
 
 
257
 
    bool BindingGenerator::eval() {
258
 
        if (_expr == nullptr)
259
 
            return true;
260
 
 
261
 
        Colored::ExpressionContext context {_bindings, _colorTypes};
262
 
        return _expr->eval(context);
263
 
    }
264
 
 
265
 
    Colored::ExpressionContext::BindingMap& BindingGenerator::nextBinding() {
266
 
        bool test = false;
267
 
        while (!test) {
268
 
            for (auto& _binding : _bindings) {
269
 
                _binding.second = &_binding.second->operator++();
270
 
                if (_binding.second->getId() != 0) {
271
 
                    break;
272
 
                }
273
 
            }
274
 
 
275
 
            if (isInitial())
276
 
                break;
277
 
 
278
 
            test = eval();
279
 
        }
280
 
        return _bindings;
281
 
    }
282
 
 
283
 
    Colored::ExpressionContext::BindingMap& BindingGenerator::currentBinding() {
284
 
        return _bindings;
285
 
    }
286
 
 
287
 
    bool BindingGenerator::isInitial() const {
288
 
        for (auto& b : _bindings) {
289
 
            if (b.second->getId() != 0) return false;
290
 
        }
291
 
        return true;
292
 
    }
293
 
 
294
 
    BindingGenerator::Iterator BindingGenerator::begin() {
295
 
        return {this};
296
 
    }
297
 
 
298
 
    BindingGenerator::Iterator BindingGenerator::end() {
299
 
        return {nullptr};
300
 
    }
301
467
}
302
 
 
 
468
    
 
469
 
 
 
b'\\ No newline at end of file'