2013-05-08 16:30:24 +05:30
|
|
|
#ifndef PERSISTENT_DATA_DATA_STRUCTURES_DAMAGE_VISITOR_H
|
|
|
|
#define PERSISTENT_DATA_DATA_STRUCTURES_DAMAGE_VISITOR_H
|
|
|
|
|
|
|
|
#include "persistent-data/data-structures/btree.h"
|
2016-04-02 14:45:00 +05:30
|
|
|
#include "persistent-data/data-structures/btree_node_checker.h"
|
2013-05-28 16:21:44 +05:30
|
|
|
#include "persistent-data/run.h"
|
2013-05-08 16:30:24 +05:30
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
namespace persistent_data {
|
2013-05-08 21:08:38 +05:30
|
|
|
namespace btree_detail {
|
|
|
|
struct damage {
|
|
|
|
typedef boost::shared_ptr<damage> ptr;
|
|
|
|
|
2013-05-28 16:50:05 +05:30
|
|
|
damage(run<uint64_t> lost_keys,
|
2013-05-08 21:08:38 +05:30
|
|
|
std::string const &desc)
|
2013-05-17 16:05:46 +05:30
|
|
|
: lost_keys_(lost_keys),
|
2013-05-08 21:08:38 +05:30
|
|
|
desc_(desc) {
|
|
|
|
}
|
|
|
|
|
2013-05-28 16:50:05 +05:30
|
|
|
run<uint64_t> lost_keys_;
|
2013-05-08 21:08:38 +05:30
|
|
|
std::string desc_;
|
|
|
|
};
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-13 17:06:57 +05:30
|
|
|
inline std::ostream &operator <<(std::ostream &out, damage const &d) {
|
2013-05-17 16:05:46 +05:30
|
|
|
out << "btree damage[lost_keys = " << d.lost_keys_
|
2013-05-13 17:06:57 +05:30
|
|
|
<< ", \"" << d.desc_ << "\"]";
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2016-02-27 12:51:23 +05:30
|
|
|
class noop_damage_visitor {
|
|
|
|
public:
|
|
|
|
virtual void visit(btree_path const &path, damage const &d) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-05-15 18:07:30 +05:30
|
|
|
// Tracks damage in a single level btree. Use multiple
|
|
|
|
// trackers if you have a multilayer tree.
|
2013-05-13 15:57:38 +05:30
|
|
|
class damage_tracker {
|
|
|
|
public:
|
|
|
|
damage_tracker()
|
|
|
|
: damaged_(false),
|
|
|
|
damage_begin_(0) {
|
|
|
|
}
|
|
|
|
|
2013-05-28 17:30:30 +05:30
|
|
|
typedef run<uint64_t> run64;
|
2013-05-28 16:50:05 +05:30
|
|
|
typedef boost::optional<run64> maybe_run64;
|
2013-05-13 15:57:38 +05:30
|
|
|
|
|
|
|
void bad_node() {
|
|
|
|
damaged_ = true;
|
|
|
|
}
|
|
|
|
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 good_internal(block_address begin) {
|
|
|
|
maybe_run64 r;
|
2013-05-13 15:57:38 +05:30
|
|
|
|
|
|
|
if (damaged_) {
|
2013-05-28 16:50:05 +05:30
|
|
|
r = maybe_run64(run64(damage_begin_, begin));
|
2013-05-13 15:57:38 +05:30
|
|
|
damaged_ = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
damage_begin_ = begin;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2013-05-23 19:07:24 +05:30
|
|
|
// remember 'end' is the one-past-the-end value, so
|
2013-05-13 15:57:38 +05:30
|
|
|
// take the last key in the leaf and add one.
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 good_leaf(block_address begin, block_address end) {
|
|
|
|
maybe_run64 r;
|
2013-05-13 15:57:38 +05:30
|
|
|
|
|
|
|
if (damaged_) {
|
2013-05-28 16:50:05 +05:30
|
|
|
r = maybe_run64(run64(damage_begin_, begin));
|
2013-05-13 15:57:38 +05:30
|
|
|
damaged_ = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
damage_begin_ = end;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 end() {
|
2015-05-26 17:19:27 +05:30
|
|
|
maybe_run64 r;
|
|
|
|
|
2013-05-13 15:57:38 +05:30
|
|
|
if (damaged_)
|
2015-05-26 17:19:27 +05:30
|
|
|
r = maybe_run64(damage_begin_);
|
2013-05-13 15:57:38 +05:30
|
|
|
else
|
2015-05-26 17:19:27 +05:30
|
|
|
r = maybe_run64();
|
|
|
|
|
|
|
|
damaged_ = false;
|
|
|
|
damage_begin_ = 0;
|
|
|
|
|
|
|
|
return r;
|
2013-05-13 15:57:38 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
bool damaged_;
|
|
|
|
block_address damage_begin_;
|
|
|
|
};
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-20 17:01:47 +05:30
|
|
|
// As we walk a btree we need to know if we've moved into a
|
|
|
|
// different sub tree (by looking at the btree_path).
|
|
|
|
class path_tracker {
|
|
|
|
public:
|
2014-08-21 15:55:07 +05:30
|
|
|
path_tracker() {
|
|
|
|
// We push an empty path, to ensure there
|
|
|
|
// is always a current_path.
|
|
|
|
paths_.push_back(btree_path());
|
|
|
|
}
|
|
|
|
|
2013-05-20 17:01:47 +05:30
|
|
|
// returns the old path if the tree has changed.
|
2014-07-30 16:51:34 +05:30
|
|
|
btree_path const *next_path(btree_path const &p) {
|
|
|
|
if (p != current_path()) {
|
|
|
|
if (paths_.size() == 2)
|
|
|
|
paths_.pop_front();
|
|
|
|
paths_.push_back(p);
|
|
|
|
|
|
|
|
return &paths_.front();
|
2013-05-20 17:01:47 +05:30
|
|
|
}
|
|
|
|
|
2014-07-30 16:51:34 +05:30
|
|
|
return NULL;
|
2013-05-20 17:01:47 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
btree_path const ¤t_path() const {
|
2014-07-30 16:51:34 +05:30
|
|
|
return paths_.back();
|
2013-05-20 17:01:47 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2014-07-30 16:51:34 +05:30
|
|
|
std::list<btree_path> paths_;
|
2013-05-20 17:01:47 +05:30
|
|
|
};
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
// This class implements consistency checking for the btrees. It
|
|
|
|
// also allows the caller to visit all accessible values.
|
|
|
|
|
|
|
|
// Derive from this if you want some additional checks. It's worth
|
|
|
|
// summarising what is checked:
|
|
|
|
|
|
|
|
//
|
|
|
|
// Implemented
|
|
|
|
// -----------
|
|
|
|
//
|
|
|
|
// - block_nr
|
|
|
|
// - nr_entries < max_entries
|
|
|
|
// - max_entries fits in block
|
|
|
|
// - max_entries is divisible by 3
|
|
|
|
// - nr_entries > minimum (except for root nodes)
|
|
|
|
//
|
|
|
|
// Not implemented
|
|
|
|
// ---------------
|
|
|
|
//
|
|
|
|
// - leaf | internal flags (this can be inferred from siblings)
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
template <typename ValueVisitor, typename DamageVisitor, uint32_t Levels, typename ValueTraits>
|
|
|
|
class btree_damage_visitor : public btree<Levels, ValueTraits>::visitor {
|
|
|
|
public:
|
|
|
|
typedef btree_detail::node_location node_location;
|
2013-05-28 16:50:05 +05:30
|
|
|
typedef run<block_address> run64;
|
|
|
|
typedef boost::optional<run64> maybe_run64;
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-12-11 22:58:14 +05:30
|
|
|
btree_damage_visitor(ValueVisitor &value_visitor,
|
2016-03-31 20:38:14 +05:30
|
|
|
DamageVisitor &damage_visitor)
|
|
|
|
: avoid_repeated_visits_(true),
|
2013-05-17 15:59:34 +05:30
|
|
|
value_visitor_(value_visitor),
|
|
|
|
damage_visitor_(damage_visitor) {
|
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool visit_internal(node_location const &loc,
|
2013-06-20 18:57:40 +05:30
|
|
|
btree_detail::node_ref<block_traits> const &n) {
|
2013-05-20 17:01:47 +05:30
|
|
|
update_path(loc.path);
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return check_internal(loc, n);
|
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool visit_internal_leaf(node_location const &loc,
|
2013-06-20 18:57:40 +05:30
|
|
|
btree_detail::node_ref<block_traits> const &n) {
|
2013-05-20 17:01:47 +05:30
|
|
|
update_path(loc.path);
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return check_leaf(loc, n);
|
|
|
|
}
|
2013-05-09 18:01:04 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool visit_leaf(node_location const &loc,
|
|
|
|
btree_detail::node_ref<ValueTraits> const &n) {
|
2013-05-20 17:01:47 +05:30
|
|
|
update_path(loc.path);
|
|
|
|
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool r = check_leaf(loc, n);
|
2013-05-09 18:01:04 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
// If anything goes wrong with the checks, we skip
|
|
|
|
// the value visiting.
|
|
|
|
if (!r)
|
|
|
|
return false;
|
2013-05-09 18:01:04 +05:30
|
|
|
|
2013-05-17 16:35:13 +05:30
|
|
|
visit_values(loc.path, n);
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return true;
|
|
|
|
}
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void visit_complete() {
|
|
|
|
end_walk();
|
|
|
|
}
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
typedef typename btree<Levels, ValueTraits>::visitor::error_outcome error_outcome;
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
error_outcome error_accessing_node(node_location const &l, block_address b,
|
|
|
|
std::string const &what) {
|
2015-09-30 05:30:00 +05:30
|
|
|
update_path(l.path);
|
2013-05-17 15:59:34 +05:30
|
|
|
report_damage(what);
|
|
|
|
return btree<Levels, ValueTraits>::visitor::EXCEPTION_HANDLED;
|
|
|
|
}
|
2013-05-09 18:01:04 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
private:
|
2013-05-17 16:35:13 +05:30
|
|
|
void visit_values(btree_path const &path,
|
|
|
|
node_ref<ValueTraits> const &n) {
|
2014-07-30 16:51:34 +05:30
|
|
|
btree_path p2(path);
|
2013-05-17 15:59:34 +05:30
|
|
|
unsigned nr = n.get_nr_entries();
|
2013-05-23 19:07:24 +05:30
|
|
|
for (unsigned i = 0; i < nr; i++) {
|
|
|
|
p2.push_back(n.key_at(i));
|
|
|
|
value_visitor_.visit(p2, n.value_at(i));
|
2014-07-30 16:51:34 +05:30
|
|
|
p2.pop_back();
|
2013-05-23 19:07:24 +05:30
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
}
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool check_internal(node_location const &loc,
|
2013-06-20 18:57:40 +05:30
|
|
|
btree_detail::node_ref<block_traits> const &n) {
|
2016-04-02 14:45:00 +05:30
|
|
|
if (already_visited(n))
|
2013-05-17 15:59:34 +05:30
|
|
|
return false;
|
2016-04-02 14:45:00 +05:30
|
|
|
else if (!checker_.check_block_nr(n) ||
|
|
|
|
!checker_.check_value_size(n) ||
|
|
|
|
!checker_.check_max_entries(n) ||
|
|
|
|
!checker_.check_nr_entries(n, loc.is_sub_root()) ||
|
|
|
|
!checker_.check_ordered_keys(n) ||
|
|
|
|
!checker_.check_parent_key(n, loc.is_sub_root() ? boost::optional<uint64_t>() : loc.key)) {
|
|
|
|
report_damage(checker_.get_last_error_string());
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2015-05-26 16:36:34 +05:30
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
if (loc.is_sub_root())
|
|
|
|
new_root(loc.level());
|
2015-05-26 16:36:34 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
good_internal(n.key_at(0));
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return true;
|
2013-05-08 16:30:24 +05:30
|
|
|
}
|
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
template <typename ValueTraits2>
|
|
|
|
bool check_leaf(node_location const &loc,
|
|
|
|
btree_detail::node_ref<ValueTraits2> const &n) {
|
|
|
|
if (already_visited(n))
|
2013-05-17 15:59:34 +05:30
|
|
|
return false;
|
2016-04-02 14:45:00 +05:30
|
|
|
else if (!checker_.check_block_nr(n) ||
|
|
|
|
!checker_.check_value_size(n) ||
|
|
|
|
!checker_.check_max_entries(n) ||
|
|
|
|
!checker_.check_nr_entries(n, loc.is_sub_root()) ||
|
|
|
|
!checker_.check_ordered_keys(n) ||
|
|
|
|
!checker_.check_parent_key(n, loc.is_sub_root() ? boost::optional<uint64_t>() : loc.key)) {
|
|
|
|
report_damage(checker_.get_last_error_string());
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return false;
|
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
if (loc.is_sub_root())
|
|
|
|
new_root(loc.level());
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
bool r = checker_.check_leaf_key(n, last_leaf_key_[loc.level()]);
|
|
|
|
if (!r)
|
|
|
|
report_damage(checker_.get_last_error_string());
|
|
|
|
else if (n.get_nr_entries() > 0) {
|
|
|
|
last_leaf_key_[loc.level()] = n.key_at(n.get_nr_entries() - 1);
|
|
|
|
good_leaf(n.key_at(0), n.key_at(n.get_nr_entries() - 1) + 1);
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
return r;
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
template <typename node>
|
2016-04-02 14:45:00 +05:30
|
|
|
bool already_visited(node const &n) {
|
|
|
|
block_address b = n.get_location();
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
if (avoid_repeated_visits_) {
|
|
|
|
if (seen_.count(b) > 0)
|
|
|
|
return true;
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
seen_.insert(b);
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
return false;
|
2013-05-08 16:30:24 +05:30
|
|
|
}
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void new_root(unsigned level) {
|
|
|
|
// we're starting a new subtree, so should
|
|
|
|
// reset the last_leaf value.
|
|
|
|
last_leaf_key_[level] = boost::optional<uint64_t>();
|
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
//--------------------------------
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
// damage tracking
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void report_damage(std::string const &desc) {
|
|
|
|
damage_reasons_.push_back(desc);
|
|
|
|
dt_.bad_node();
|
|
|
|
}
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void good_internal(block_address b) {
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 mr = dt_.good_internal(b);
|
2013-05-17 15:59:34 +05:30
|
|
|
if (mr)
|
2013-05-20 17:01:47 +05:30
|
|
|
issue_damage(path_tracker_.current_path(), *mr);
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void good_leaf(block_address b, block_address e) {
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 mr = dt_.good_leaf(b, e);
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
if (mr)
|
2013-05-20 17:01:47 +05:30
|
|
|
issue_damage(path_tracker_.current_path(), *mr);
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void end_walk() {
|
2013-05-20 17:45:51 +05:30
|
|
|
maybe_issue_damage(path_tracker_.current_path());
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-28 16:50:05 +05:30
|
|
|
void issue_damage(btree_path const &path, run64 const &r) {
|
2013-05-17 16:05:46 +05:30
|
|
|
damage d(r, build_damage_desc());
|
2013-05-17 15:59:34 +05:30
|
|
|
clear_damage_desc();
|
2013-05-20 17:01:47 +05:30
|
|
|
damage_visitor_.visit(path, d);
|
2013-05-17 15:59:34 +05:30
|
|
|
}
|
2013-05-13 17:06:57 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
std::string build_damage_desc() const {
|
|
|
|
std::string r;
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
std::list<std::string>::const_iterator it, end = damage_reasons_.end();
|
|
|
|
for (it = damage_reasons_.begin(); it != end; ++it)
|
|
|
|
r += *it;
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
return r;
|
|
|
|
}
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
void clear_damage_desc() {
|
|
|
|
damage_reasons_.clear();
|
|
|
|
}
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-20 17:45:51 +05:30
|
|
|
void maybe_issue_damage(btree_path const &path) {
|
2013-05-28 16:50:05 +05:30
|
|
|
maybe_run64 mr = dt_.end();
|
2013-05-20 17:45:51 +05:30
|
|
|
if (mr)
|
|
|
|
issue_damage(path, *mr);
|
|
|
|
}
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-20 17:01:47 +05:30
|
|
|
void update_path(btree_path const &path) {
|
2014-07-30 16:51:34 +05:30
|
|
|
btree_path const *old_path = path_tracker_.next_path(path);
|
2013-05-20 17:45:51 +05:30
|
|
|
if (old_path)
|
2013-05-20 17:01:47 +05:30
|
|
|
// we need to emit any errors that
|
|
|
|
// were accrued against the old
|
|
|
|
// path.
|
2013-05-20 17:45:51 +05:30
|
|
|
maybe_issue_damage(*old_path);
|
2013-05-20 17:01:47 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------
|
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
bool avoid_repeated_visits_;
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
ValueVisitor &value_visitor_;
|
|
|
|
DamageVisitor &damage_visitor_;
|
2013-05-08 21:08:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
std::set<block_address> seen_;
|
|
|
|
boost::optional<uint64_t> last_leaf_key_[Levels];
|
2013-05-08 16:30:24 +05:30
|
|
|
|
2016-04-02 14:45:00 +05:30
|
|
|
btree_node_checker checker_;
|
2013-05-20 17:01:47 +05:30
|
|
|
path_tracker path_tracker_;
|
2013-05-17 15:59:34 +05:30
|
|
|
damage_tracker dt_;
|
|
|
|
std::list<std::string> damage_reasons_;
|
|
|
|
};
|
|
|
|
}
|
2013-05-13 15:57:38 +05:30
|
|
|
|
2013-05-17 15:59:34 +05:30
|
|
|
template <unsigned Levels, typename ValueTraits, typename ValueVisitor, typename DamageVisitor>
|
|
|
|
void btree_visit_values(btree<Levels, ValueTraits> const &tree,
|
|
|
|
ValueVisitor &value_visitor,
|
|
|
|
DamageVisitor &damage_visitor) {
|
|
|
|
btree_detail::btree_damage_visitor<ValueVisitor, DamageVisitor, Levels, ValueTraits>
|
2013-12-11 22:58:14 +05:30
|
|
|
v(value_visitor, damage_visitor);
|
2013-05-17 15:59:34 +05:30
|
|
|
tree.visit_depth_first(v);
|
|
|
|
}
|
2013-05-08 16:30:24 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
|
|
|
#endif
|