mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			2942 lines
		
	
	
	
		
			101 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2942 lines
		
	
	
	
		
			101 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|     This file is part of TON Blockchain Library.
 | |
| 
 | |
|     TON Blockchain Library is free software: you can redistribute it and/or modify
 | |
|     it under the terms of the GNU Lesser General Public License as published by
 | |
|     the Free Software Foundation, either version 2 of the License, or
 | |
|     (at your option) any later version.
 | |
| 
 | |
|     TON Blockchain Library is distributed in the hope that it will be useful,
 | |
|     but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|     GNU Lesser General Public License for more details.
 | |
| 
 | |
|     You should have received a copy of the GNU Lesser General Public License
 | |
|     along with TON Blockchain Library.  If not, see <http://www.gnu.org/licenses/>.
 | |
| 
 | |
|     Copyright 2017-2020 Telegram Systems LLP
 | |
| */
 | |
| #include "vm/dict.h"
 | |
| #include "vm/cells.h"
 | |
| #include "vm/cellslice.h"
 | |
| #include "vm/stack.hpp"
 | |
| #include "common/bitstring.h"
 | |
| 
 | |
| #include "td/utils/bits.h"
 | |
| 
 | |
| namespace vm {
 | |
| 
 | |
| /*
 | |
|  * 
 | |
|  *  DictionaryBase : basic (common) dictionary manipulation
 | |
|  * 
 | |
|  */
 | |
| 
 | |
| DictionaryBase::DictionaryBase(Ref<CellSlice> _root, int _n, bool validate)
 | |
|     : root(std::move(_root)), root_cell(), key_bits(_n), flags(f_root_cached) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(const CellSlice& root_cs, int _n, bool validate)
 | |
|     : root(), root_cell(), key_bits(_n), flags(0) {
 | |
|   int f = (int)root_cs.prefetch_ulong(1);
 | |
|   if (f < 0) {
 | |
|     flags |= f_invalid;
 | |
|   } else if (f > 0) {
 | |
|     if (root_cs.size_refs()) {
 | |
|       root_cell = root_cs.prefetch_ref();
 | |
|     } else {
 | |
|       flags |= f_invalid;
 | |
|     }
 | |
|   }
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(DictAdvance, CellSlice& root_cs, int _n, bool validate)
 | |
|     : root(), root_cell(), key_bits(_n), flags(0) {
 | |
|   int f = (int)root_cs.prefetch_ulong(1);
 | |
|   if (!f) {
 | |
|     root_cs.advance(1);
 | |
|   } else if (f > 0 && root_cs.size_refs()) {
 | |
|     root_cs.advance(1);
 | |
|     root_cell = root_cs.fetch_ref();
 | |
|   } else {
 | |
|     flags |= f_invalid;
 | |
|   }
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(Ref<Cell> cell, int _n, bool validate)
 | |
|     : root(), root_cell(std::move(cell)), key_bits(_n), flags(0) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(int _n, bool validate) : root(), root_cell(), key_bits(_n), flags(0) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(DictNonEmpty, Ref<CellSlice> _root, int _n, bool validate)
 | |
|     : root(), root_cell(), key_bits(_n), flags(0) {
 | |
|   if (_root.is_null() || !init_root_for_nonempty(*_root)) {  // empty ?
 | |
|     invalidate();                                            // invalidate
 | |
|   }
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| DictionaryBase::DictionaryBase(DictNonEmpty, const CellSlice& _root, int _n, bool validate)
 | |
|     : root(), root_cell(), key_bits(_n), flags(0) {
 | |
|   if (!init_root_for_nonempty(_root)) {
 | |
|     invalidate();
 | |
|   }
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryBase::init_root_for_nonempty(const CellSlice& cs) {
 | |
|   vm::CellBuilder cb;
 | |
|   return cb.append_cellslice_bool(cs) && cb.finalize_to(root_cell);
 | |
| }
 | |
| 
 | |
| Ref<Cell> DictionaryBase::construct_root_from(const CellSlice& root_node_cs) {
 | |
|   vm::CellBuilder cb;
 | |
|   if (cb.append_cellslice_bool(root_node_cs)) {
 | |
|     return cb.finalize();
 | |
|   } else {
 | |
|     return {};
 | |
|   }
 | |
| }
 | |
| 
 | |
| void DictionaryBase::force_validate() {
 | |
|   if (!is_valid() && !validate()) {
 | |
|     throw VmError{Excno::dict_err, "invalid dictionary"};
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryBase::validate() {
 | |
|   if (is_valid()) {
 | |
|     return true;
 | |
|   }
 | |
|   if (flags & f_invalid) {
 | |
|     return false;
 | |
|   }
 | |
|   if (key_bits < 0 || key_bits > max_key_bits) {
 | |
|     return invalidate();
 | |
|   }
 | |
|   if (flags & f_root_cached) {
 | |
|     if (root.is_null() || root->size() != 1) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     bool non_empty = root->prefetch_ulong(1);
 | |
|     if (root->size_refs() != (non_empty ? 1u : 0u)) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     if (root_cell.not_null()) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     if (non_empty) {
 | |
|       root_cell = root->prefetch_ref();
 | |
|     }
 | |
|   } else if (root.not_null()) {
 | |
|     return invalidate();
 | |
|   }
 | |
|   flags |= f_valid;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryBase::get_root() const {
 | |
|   if (!(flags & f_root_cached) && !compute_root()) {
 | |
|     return {};
 | |
|   }
 | |
|   return root;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryBase::extract_root() && {
 | |
|   if (!(flags & f_root_cached) && !compute_root()) {
 | |
|     return {};
 | |
|   }
 | |
|   flags = f_invalid;
 | |
|   return std::move(root);
 | |
| }
 | |
| 
 | |
| bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) && {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   flags = f_invalid;
 | |
|   return cb.store_maybe_ref(std::move(root_cell));
 | |
| }
 | |
| 
 | |
| bool DictionaryBase::append_dict_to_bool(CellBuilder& cb) const & {
 | |
|   return is_valid() && cb.store_maybe_ref(root_cell);
 | |
| }
 | |
| 
 | |
| bool DictionaryBase::compute_root() const {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (root_cell.is_null()) {
 | |
|     root = get_empty_dictionary();
 | |
|     flags |= f_root_cached;
 | |
|     return true;
 | |
|   }
 | |
|   CellBuilder cb;
 | |
|   cb.store_long(1, 1);
 | |
|   cb.store_ref(root_cell);
 | |
|   root = Ref<CellSlice>{true, cb.finalize()};
 | |
|   flags |= f_root_cached;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryBase::get_empty_dictionary() {
 | |
|   static Ref<CellSlice> empty_dict{new_empty_dictionary()};
 | |
|   return empty_dict;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryBase::new_empty_dictionary() {
 | |
|   CellBuilder cb;  // Builder
 | |
|   cb.store_long(0, 1);
 | |
|   return Ref<CellSlice>{true, cb.finalize()};
 | |
| }
 | |
| 
 | |
| Ref<Cell> DictionaryFixed::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
 | |
|   if (!cb.append_cellslice_bool(value)) {
 | |
|     throw VmError{Excno::dict_err, "cannot store new value into a dictionary leaf cell"};
 | |
|   }
 | |
|   return cb.finalize();
 | |
| }
 | |
| 
 | |
| Ref<Cell> DictionaryFixed::finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const {
 | |
|   assert(n > 0);
 | |
|   if (!(cb.store_ref_bool(std::move(c1)) && cb.store_ref_bool(std::move(c2)))) {
 | |
|     throw VmError{Excno::dict_err, "cannot store branch references into a dictionary fork cell"};
 | |
|   }
 | |
|   return cb.finalize();
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::check_fork_raw(Ref<CellSlice> cs_ref, int n) const {
 | |
|   if (cs_ref.is_null()) {
 | |
|     return false;
 | |
|   }
 | |
|   Ref<Cell> c1, c2;
 | |
|   CellSlice& cs = cs_ref.write();
 | |
|   return cs.fetch_ref_to(c1) && cs.fetch_ref_to(c2) && check_fork(cs, std::move(c1), std::move(c2), n);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 
 | |
|  *  Label parser (HmLabel n ~l) for all dictionary types
 | |
|  *
 | |
|  */
 | |
| 
 | |
| namespace dict {
 | |
| 
 | |
| LabelParser::LabelParser(Ref<CellSlice> cs, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
 | |
|   if (!parse_label(cs.write(), max_label_len)) {
 | |
|     l_offs = 0;
 | |
|   } else {
 | |
|     s_bits = (l_same ? 0 : l_bits);
 | |
|     remainder = std::move(cs);
 | |
|   }
 | |
|   if (auto_validate) {
 | |
|     if (auto_validate > 2) {
 | |
|       validate_ext(max_label_len);
 | |
|     } else if (auto_validate == 2) {
 | |
|       validate_simple(max_label_len);
 | |
|     } else {
 | |
|       validate();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| LabelParser::LabelParser(Ref<Cell> cell, int max_label_len, int auto_validate) : remainder(), l_offs(0), l_same(0) {
 | |
|   Ref<CellSlice> cs = load_cell_slice_ref(std::move(cell));
 | |
|   if (!parse_label(cs.unique_write(), max_label_len)) {
 | |
|     l_offs = 0;
 | |
|   } else {
 | |
|     s_bits = (l_same ? 0 : l_bits);
 | |
|     remainder = std::move(cs);
 | |
|   }
 | |
|   if (auto_validate) {
 | |
|     if (auto_validate > 2) {
 | |
|       validate_ext(max_label_len);
 | |
|     } else if (auto_validate == 2) {
 | |
|       validate_simple(max_label_len);
 | |
|     } else {
 | |
|       validate();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool LabelParser::parse_label(CellSlice& cs, int max_label_len) {
 | |
|   int ltype = (int)cs.prefetch_ulong(2);
 | |
|   // std::cerr << "parse_label of type " << ltype << " and maximal length " << max_label_len << " in ";
 | |
|   // cs.dump_hex(std::cerr, 0, true);
 | |
|   switch (ltype) {
 | |
|     case 0: {
 | |
|       l_bits = 0;
 | |
|       l_offs = 2;
 | |
|       cs.advance(2);
 | |
|       return true;
 | |
|     }
 | |
|     case 1: {
 | |
|       cs.advance(1);
 | |
|       l_bits = cs.count_leading(1);
 | |
|       // std::cerr << "unary-encoded l_bits = " << l_bits << ", have " << cs.size() << std::endl;
 | |
|       if (l_bits > max_label_len || !cs.have(2 * l_bits + 1)) {
 | |
|         return false;
 | |
|       }
 | |
|       l_offs = l_bits + 2;
 | |
|       cs.advance(l_bits + 1);
 | |
|       return true;
 | |
|     }
 | |
|     case 2: {
 | |
|       int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
 | |
|       cs.advance(2);
 | |
|       l_bits = (int)cs.fetch_ulong(len_bits);
 | |
|       if (l_bits < 0 || l_bits > max_label_len) {
 | |
|         return false;
 | |
|       }
 | |
|       l_offs = len_bits + 2;
 | |
|       return cs.have(l_bits);
 | |
|     }
 | |
|     case 3: {
 | |
|       int len_bits = 32 - td::count_leading_zeroes32(max_label_len);
 | |
|       // std::cerr << "len_bits = " << len_bits << ", have " << cs.size() << std::endl;
 | |
|       if (!cs.have(3 + len_bits)) {
 | |
|         return false;
 | |
|       }
 | |
|       l_same = (int)cs.fetch_ulong(3);
 | |
|       l_bits = (int)cs.fetch_ulong(len_bits);
 | |
|       // std::cerr << "l_bits = " << l_bits << ", l_same = " << l_same << std::endl;
 | |
|       if (l_bits < 0 || l_bits > max_label_len) {
 | |
|         return false;
 | |
|       }
 | |
|       l_offs = -1;
 | |
|       return true;
 | |
|     }
 | |
|     default:
 | |
|       return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LabelParser::validate() const {
 | |
|   if (!is_valid()) {
 | |
|     throw VmError{Excno::cell_und, "error while parsing a dictionary node label"};
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LabelParser::validate_ext(int n) const {
 | |
|   validate();
 | |
|   if (l_bits > n) {
 | |
|     throw VmError{Excno::dict_err, "invalid dictionary node"};
 | |
|   } else if (l_bits < n && (remainder->size() != s_bits || remainder->size_refs() != 2)) {
 | |
|     throw VmError{Excno::dict_err, "invalid dictionary fork node"};
 | |
|   }
 | |
| }
 | |
| 
 | |
| void LabelParser::validate_simple(int n) const {
 | |
|   validate();
 | |
|   if (l_bits > n) {
 | |
|     throw VmError{Excno::dict_err, "invalid dictionary node"};
 | |
|   } else if (l_bits < n && (remainder->size() < s_bits || remainder->size_refs() < 2)) {
 | |
|     throw VmError{Excno::dict_err, "invalid dictionary fork node"};
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool LabelParser::is_prefix_of(td::ConstBitPtr key, int len) const {
 | |
|   if (l_bits > len) {
 | |
|     return false;
 | |
|   } else if (!l_same) {
 | |
|     //std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
 | |
|     //remainder->dump_hex(std::cerr, 0, true);
 | |
|     return remainder->has_prefix(key, l_bits);
 | |
|   } else {
 | |
|     return td::bitstring::bits_memscan(key, l_bits, l_same & 1) == (unsigned)l_bits;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool LabelParser::has_prefix(td::ConstBitPtr key, int len) const {
 | |
|   return len >= 0 && len <= l_bits && common_prefix_len(key, len) == len;
 | |
| }
 | |
| 
 | |
| int LabelParser::common_prefix_len(td::ConstBitPtr key, int len) const {
 | |
|   if (!l_same) {
 | |
|     //std::cerr << "key is " << key.to_hex(len) << "; len = " << len << "; label_bits = " << l_bits << "; remainder = ";
 | |
|     //remainder->dump_hex(std::cerr, 0, true);
 | |
|     return remainder->common_prefix_len(key, std::min(l_bits, len));
 | |
|   } else {
 | |
|     return (int)td::bitstring::bits_memscan(key, std::min(l_bits, len), l_same & 1);
 | |
|   }
 | |
| }
 | |
| 
 | |
| int LabelParser::extract_label_to(td::BitPtr to) {
 | |
|   if (!l_same) {
 | |
|     to.copy_from(remainder->data_bits(), l_bits);
 | |
|     remainder.write().advance(l_bits);
 | |
|   } else {
 | |
|     to.fill(l_same & 1, l_bits);
 | |
|   }
 | |
|   return l_bits;
 | |
| }
 | |
| 
 | |
| int LabelParser::copy_label_prefix_to(td::BitPtr to, int max_len) const {
 | |
|   if (max_len <= 0) {
 | |
|     return max_len;
 | |
|   }
 | |
|   int sz = std::min(max_len, l_bits);
 | |
|   if (!l_same) {
 | |
|     to.copy_from(remainder->data_bits(), sz);
 | |
|   } else {
 | |
|     to.fill(l_same & 1, sz);
 | |
|   }
 | |
|   return sz;
 | |
| }
 | |
| 
 | |
| }  // namespace dict
 | |
| 
 | |
| /*
 | |
|  * 
 | |
|  *   Usual Dictionary
 | |
|  * 
 | |
|  */
 | |
| 
 | |
| using dict::LabelParser;
 | |
| 
 | |
| BitSlice DictionaryFixed::integer_key(td::RefInt256 x, unsigned n, bool sgnd, unsigned char buffer[128], bool quiet) {
 | |
|   if (x.not_null() && x->fits_bits(n, sgnd)) {
 | |
|     if (buffer) {
 | |
|       if (x->export_bits(buffer, 0, n, sgnd)) {
 | |
|         return BitSlice{{}, buffer, 0, n};
 | |
|       }
 | |
|     } else {
 | |
|       Ref<td::BitString> bs{true, n};
 | |
|       if (x->export_bits(bs.unique_write().reserve_bitslice(n), sgnd)) {
 | |
|         return static_cast<BitSlice>(*bs);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (!quiet) {
 | |
|     throw VmError{Excno::range_chk, "dictionary index out of bounds"};
 | |
|   }
 | |
|   return {};
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::integer_key_simple(td::RefInt256 x, unsigned n, bool sgnd, td::BitPtr buffer, bool quiet) {
 | |
|   if (x.not_null() && x->fits_bits(n, sgnd) && x->export_bits(buffer, n, sgnd)) {
 | |
|     return true;
 | |
|   }
 | |
|   if (!quiet) {
 | |
|     throw VmError{Excno::range_chk, "dictionary index out of bounds"};
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::extract_value_ref(Ref<CellSlice> cs) {
 | |
|   if (cs.is_null()) {
 | |
|     return {};
 | |
|   } else if (!cs->size() && cs->size_refs() == 1) {
 | |
|     return cs->prefetch_ref();
 | |
|   } else {
 | |
|     throw VmError{Excno::dict_err, "dictionary value does not consist of exactly one reference"};
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::lookup(td::ConstBitPtr key, int key_len) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits() || is_empty()) {
 | |
|     return {};
 | |
|   }
 | |
|   //std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
 | |
|   Ref<Cell> cell = get_root_cell();
 | |
|   int n = key_len;
 | |
|   while (true) {
 | |
|     LabelParser label{std::move(cell), n, label_mode()};
 | |
|     if (!label.is_prefix_of(key, n)) {
 | |
|       //std::cerr << "(not a prefix)\n";
 | |
|       return {};
 | |
|     }
 | |
|     n -= label.l_bits;
 | |
|     if (n <= 0) {
 | |
|       assert(!n);
 | |
|       label.skip_label();
 | |
|       return std::move(label.remainder);
 | |
|     }
 | |
|     key += label.l_bits;
 | |
|     bool sw = *key++;
 | |
|     //std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
 | |
|     --n;
 | |
|     cell = label.remainder->prefetch_ref(sw);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value_ref(lookup(key, key_len));
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::has_common_prefix(td::ConstBitPtr prefix, int prefix_len) {
 | |
|   force_validate();
 | |
|   if (is_empty() || prefix_len <= 0) {
 | |
|     return true;
 | |
|   }
 | |
|   if (prefix_len > get_key_bits()) {
 | |
|     return false;
 | |
|   }
 | |
|   LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
 | |
|   return label.has_prefix(prefix, prefix_len);
 | |
| }
 | |
| 
 | |
| int DictionaryFixed::get_common_prefix(td::BitPtr buffer, unsigned buffer_len) {
 | |
|   force_validate();
 | |
|   if (is_empty()) {
 | |
|     return 0;
 | |
|   }
 | |
|   LabelParser label{get_root_cell(), get_key_bits(), label_mode()};
 | |
|   return label.copy_label_prefix_to(buffer, (int)buffer_len);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::key_exists(td::ConstBitPtr key, int key_len) {
 | |
|   return lookup(key, key_len).not_null();
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::int_key_exists(long long key) {
 | |
|   force_validate();
 | |
|   int l = get_key_bits();
 | |
|   if (is_empty() || l > 64) {
 | |
|     return false;
 | |
|   }
 | |
|   if (l < 64) {
 | |
|     long long m = (1LL << (l - 1));
 | |
|     if (key < -m || key >= m) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   td::BitArray<64> a;
 | |
|   a.bits().store_int(key, l);
 | |
|   return key_exists(a.cbits(), l);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::uint_key_exists(unsigned long long key) {
 | |
|   force_validate();
 | |
|   int l = get_key_bits();
 | |
|   if (is_empty() || l > 64) {
 | |
|     return false;
 | |
|   }
 | |
|   if (l < 64 && key >= (1ULL << l)) {
 | |
|     return false;
 | |
|   }
 | |
|   td::BitArray<64> a;
 | |
|   a.bits().store_uint(key, l);
 | |
|   return key_exists(a.cbits(), l);
 | |
| }
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| void append_dict_label_same(CellBuilder& cb, bool same, int len, int max_len) {
 | |
|   int k = 32 - td::count_leading_zeroes32(max_len);
 | |
|   assert(len >= 0 && len <= max_len && max_len <= 1023);
 | |
|   // options: mode '0', requires 2n+2 bits (always for n=0)
 | |
|   // mode '10', requires 2+k+n bits (only for n<=1)
 | |
|   // mode '11', requires 3+k bits (for n>=2, k<2n-1)
 | |
|   if (len > 1 && k < 2 * len - 1) {
 | |
|     // mode '11'
 | |
|     cb.store_long(6 + same, 3).store_long(len, k);
 | |
|   } else if (k < len) {
 | |
|     // mode '10'
 | |
|     cb.store_long(2, 2).store_long(len, k).store_long(-static_cast<int>(same), len);
 | |
|   } else {
 | |
|     // mode '0'
 | |
|     cb.store_long(0, 1).store_long(-2, len + 1).store_long(-static_cast<int>(same), len);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void append_dict_label(CellBuilder& cb, td::ConstBitPtr label, int len, int max_len) {
 | |
|   assert(len <= max_len && max_len <= 1023);
 | |
|   if (len > 0 && (int)td::bitstring::bits_memscan(label, len, *label) == len) {
 | |
|     return append_dict_label_same(cb, *label, len, max_len);
 | |
|   }
 | |
|   int k = 32 - td::count_leading_zeroes32(max_len);
 | |
|   // two options: mode '0', requires 2n+2 bits
 | |
|   // mode '10', requires 2+k+n bits
 | |
|   if (k < len) {
 | |
|     cb.store_long(2, 2).store_long(len, k);
 | |
|   } else {
 | |
|     cb.store_long(0, 1).store_long(-2, len + 1);
 | |
|   }
 | |
|   if ((int)cb.remaining_bits() < len) {
 | |
|     throw VmError{Excno::cell_ov, "cannot store a label into a dictionary cell"};
 | |
|   }
 | |
|   cb.store_bits(label, len);
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, bool> dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
 | |
|                                     const Dictionary::store_value_func_t& store_val, Dictionary::SetMode mode) {
 | |
|   //std::cerr << "dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       return std::make_pair<Ref<Cell>, bool>({}, false);
 | |
|     }
 | |
|     // create an one-element dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_pair(cb.finalize(), true);
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n};
 | |
|   label.validate();
 | |
|   int pfx_len = label.common_prefix_len(key, n);
 | |
|   assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|   if (pfx_len < label.l_bits) {
 | |
|     // have to insert a new node (fork) inside the current edge
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       // key not found, return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // first, create the edge + new leaf cell
 | |
|     int m = n - pfx_len - 1;
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key + (pfx_len + 1), m, m);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     Ref<Cell> c1 = cb.finalize();  // new leaf cell corresponding to `key`
 | |
|     //cb.reset();
 | |
|     // create the lower portion of the old edge
 | |
|     int t = label.l_bits - pfx_len - 1;
 | |
|     auto cs = std::move(label.remainder);
 | |
|     if (label.l_same) {
 | |
|       append_dict_label_same(cb, label.l_same & 1, t, m);
 | |
|     } else {
 | |
|       cs.write().advance(pfx_len + 1);
 | |
|       append_dict_label(cb, cs->data_bits(), t, m);
 | |
|       cs.unique_write().advance(t);
 | |
|     }
 | |
|     // now cs is the old payload of the edge, either a value or two subdictionary references
 | |
|     if (!cell_builder_add_slice_bool(cb, *cs)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
 | |
|     }
 | |
|     Ref<Cell> c2 = cb.finalize();  // the other child of the new fork
 | |
|     // cb.reset();
 | |
|     append_dict_label(cb, key, pfx_len, n);
 | |
|     bool sw_bit = key[pfx_len];
 | |
|     if (sw_bit) {
 | |
|       c1.swap(c2);
 | |
|     }
 | |
|     cb.store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|     return std::make_pair(cb.finalize(), true);
 | |
|   }
 | |
|   if (label.l_bits == n) {
 | |
|     // the edge leads to a leaf node
 | |
|     // this leaf node already contains a value for the key wanted
 | |
|     if (mode == Dictionary::SetMode::Add) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // replace the value of the only element of the dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_pair(cb.finalize(), true);
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   label.remainder.clear();
 | |
|   if (key[label.l_bits]) {
 | |
|     // insert key into the right child (c2)
 | |
|     auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c2 = std::move(res.first);
 | |
|   } else {
 | |
|     // insert key into the left child (c1)
 | |
|     auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c1 = std::move(res.first);
 | |
|   }
 | |
|   // create a new label with the same content
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key, label.l_bits, n);
 | |
|   cb.store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|   return std::make_pair(cb.finalize(), true);
 | |
| }
 | |
| 
 | |
| std::tuple<Ref<CellSlice>, Ref<Cell>, bool> dict_lookup_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
 | |
|                                                             const Dictionary::store_value_func_t& store_val,
 | |
|                                                             Dictionary::SetMode mode) {
 | |
|   //std::cerr << "dictionary lookup/modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, {}, false);
 | |
|     }
 | |
|     // create an one-element dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, cb.finalize(), true);
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n};
 | |
|   int pfx_len = label.common_prefix_len(key, n);
 | |
|   assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|   if (pfx_len < label.l_bits) {
 | |
|     // have to insert a new node (fork) inside the current edge
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       // key not found, return unchanged dictionary
 | |
|       return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, {}, false);
 | |
|     }
 | |
|     // first, create the edge + new leaf cell
 | |
|     int m = n - pfx_len - 1;
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key + (pfx_len + 1), m, m);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     Ref<Cell> c1 = cb.finalize();  // new leaf cell corresponding to `key`
 | |
|     //cb.reset();
 | |
|     // create the lower portion of the old edge
 | |
|     int t = label.l_bits - pfx_len - 1;
 | |
|     auto cs = std::move(label.remainder);
 | |
|     if (label.l_same) {
 | |
|       append_dict_label_same(cb, label.l_same & 1, t, m);
 | |
|     } else {
 | |
|       cs.write().advance(pfx_len + 1);
 | |
|       append_dict_label(cb, cs->data_bits(), t, m);
 | |
|       cs.unique_write().fetch_bits(t);
 | |
|     }
 | |
|     // now cs is the old payload of the edge, either a value or two subdictionary references
 | |
|     if (!cell_builder_add_slice_bool(cb, *cs)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
 | |
|     }
 | |
|     Ref<Cell> c2 = cb.finalize();  // the other child of the new fork
 | |
|     //cb.reset();
 | |
|     append_dict_label(cb, key, pfx_len, n);
 | |
|     bool sw_bit = key[pfx_len];
 | |
|     if (sw_bit) {
 | |
|       c1.swap(c2);
 | |
|     }
 | |
|     cb.store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|     return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>({}, cb.finalize(), true);
 | |
|   }
 | |
|   if (label.l_bits == n) {
 | |
|     // the edge leads to a leaf node
 | |
|     // this leaf node already contains a value for the key wanted
 | |
|     auto old_val = std::move(label.remainder);
 | |
|     old_val.write().advance(label.s_bits);
 | |
|     if (mode == Dictionary::SetMode::Add) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), {}, false);
 | |
|     }
 | |
|     // replace the value of the only element of the dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_tuple(std::move(old_val), cb.finalize(), true);
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   Ref<CellSlice> old_val;
 | |
|   label.remainder.clear();
 | |
|   if (key[label.l_bits]) {
 | |
|     // insert key into the right child (c2)
 | |
|     auto res = dict_lookup_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
 | |
|     old_val = std::get<Ref<CellSlice>>(res);
 | |
|     if (!std::get<bool>(res)) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), {}, false);
 | |
|     }
 | |
|     c2 = std::get<Ref<Cell>>(std::move(res));
 | |
|   } else {
 | |
|     // insert key into the left child (c1)
 | |
|     auto res = dict_lookup_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, store_val, mode);
 | |
|     old_val = std::get<Ref<CellSlice>>(res);
 | |
|     if (!std::get<bool>(res)) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_tuple(std::move(old_val), Ref<Cell>{}, false);
 | |
|     }
 | |
|     c1 = std::get<Ref<Cell>>(std::move(res));
 | |
|   }
 | |
|   // create a new label with the same content
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key, label.l_bits, n);
 | |
|   cb.store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|   return std::make_tuple<Ref<CellSlice>, Ref<Cell>, bool>(std::move(old_val), cb.finalize(), true);
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, bool> pfx_dict_set(Ref<Cell> dict, td::ConstBitPtr key, int m, int n,
 | |
|                                         const PrefixDictionary::store_value_func_t& store_val,
 | |
|                                         Dictionary::SetMode mode) {
 | |
|   // std::cerr << "up to " << n << "-bit prefix code dictionary modification for " << m << "-bit key = " << key.to_hex(m) << std::endl;
 | |
|   if (m > n) {
 | |
|     return std::make_pair(Ref<Cell>{}, false);
 | |
|   }
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // create an one-element dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, m, n);
 | |
|     cb.store_long(0, 1);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_pair(cb.finalize(), true);
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, 1};
 | |
|   int l = label.common_prefix_len(key, m);
 | |
|   assert(l >= 0 && l <= label.l_bits && label.l_bits <= n && l <= m && m <= n);
 | |
|   if (l < label.l_bits) {
 | |
|     // have to insert a new node (fork) inside the current edge
 | |
|     if (l == m || mode == Dictionary::SetMode::Replace) {
 | |
|       // key not found, return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // first, create the edge + new leaf cell
 | |
|     int q = l + 1;
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key + q, m - q, n - q);
 | |
|     cb.store_long(0, 1);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a prefix dictionary cell"};
 | |
|     }
 | |
|     Ref<Cell> c1 = cb.finalize();  // new leaf cell corresponding to `key`
 | |
|     // cb.reset(); // contained in finalize()
 | |
|     // create the lower portion of the old edge
 | |
|     int t = label.l_bits - q;
 | |
|     auto cs = std::move(label.remainder);
 | |
|     if (label.l_same) {
 | |
|       append_dict_label_same(cb, label.l_same & 1, t, n - q);
 | |
|     } else {
 | |
|       cs.write().advance(l + 1);
 | |
|       append_dict_label(cb, cs->data_bits(), t, n - q);
 | |
|       cs.unique_write().advance(t);
 | |
|     }
 | |
|     // now cs is the old payload of the edge, either a value or two subdictionary references
 | |
|     if (!cell_builder_add_slice_bool(cb, *cs)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell (?)"};
 | |
|     }
 | |
|     Ref<Cell> c2 = cb.finalize();  // the other child of the new fork
 | |
|     //cb.reset();
 | |
|     append_dict_label(cb, key, l, n);
 | |
|     bool sw_bit = key[l];
 | |
|     if (sw_bit) {
 | |
|       c1.swap(c2);
 | |
|     }
 | |
|     cb.store_long(1, 1).store_ref(c1).store_ref(c2);
 | |
|     return std::make_pair<Ref<Cell>, bool>(cb.finalize(), true);
 | |
|   }
 | |
|   assert(label.l_bits == l);
 | |
|   label.skip_label();
 | |
|   if (!label.remainder->have(1)) {
 | |
|     throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
 | |
|   }
 | |
|   if (!label.remainder.unique_write().fetch_ulong(1)) {
 | |
|     // the edge leads to a leaf node
 | |
|     if (l != m || mode == Dictionary::SetMode::Add) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair<Ref<Cell>, bool>({}, false);
 | |
|     }
 | |
|     // this leaf node already contains a value for the key wanted
 | |
|     // replace the value of the only element of the dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, m, n);
 | |
|     cb.store_long(0, 1);
 | |
|     if (!store_val(cb)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot store new value into a dictionary cell"};
 | |
|     }
 | |
|     return std::make_pair<Ref<Cell>, bool>(cb.finalize(), true);
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
 | |
|   if (label.remainder->size() || label.remainder->size_refs() != 2) {
 | |
|     throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
 | |
|   }
 | |
|   if (m == l) {
 | |
|     // cannot insert a value into a fork
 | |
|     return std::make_pair(Ref<Cell>{}, false);
 | |
|   }
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   label.remainder.clear();
 | |
|   if (key[l++]) {
 | |
|     // insert key into the right child (c2)
 | |
|     auto res = pfx_dict_set(std::move(c2), key + l, m - l, n - l, store_val, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c2 = std::move(res.first);
 | |
|   } else {
 | |
|     // insert key into the left child (c1)
 | |
|     auto res = pfx_dict_set(std::move(c1), key + l, m - l, n - l, store_val, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c1 = std::move(res.first);
 | |
|   }
 | |
|   // create a new label with the same content
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key, l - 1, n);
 | |
|   cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|   return std::make_pair(cb.finalize(), true);
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<Cell>> pfx_dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key, int m, int n) {
 | |
|   //std::cerr << "up to " << n << "-bit prefix dictionary delete for " << m << "-bit key = " << key.to_hex(m) << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, 1};
 | |
|   int l = label.common_prefix_len(key, m);
 | |
|   assert(l >= 0 && l <= label.l_bits && l <= m && m <= n && label.l_bits <= n);
 | |
|   if (l < label.l_bits) {
 | |
|     // key not found
 | |
|     return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|   }
 | |
|   assert(label.l_bits == l);
 | |
|   label.skip_label();
 | |
|   if (!label.remainder->have(1)) {
 | |
|     throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
 | |
|   }
 | |
|   if (!label.remainder.unique_write().fetch_ulong(1)) {
 | |
|     // the edge leads to a leaf node
 | |
|     if (l < m) {
 | |
|       // key not found
 | |
|       return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|     }
 | |
|     // this leaf node contains the value for the key wanted
 | |
|     return std::make_pair(std::move(label.remainder), Ref<Cell>{});
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
 | |
|   if (label.remainder->size() || label.remainder->size_refs() != 2) {
 | |
|     throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
 | |
|   }
 | |
|   if (l == m) {
 | |
|     // the fork itself cannot correspond to a key, key not found
 | |
|     return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|   }
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   Ref<CellSlice> old_val;
 | |
|   label.remainder.clear();
 | |
|   bool sw_bit = key[l++];
 | |
|   if (sw_bit) {
 | |
|     // delete key from the right child (c2)
 | |
|     auto res = pfx_dict_lookup_delete(std::move(c2), key + l, m - l, n - l);
 | |
|     if (res.first.is_null()) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|     }
 | |
|     old_val = std::move(res.first);
 | |
|     c2 = std::move(res.second);
 | |
|   } else {
 | |
|     // delete key from the left child (c1)
 | |
|     auto res = pfx_dict_lookup_delete(std::move(c1), key + l, m - l, n - l);
 | |
|     if (res.first.is_null()) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|     }
 | |
|     old_val = std::move(res.first);
 | |
|     c1 = std::move(res.second);
 | |
|   }
 | |
|   if (c1.not_null() && c2.not_null()) {
 | |
|     // create a new label with the same content leading to a fork with modified children
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, label.l_bits, n);
 | |
|     cb.store_long(1, 1).store_ref(std::move(c1)).store_ref(std::move(c2));
 | |
|     return std::make_pair(std::move(old_val), cb.finalize());
 | |
|   }
 | |
|   // have to merge current edge with the edge leading to c1 or c2
 | |
|   if (!sw_bit) {
 | |
|     c1.swap(c2);
 | |
|   }
 | |
|   assert(c1.not_null() && c2.is_null());
 | |
|   unsigned char buffer[Dictionary::max_key_bytes];
 | |
|   td::BitPtr bw{buffer};
 | |
|   bw.concat(key, label.l_bits);
 | |
|   bw.concat_same(!sw_bit, 1);
 | |
|   LabelParser label2{std::move(c1), n - l, 1};
 | |
|   bw += label2.extract_label_to(bw);
 | |
|   assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
 | |
|   if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
 | |
|     throw VmError{Excno::cell_ov, "cannot change label of an old prefix code dictionary cell while merging edges"};
 | |
|   }
 | |
|   label2.remainder.clear();
 | |
|   return std::make_pair(std::move(old_val), cb.finalize());
 | |
| }
 | |
| 
 | |
| Ref<Cell> dict_map(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
 | |
|                    const Dictionary::map_func_t& map_func) {
 | |
|   if (dict.is_null()) {
 | |
|     // dictionary is empty
 | |
|     return dict;
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n};
 | |
|   int l = label.l_bits;
 | |
|   label.extract_label_to(key_buffer);
 | |
|   if (l == n) {
 | |
|     // leaf node, value left in label.remainder
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key_buffer, l, n);
 | |
|     if (!map_func(cb, std::move(label.remainder), key_buffer + n - total_key_len, total_key_len)) {
 | |
|       return {};  // leaf to be omitted from the result altogether
 | |
|     }
 | |
|     return cb.finalize();
 | |
|   }
 | |
|   assert(l >= 0 && l < n);
 | |
|   // a fork with two children, c1 and c2
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   key_buffer += l + 1;
 | |
|   key_buffer[-1] = 0;
 | |
|   // recursive map applied to both children
 | |
|   c1 = dict_map(std::move(c1), key_buffer, n - l - 1, total_key_len, map_func);
 | |
|   key_buffer[-1] = 1;
 | |
|   c2 = dict_map(std::move(c2), key_buffer, n - l - 1, total_key_len, map_func);
 | |
|   if (c1.is_null() && c2.is_null()) {
 | |
|     return {};  // both children have become empty
 | |
|   }
 | |
|   if (c1.is_null() || c2.is_null()) {
 | |
|     if (c1.is_null()) {
 | |
|       c1 = std::move(c2);
 | |
|       // notice that the label of c2 is still in key_buffer
 | |
|     } else {
 | |
|       // recover label of c1
 | |
|       key_buffer[-1] = 0;
 | |
|     }
 | |
|     // one of children is empty, have to combine current edge with the root edge of c1
 | |
|     LabelParser label1{std::move(c1), n - l - 1};
 | |
|     label1.extract_label_to(key_buffer);
 | |
|     CellBuilder cb;
 | |
|     key_buffer -= l + 1;
 | |
|     // store combined label for the new edge
 | |
|     append_dict_label(cb, key_buffer, l + 1 + label1.l_bits, n);
 | |
|     // store payload
 | |
|     if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|     }
 | |
|     return cb.finalize();
 | |
|   }
 | |
|   // main case: both children c1 and c2 remain non-empty
 | |
|   key_buffer -= l + 1;
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key_buffer, l, n);
 | |
|   return cb.store_ref(std::move(c1)).store_ref(std::move(c2)).finalize();
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| bool Dictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function<bool(CellBuilder&)>& store_val,
 | |
|                          SetMode mode) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return false;
 | |
|   }
 | |
|   auto res = dict_set(get_root_cell(), key, key_len, store_val, mode);
 | |
|   if (res.second) {
 | |
|     set_root_cell(std::move(res.first));
 | |
|   }
 | |
|   return res.second;
 | |
| }
 | |
| 
 | |
| bool Dictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
 | |
|   return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
 | |
| }
 | |
| 
 | |
| bool Dictionary::set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode) {
 | |
|   return set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode);
 | |
| }
 | |
| 
 | |
| bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
 | |
|   return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
 | |
| }
 | |
| 
 | |
| bool Dictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& val_b, SetMode mode) {
 | |
|   return set_gen(key, key_len, [&val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> Dictionary::lookup_set_gen(td::ConstBitPtr key, int key_len, const store_value_func_t& store_val,
 | |
|                                           SetMode mode) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto res = dict_lookup_set(get_root_cell(), key, key_len, store_val, mode);
 | |
|   if (std::get<bool>(res)) {
 | |
|     set_root_cell(std::get<Ref<Cell>>(res));
 | |
|   }
 | |
|   return std::get<Ref<CellSlice>>(std::move(res));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> Dictionary::lookup_set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
 | |
|   return lookup_set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); },
 | |
|                         mode);
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::lookup_set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> val_ref, SetMode mode) {
 | |
|   return extract_value_ref(
 | |
|       lookup_set_gen(key, key_len, [val_ref](CellBuilder& cb) { return cb.store_ref_bool(val_ref); }, mode));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> Dictionary::lookup_set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
 | |
|   return lookup_set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<Cell>> DictionaryFixed::dict_lookup_delete(Ref<Cell> dict, td::ConstBitPtr key,
 | |
|                                                                          int n) const {
 | |
|   // std::cerr << "dictionary delete for " << n << "-bit key = " << key.to_hex(n) << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, label_mode()};
 | |
|   int pfx_len = label.common_prefix_len(key, n);
 | |
|   assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|   if (pfx_len < label.l_bits) {
 | |
|     // key not found
 | |
|     return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|   }
 | |
|   if (label.l_bits == n) {
 | |
|     // the edge leads to a leaf node
 | |
|     // this leaf node contains the value for the key wanted
 | |
|     label.skip_label();
 | |
|     return std::make_pair(std::move(label.remainder), Ref<Cell>{});
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to delete the key either from the right or from the left subtree
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   Ref<CellSlice> old_val;
 | |
|   label.remainder.clear();
 | |
|   bool sw_bit = key[label.l_bits];
 | |
|   if (sw_bit) {
 | |
|     // delete key from the right child (c2)
 | |
|     auto res = dict_lookup_delete(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1);
 | |
|     if (res.first.is_null()) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|     }
 | |
|     old_val = std::move(res.first);
 | |
|     c2 = std::move(res.second);
 | |
|   } else {
 | |
|     // delete key from the left child (c1)
 | |
|     auto res = dict_lookup_delete(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1);
 | |
|     if (res.first.is_null()) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<CellSlice>{}, Ref<Cell>{});
 | |
|     }
 | |
|     old_val = std::move(res.first);
 | |
|     c1 = std::move(res.second);
 | |
|   }
 | |
|   if (c1.not_null() && c2.not_null()) {
 | |
|     // create a new label with the same content leading to a fork with modified children
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, label.l_bits, n);
 | |
|     return std::make_pair(std::move(old_val), finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits));
 | |
|   }
 | |
|   // have to merge current edge with the edge leading to c1 or c2
 | |
|   if (!sw_bit) {
 | |
|     c1.swap(c2);
 | |
|   }
 | |
|   assert(c1.not_null() && c2.is_null());
 | |
|   unsigned char buffer[Dictionary::max_key_bytes];
 | |
|   td::BitPtr bw{buffer};
 | |
|   bw.concat(key, label.l_bits);
 | |
|   bw.concat_same(!sw_bit, 1);
 | |
|   LabelParser label2{std::move(c1), n - label.l_bits - 1, label_mode()};
 | |
|   bw += label2.extract_label_to(bw);
 | |
|   assert(bw.offs >= 0 && bw.offs <= Dictionary::max_key_bits);
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, td::ConstBitPtr{buffer}, bw.offs, n);
 | |
|   if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
 | |
|     throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|   }
 | |
|   label2.remainder.clear();
 | |
|   return std::make_pair(std::move(old_val), cb.finalize());
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::lookup_delete(td::ConstBitPtr key, int key_len) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto res = dict_lookup_delete(get_root_cell(), key, key_len);
 | |
|   if (res.first.not_null()) {
 | |
|     set_root_cell(std::move(res.second));
 | |
|   }
 | |
|   return std::move(res.first);
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value_ref(lookup_delete(key, key_len));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::dict_lookup_minmax(Ref<Cell> dict, td::BitPtr key_buffer, int n, int mode) const {
 | |
|   if (dict.is_null()) {
 | |
|     return {};
 | |
|   }
 | |
|   while (1) {
 | |
|     LabelParser label{std::move(dict), n, label_mode()};
 | |
|     int l = label.extract_label_to(key_buffer);
 | |
|     assert(l >= 0 && l <= n);
 | |
|     key_buffer += l;
 | |
|     n -= l;
 | |
|     if (!n) {
 | |
|       return std::move(label.remainder);
 | |
|     }
 | |
|     if (l) {
 | |
|       mode >>= 1;
 | |
|     }
 | |
|     bool bit = mode & 1;
 | |
|     dict = label.remainder->prefetch_ref(bit);
 | |
|     *key_buffer++ = bit;
 | |
|     --n;
 | |
|     mode >>= 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::dict_lookup_nearest(Ref<Cell> dict, td::BitPtr key_buffer, int n, bool allow_eq,
 | |
|                                                     int mode) const {
 | |
|   if (dict.is_null()) {
 | |
|     return {};
 | |
|   }
 | |
|   LabelParser label{dict, n, label_mode()};
 | |
|   int pfx_len = label.common_prefix_len(key_buffer, n);
 | |
|   assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|   if (pfx_len < label.l_bits) {
 | |
|     if (key_buffer[pfx_len] == ((mode >> static_cast<int>(pfx_len != 0)) & 1)) {
 | |
|       return {};
 | |
|     } else {
 | |
|       return dict_lookup_minmax(std::move(dict), key_buffer, n, ~mode);
 | |
|     }
 | |
|   }
 | |
|   dict.clear();
 | |
|   if (label.l_bits) {
 | |
|     mode >>= 1;
 | |
|   }
 | |
|   key_buffer += label.l_bits;
 | |
|   n -= label.l_bits;
 | |
|   if (!n) {
 | |
|     if (!allow_eq) {
 | |
|       return {};
 | |
|     }
 | |
|     label.skip_label();
 | |
|     return std::move(label.remainder);
 | |
|   }
 | |
|   bool bit = *key_buffer++;
 | |
|   auto res = dict_lookup_nearest(label.remainder->prefetch_ref(bit), key_buffer, n - 1, allow_eq, mode >> 1);
 | |
|   if (res.not_null() || bit == (mode & 1)) {
 | |
|     return res;
 | |
|   }
 | |
|   key_buffer[-1] = mode & 1;
 | |
|   dict = label.remainder->prefetch_ref(mode & 1);
 | |
|   label.remainder.clear();
 | |
|   return dict_lookup_minmax(std::move(dict), key_buffer, n - 1, ~mode >> 1);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::lookup_nearest_key(td::BitPtr key_buffer, int key_len, bool fetch_next, bool allow_eq,
 | |
|                                                    bool invert_first) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   return dict_lookup_nearest(get_root_cell(), key_buffer, key_len, allow_eq,
 | |
|                              (-static_cast<int>(fetch_next)) ^ static_cast<int>(invert_first));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::get_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   return dict_lookup_minmax(get_root_cell(), key_buffer, key_len,
 | |
|                             (-static_cast<int>(fetch_max)) ^ static_cast<int>(invert_first));
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::get_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
 | |
|   return extract_value_ref(get_minmax_key(key_buffer, key_len, fetch_max, invert_first));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> DictionaryFixed::extract_minmax_key(td::BitPtr key_buffer, int key_len, bool fetch_max,
 | |
|                                                    bool invert_first) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto val = dict_lookup_minmax(get_root_cell(), key_buffer, key_len, -(fetch_max ? 1 : 0) ^ (invert_first ? 1 : 0));
 | |
|   if (val.is_null()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto res = dict_lookup_delete(get_root_cell(), key_buffer, key_len);
 | |
|   assert(res.first.not_null());
 | |
|   set_root_cell(std::move(res.second));
 | |
|   return val;
 | |
| }
 | |
| 
 | |
| Ref<Cell> Dictionary::extract_minmax_key_ref(td::BitPtr key_buffer, int key_len, bool fetch_max, bool invert_first) {
 | |
|   return extract_value_ref(extract_minmax_key(key_buffer, key_len, fetch_max, invert_first));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *
 | |
|  *   ITERATORS (to be moved into a separate file)
 | |
|  *
 | |
|  */
 | |
| 
 | |
| bool DictIterator::prevalidate(int mode) {
 | |
|   if (key_bits_ <= 0 || key_bits_ > Dictionary::max_key_bits) {
 | |
|     reset();
 | |
|     flags_ &= ~f_valid;
 | |
|     key_bits_ = 0;
 | |
|     return false;
 | |
|   } else {
 | |
|     if (mode >= 0) {
 | |
|       order_ = -(mode & 1) ^ ((mode >> 1) & 1);
 | |
|     }
 | |
|     flags_ |= f_valid;
 | |
|     return true;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictIterator::bind(const DictionaryFixed& dict, int do_rewind) {
 | |
|   if (!is_valid() || !is_bound_to(dict)) {
 | |
|     return false;
 | |
|   }
 | |
|   dict_ = &dict;
 | |
|   label_mode_ = dict.label_mode();
 | |
|   return !do_rewind || rewind(do_rewind < 0);
 | |
| }
 | |
| 
 | |
| bool DictIterator::rebind_to(const DictionaryFixed& dict, int do_rewind) {
 | |
|   reset();
 | |
|   dict_ = &dict;
 | |
|   label_mode_ = dict.label_mode();
 | |
|   root_ = dict.get_root_cell();
 | |
|   key_bits_ = dict.get_key_bits();
 | |
|   flags_ &= 3;
 | |
|   return prevalidate() && (!do_rewind || rewind(do_rewind < 0));
 | |
| }
 | |
| 
 | |
| int DictIterator::compare_keys(td::ConstBitPtr a, td::ConstBitPtr b) const {
 | |
|   if (!key_bits_) {
 | |
|     return 0;
 | |
|   }
 | |
|   int c = (int)*a - (int)*b;
 | |
|   if (c) {
 | |
|     return (order_ & 1) ? -c : c;
 | |
|   }
 | |
|   c = a.compare(b, key_bits_);
 | |
|   return order_ >= 0 ? c : -c;
 | |
| }
 | |
| 
 | |
| bool DictIterator::dive(int mode) {
 | |
|   int n = key_bits_, m = 0;
 | |
|   Ref<Cell> node = path_.empty() ? root_ : path_.back().next;
 | |
|   if (!path_.empty()) {
 | |
|     m = path_.back().pos + 1;
 | |
|     n -= m;
 | |
|     mode >>= 1;
 | |
|   }
 | |
|   // similar to dict_lookup_minmax: create new path down until the leaf
 | |
|   while (1) {
 | |
|     LabelParser label{std::move(node), n, label_mode_};
 | |
|     int l = label.extract_label_to(key(m));
 | |
|     assert(l >= 0 && l <= n);
 | |
|     m += l;
 | |
|     n -= l;
 | |
|     if (!n) {
 | |
|       leaf_ = std::move(label.remainder);
 | |
|       return true;
 | |
|     }
 | |
|     if (l) {
 | |
|       mode >>= 1;
 | |
|     }
 | |
|     int bit = mode & 1;
 | |
|     node = label.remainder->prefetch_ref(bit);
 | |
|     auto alt = label.remainder->prefetch_ref(1 - bit);
 | |
|     path_.emplace_back(node, std::move(alt), m, bit);
 | |
|     *key(m++) = (bool)bit;
 | |
|     --n;
 | |
|     mode >>= 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictIterator::rewind(bool to_end) {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (root_.is_null()) {
 | |
|     return true;
 | |
|   }
 | |
|   auto node = root_;
 | |
|   int k = 0, mode = order_ ^ -(int)to_end;
 | |
|   // NB: can optimize by reusing several first entries of current path_
 | |
|   while (k < (int)path_.size()) {
 | |
|     auto& pe = path_[k++];
 | |
|     assert(pe.pos >= 0 && pe.pos < key_bits_);
 | |
|     if (pe.pos) {
 | |
|       mode >>= 1;
 | |
|     }
 | |
|     if (pe.v != (bool)(mode & 1)) {
 | |
|       // went different way at this node before, rotate and stop going down
 | |
|       pe.rotate(key());
 | |
|       leaf_.clear();
 | |
|       path_.resize(k);  // drop the remainder of the original path after first incorrect branch
 | |
|       return dive(mode);
 | |
|     }
 | |
|     mode >>= 1;
 | |
|   }
 | |
|   return !eof() || dive(mode);
 | |
| }
 | |
| 
 | |
| bool DictIterator::next(bool go_back) {
 | |
|   if (!is_valid() || root_.is_null() || eof()) {
 | |
|     return false;
 | |
|   }
 | |
|   leaf_.clear();
 | |
|   int mode = order_ ^ -(int)go_back;
 | |
|   while (!path_.empty()) {
 | |
|     auto& pe = path_.back();
 | |
|     int bit = (mode >> (pe.pos > 0)) & 1;
 | |
|     if (pe.v == bit) {
 | |
|       pe.rotate(key());
 | |
|       return dive(mode);
 | |
|     }
 | |
|     path_.pop_back();
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool DictIterator::lookup(td::ConstBitPtr pos, int pos_bits, bool strict_after, bool backw) {
 | |
|   if (!is_valid() || root_.is_null() || pos_bits < 0 || pos_bits > key_bits_) {
 | |
|     return false;
 | |
|   }
 | |
|   int fill_mode = -(strict_after ^ backw) ^ order_;
 | |
|   if (!eof()) {
 | |
|     // reuse part of current path
 | |
|     std::size_t bp0 = 0;
 | |
|     int bp;
 | |
|     if (!key().compare(pos, pos_bits, &bp0)) {
 | |
|       bp = pos_bits;
 | |
|       if (bp >= key_bits_) {
 | |
|         // already at the desired element
 | |
|         return !strict_after || next(backw);
 | |
|       }
 | |
|     } else {
 | |
|       bp = (int)bp0;
 | |
|     }
 | |
|     int k = 0;
 | |
|     while (k < (int)path_.size() && path_[k].pos <= bp) {
 | |
|       auto& pe = path_[k];
 | |
|       if (pe.pos == bp) {
 | |
|         if (bp < pos_bits || pe.v != ((fill_mode >> (bp > 0)) & 1)) {
 | |
|           // rotate the last path element if it branched in incorrect direction
 | |
|           path_[k++].rotate(key());
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       ++k;
 | |
|     }
 | |
|     path_.resize(k);  // drop the remainder of the path
 | |
|   }
 | |
|   int m = 0, n = key_bits_;
 | |
|   auto node = path_.empty() ? root_ : path_.back().next;
 | |
|   if (!path_.empty()) {
 | |
|     m = path_.back().pos + 1;  // m <= pos_bits + 1
 | |
|     n -= m;
 | |
|   }
 | |
|   int mode = -backw ^ order_, action = 0;
 | |
|   while (m < pos_bits && !action) {
 | |
|     LabelParser label{std::move(node), n, label_mode_};
 | |
|     int pfx_len = label.common_prefix_len(pos + m, pos_bits - m);
 | |
|     int l = label.extract_label_to(key() + m);
 | |
|     assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|     if (pfx_len == pos_bits - m) {
 | |
|       // end of given position prefix
 | |
|       if (strict_after) {
 | |
|         // have to backtrace
 | |
|         action = 2;
 | |
|         break;
 | |
|       } else {
 | |
|         // label has correct prefix, register and dive down
 | |
|         action = 1;
 | |
|       }
 | |
|     } else if (pfx_len < l) {
 | |
|       // all subtree is either smaller or larger than required
 | |
|       if (pos[m + pfx_len] != ((mode >> (int)(m + pfx_len > 0)) & 1)) {
 | |
|         // label smaller than required, have to backtrace
 | |
|         action = 2;
 | |
|         break;
 | |
|       } else {
 | |
|         // label larger than required, register node and dive down
 | |
|         action = 1;
 | |
|       }
 | |
|     }
 | |
|     // if we are here, then either action=1 (dive down activated)
 | |
|     // ... or l = pfx_len < pos_bits - m
 | |
|     // continue going down
 | |
|     m += l;
 | |
|     n -= l;
 | |
|     if (!n) {
 | |
|       // key found in a leaf
 | |
|       leaf_ = std::move(label.remainder);
 | |
|       return true;
 | |
|     }
 | |
|     bool bit = action ? ((mode >> (m > 0)) & 1) : pos[m];
 | |
|     node = label.remainder->prefetch_ref(bit);
 | |
|     auto alt = label.remainder->prefetch_ref(1 - bit);
 | |
|     path_.emplace_back(node, std::move(alt), m, bit);
 | |
|     *key(m++) = (bool)bit;
 | |
|     --n;
 | |
|   }
 | |
|   if (!action) {
 | |
|     action = (strict_after ? 2 : 1);
 | |
|   }
 | |
|   if (action == 2) {
 | |
|     // have to backtrace to the "next" larger branch
 | |
|     // similar to next()
 | |
|     leaf_.clear();
 | |
|     while (!path_.empty()) {
 | |
|       auto& pe = path_.back();
 | |
|       int bit = (mode >> (pe.pos > 0)) & 1;
 | |
|       if (pe.v == bit) {
 | |
|         pe.rotate(key());
 | |
|         return dive(mode);
 | |
|       }
 | |
|       path_.pop_back();
 | |
|     }
 | |
|     return false;  // eof: no suitable element
 | |
|   }
 | |
|   // action=1, dive down
 | |
|   return dive(mode);
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::null_iterator() {
 | |
|   force_validate();
 | |
|   return DictIterator{*this};
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::make_iterator(int mode) {
 | |
|   force_validate();
 | |
|   DictIterator it{*this, mode};
 | |
|   it.rewind();
 | |
|   return it;
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::init_iterator(bool backw, bool invert_first) {
 | |
|   return make_iterator((int)backw + 2 * (int)invert_first);
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::begin() {
 | |
|   return init_iterator();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::end() {
 | |
|   return null_iterator();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::cbegin() {
 | |
|   return begin();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::cend() {
 | |
|   return end();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::rbegin() {
 | |
|   return init_iterator(true);
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::rend() {
 | |
|   return null_iterator();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::crbegin() {
 | |
|   return rbegin();
 | |
| }
 | |
| 
 | |
| DictIterator DictionaryFixed::crend() {
 | |
|   return rend();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  *
 | |
|  *   END (ITERATORS)
 | |
|  *
 | |
|  */
 | |
| 
 | |
| std::pair<Ref<Cell>, bool> DictionaryFixed::extract_prefix_subdict_internal(Ref<Cell> dict, td::ConstBitPtr prefix,
 | |
|                                                                             int prefix_len, bool remove_prefix) const {
 | |
|   if (is_empty() || prefix_len <= 0) {
 | |
|     return {{}, false};  // unchanged
 | |
|   }
 | |
|   if (prefix_len > get_key_bits()) {
 | |
|     return {{}, true};  // empty dict
 | |
|   }
 | |
|   int n = get_key_bits(), m = 0;
 | |
|   while (true) {
 | |
|     LabelParser label{std::move(dict), n - m, label_mode()};
 | |
|     int l = std::min(prefix_len - m, label.l_bits);
 | |
|     if (label.common_prefix_len(prefix + m, l) < l) {
 | |
|       return {{}, true};  // empty dict
 | |
|     }
 | |
|     if (m + label.l_bits < prefix_len) {
 | |
|       m += label.l_bits;
 | |
|       dict = label.remainder->prefetch_ref(prefix[m++]);
 | |
|       continue;
 | |
|     }
 | |
|     // end, have consumed all of prefix
 | |
|     vm::CellBuilder cb;
 | |
|     if (!remove_prefix) {
 | |
|       if (!m) {
 | |
|         // dictionary unchanged: all keys already begin with prefix
 | |
|         return {{}, false};
 | |
|       }
 | |
|       // concatenate prefix with a suffix of the label
 | |
|       assert(m <= prefix_len);
 | |
|       unsigned char buffer[max_key_bytes];
 | |
|       auto p = td::BitPtr{buffer};
 | |
|       p.copy_from(prefix, m);
 | |
|       label.extract_label_to(p + m);
 | |
|       append_dict_label(cb, p, m + label.l_bits, key_bits);
 | |
|     } else if (!label.l_same) {
 | |
|       m += label.l_bits - prefix_len;  // leave that many last bits of the label
 | |
|       append_dict_label(cb, label.bits_end() - m, m, key_bits - prefix_len);
 | |
|       label.skip_label();
 | |
|     } else {
 | |
|       m += label.l_bits - prefix_len;  // leave that many last bits of the label
 | |
|       append_dict_label_same(cb, label.l_same & 1, m, key_bits - prefix_len);
 | |
|     }
 | |
|     if (!cb.append_cellslice_bool(*label.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot create new dictionary root while constructing prefix subdictionary"};
 | |
|     }
 | |
|     return {Ref<Cell>{cb.finalize()}, true};
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::cut_prefix_subdict(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
 | |
|   force_validate();
 | |
|   if (prefix_len < 0) {
 | |
|     return false;
 | |
|   }
 | |
|   if (prefix_len > key_bits && remove_prefix) {
 | |
|     return false;
 | |
|   }
 | |
|   auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
 | |
|   if (remove_prefix) {
 | |
|     key_bits -= prefix_len;
 | |
|   }
 | |
|   if (res.second) {
 | |
|     set_root_cell(std::move(res.first));
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Ref<vm::Cell> DictionaryFixed::extract_prefix_subdict_root(td::ConstBitPtr prefix, int prefix_len, bool remove_prefix) {
 | |
|   force_validate();
 | |
|   auto res = extract_prefix_subdict_internal(get_root_cell(), prefix, prefix_len, remove_prefix);
 | |
|   return res.second ? res.first : root_cell;
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, int> DictionaryFixed::dict_filter(Ref<Cell> dict, td::BitPtr key, int n,
 | |
|                                                        const DictionaryFixed::filter_func_t& check_leaf) const {
 | |
|   // std::cerr << "dictionary filter for " << n << "-bit key = " << (key + n - key_bits).to_hex(key_bits - n)
 | |
|   //           << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // empty dictionary, return unchanged
 | |
|     return {{}, 0};
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, label_mode()};
 | |
|   assert(label.l_bits >= 0 && label.l_bits <= n);
 | |
|   label.extract_label_to(key);
 | |
|   key += label.l_bits;
 | |
|   if (label.l_bits == n) {
 | |
|     // leaf
 | |
|     int res = check_leaf(label.remainder.write(), key - key_bits, key_bits);
 | |
|     return {{}, res < 0 ? res : !res};
 | |
|   }
 | |
|   // fork, process left and right subtrees
 | |
|   ++key;
 | |
|   key[-1] = false;
 | |
|   int delta = label.l_bits + 1;
 | |
|   n -= delta;
 | |
|   auto left_res = dict_filter(label.remainder->prefetch_ref(0), key, n, check_leaf);
 | |
|   if (left_res.second < 0) {
 | |
|     return left_res;
 | |
|   }
 | |
|   key[-1] = true;
 | |
|   auto right_res = dict_filter(label.remainder->prefetch_ref(1), key, n, check_leaf);
 | |
|   if ((left_res.second | right_res.second) <= 0) {
 | |
|     // error in right, or both left and right unchanged
 | |
|     return right_res;
 | |
|   }
 | |
|   auto left = left_res.second ? std::move(left_res.first) : label.remainder->prefetch_ref(0);
 | |
|   auto right = right_res.second ? std::move(right_res.first) : label.remainder->prefetch_ref(1);
 | |
|   auto changes = left_res.second + right_res.second;
 | |
|   label.clear();
 | |
|   if (left.is_null()) {
 | |
|     if (right.is_null()) {
 | |
|       // both branches are empty => the result is an empty tree
 | |
|       return {{}, changes};
 | |
|     }
 | |
|     std::swap(left, right);
 | |
|   } else if (right.is_null()) {
 | |
|     key[-1] = false;
 | |
|   } else {
 | |
|     // both new branches are non-empty => create new fork
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key - delta, label.l_bits, n + delta);
 | |
|     return {finish_create_fork(cb, std::move(left), std::move(right), n + 1), changes};
 | |
|   }
 | |
|   // only one child (in `left`) remains, collapse an edge
 | |
|   // NB: similar to code in lookup_delete()
 | |
|   assert(left.not_null() && right.is_null());
 | |
|   LabelParser label2{std::move(left), n, label_mode()};
 | |
|   label2.extract_label_to(key);
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key - delta, delta + label2.l_bits, n + delta);
 | |
|   if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
 | |
|     throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|   }
 | |
|   label2.remainder.clear();
 | |
|   return {cb.finalize(), changes};
 | |
| }
 | |
| 
 | |
| int DictionaryFixed::filter(DictionaryFixed::filter_func_t check_leaf) {
 | |
|   force_validate();
 | |
|   unsigned char buffer[DictionaryFixed::max_key_bytes];
 | |
|   auto res = dict_filter(get_root_cell(), td::BitPtr{buffer}, key_bits, check_leaf);
 | |
|   if (res.second > 0) {
 | |
|     // std::cerr << "after filter (" << res.second << " changes): new augmented dictionary root is:\n";
 | |
|     // vm::load_cell_slice(res.first).print_rec(std::cerr);
 | |
|     set_root_cell(std::move(res.first));
 | |
|   }
 | |
|   return res.second;
 | |
| }
 | |
| 
 | |
| void Dictionary::map(const map_func_t& map_func) {
 | |
|   force_validate();
 | |
|   int key_len = get_key_bits();
 | |
|   unsigned char key_buffer[max_key_bytes];
 | |
|   auto res = dict_map(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, map_func);
 | |
|   set_root_cell(std::move(res));
 | |
| }
 | |
| 
 | |
| void Dictionary::map(const simple_map_func_t& simple_map_func) {
 | |
|   using namespace std::placeholders;
 | |
|   map_func_t map_func = std::bind(simple_map_func, _1, _2);
 | |
|   map(map_func);
 | |
| }
 | |
| 
 | |
| // mode: +1 = forbid empty dict1 with non-empty dict2
 | |
| //       +2 = forbid empty dict2 with non-empty dict1
 | |
| Ref<Cell> DictionaryFixed::dict_combine_with(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n,
 | |
|                                              int total_key_len, const DictionaryFixed::combine_func_t& combine_func,
 | |
|                                              int mode, int skip1, int skip2) const {
 | |
|   if (dict1.is_null()) {
 | |
|     assert(!skip2);
 | |
|     if ((mode & 1) && dict2.is_null()) {
 | |
|       throw CombineError{};
 | |
|     }
 | |
|     return dict2;
 | |
|   } else if (dict2.is_null()) {
 | |
|     assert(!skip1);
 | |
|     if ((mode & 2)) {
 | |
|       throw CombineError{};
 | |
|     }
 | |
|     return dict1;
 | |
|   }
 | |
|   // both dictionaries non-empty
 | |
|   // skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
 | |
|   // skip2: similar for dict2
 | |
|   // resulting dictionary will have n-bit keys
 | |
|   LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
 | |
|   int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
 | |
|   assert(l1 >= 0 && l2 >= 0);
 | |
|   assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
 | |
|   assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
 | |
|   label1.extract_label_to(key_buffer - skip1);
 | |
|   int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
 | |
|   assert(c >= 0 && c <= l1 && c <= l2);
 | |
|   if (c < l1 && c < l2) {
 | |
|     // the two dictionaries have disjoint keys
 | |
|     dict1.clear();
 | |
|     dict2.clear();
 | |
|     if ((mode & 3)) {
 | |
|       throw CombineError{};
 | |
|     }
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key_buffer + c + 1, l1 - c - 1, n - c - 1);
 | |
|     if (!cell_builder_add_slice_bool(cb, *label1.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot prune label of an old dictionary cell while merging dictionaries"};
 | |
|     }
 | |
|     label1.remainder.clear();
 | |
|     dict1 = cb.finalize();
 | |
|     // cb.reset(); // included into finalize();
 | |
|     // now dict1 has been "pruned" -- first skip1+c+1 bits removed from its root egde label
 | |
|     label2.extract_label_to(key_buffer - skip2);
 | |
|     append_dict_label(cb, key_buffer + c + 1, l2 - c - 1, n - c - 1);
 | |
|     if (!cell_builder_add_slice_bool(cb, *label2.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|     }
 | |
|     label2.remainder.clear();
 | |
|     dict2 = cb.finalize();
 | |
|     // now dict2 has also been pruned
 | |
|     if (!key_buffer[c]) {
 | |
|       std::swap(dict1, dict2);
 | |
|     }
 | |
|     // put dict1 into the left tree (with smaller labels), dict2 into the right tree
 | |
|     append_dict_label(cb, key_buffer, c, n);
 | |
|     return finish_create_fork(cb, std::move(dict1), std::move(dict2), n - c);
 | |
|   }
 | |
|   if (c == l1 && c == l2) {
 | |
|     // funny enough, the non-skipped parts of labels of l1 and l2 match
 | |
|     dict1.clear();
 | |
|     dict2.clear();
 | |
|     label2.skip_label();
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key_buffer, c, n);
 | |
|     if (c == n) {
 | |
|       // our two dictionaries are in fact leafs with matching edge labels (keys)
 | |
|       if (!combine_func(cb, std::move(label1.remainder), std::move(label2.remainder), key_buffer + n - total_key_len,
 | |
|                         total_key_len)) {
 | |
|         // alas, the two values did not combine, this key will be absent from resulting dictionary
 | |
|         return {};
 | |
|       }
 | |
|       return cb.finalize();
 | |
|     }
 | |
|     assert(c < n);
 | |
|     key_buffer += c + 1;
 | |
|     key_buffer[-1] = 0;
 | |
|     // combine left subtrees
 | |
|     auto c1 = dict_combine_with(label1.remainder->prefetch_ref(0), label2.remainder->prefetch_ref(0), key_buffer,
 | |
|                                 n - c - 1, total_key_len, combine_func);
 | |
|     key_buffer[-1] = 1;
 | |
|     // combine right subtrees
 | |
|     auto c2 = dict_combine_with(label1.remainder->prefetch_ref(1), label2.remainder->prefetch_ref(1), key_buffer,
 | |
|                                 n - c - 1, total_key_len, combine_func);
 | |
|     label1.remainder.clear();
 | |
|     label2.remainder.clear();
 | |
|     // c1 and c2 are merged left and right children of dict1 and dict2
 | |
|     if (!c1.is_null() && !c2.is_null()) {
 | |
|       // both children non-empty, simply put them into the new node
 | |
|       return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
 | |
|     }
 | |
|     if (c1.is_null() && c2.is_null()) {
 | |
|       return {};  // both children empty, resulting dictionary also empty
 | |
|     }
 | |
|     // exactly one of c1 and c2 is non-empty, have to merge labels
 | |
|     bool sw = c1.is_null();
 | |
|     key_buffer[-1] = sw;
 | |
|     if (sw) {
 | |
|       c1 = std::move(c2);
 | |
|     }
 | |
|     LabelParser label3{std::move(c1), n - c - 1, label_mode()};
 | |
|     label3.extract_label_to(key_buffer);
 | |
|     key_buffer -= c + 1;
 | |
|     // store combined label for the new edge
 | |
|     cb.reset();
 | |
|     append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
 | |
|     // store payload
 | |
|     if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|     }
 | |
|     return cb.finalize();
 | |
|   }
 | |
|   if (c == l1) {
 | |
|     assert(c < l2);
 | |
|     dict1.clear();
 | |
|     if ((mode & 2)) {
 | |
|       throw CombineError{};
 | |
|     }
 | |
|     // children of root node of dict1
 | |
|     auto c1 = label1.remainder->prefetch_ref(0);
 | |
|     auto c2 = label1.remainder->prefetch_ref(1);
 | |
|     label1.remainder.clear();
 | |
|     // have to merge dict2 with one of the children of dict1
 | |
|     label2.extract_label_to(key_buffer - skip2);  // dict2 has longer label, extract it
 | |
|     bool sw = key_buffer[c];
 | |
|     if (!sw) {
 | |
|       // merge c1 with dict2
 | |
|       c1 = dict_combine_with(std::move(c1), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
 | |
|                              combine_func, mode, 0, skip2 + c + 1);
 | |
|     } else {
 | |
|       // merge c2 with dict2
 | |
|       c2 = dict_combine_with(std::move(c2), std::move(dict2), key_buffer + c + 1, n - c - 1, total_key_len,
 | |
|                              combine_func, mode, 0, skip2 + c + 1);
 | |
|     }
 | |
|     if (!c1.is_null() && !c2.is_null()) {
 | |
|       CellBuilder cb;
 | |
|       append_dict_label(cb, key_buffer, c, n);
 | |
|       return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
 | |
|     }
 | |
|     // one of children is empty, have to merge root edges
 | |
|     key_buffer[c] = !sw;
 | |
|     if (!sw) {
 | |
|       std::swap(c1, c2);
 | |
|     }
 | |
|     assert(!c1.is_null() && c2.is_null());
 | |
|     LabelParser label3{std::move(c1), n - c - 1, label_mode()};
 | |
|     label3.extract_label_to(key_buffer + c + 1);
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
 | |
|     // store payload
 | |
|     if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|     }
 | |
|     return cb.finalize();
 | |
|   } else {
 | |
|     assert(c == l2 && c < l1);
 | |
|     dict2.clear();
 | |
|     if ((mode & 1)) {
 | |
|       throw CombineError{};
 | |
|     }
 | |
|     // children of root node of dict2
 | |
|     label2.skip_label();  // dict2 had shorter label anyway, label1 is already unpacked
 | |
|     auto c1 = label2.remainder->prefetch_ref(0);
 | |
|     auto c2 = label2.remainder->prefetch_ref(1);
 | |
|     label2.remainder.clear();
 | |
|     // have to merge dict1 with one of the children of dict2
 | |
|     bool sw = key_buffer[c];
 | |
|     if (!sw) {
 | |
|       // merge dict1 with c1
 | |
|       c1 = dict_combine_with(std::move(dict1), std::move(c1), key_buffer + c + 1, n - c - 1, total_key_len,
 | |
|                              combine_func, mode, skip1 + c + 1, 0);
 | |
|     } else {
 | |
|       // merge dict1 with c2
 | |
|       c2 = dict_combine_with(std::move(dict1), std::move(c2), key_buffer + c + 1, n - c - 1, total_key_len,
 | |
|                              combine_func, mode, skip1 + c + 1, 0);
 | |
|     }
 | |
|     if (!c1.is_null() && !c2.is_null()) {
 | |
|       CellBuilder cb;
 | |
|       append_dict_label(cb, key_buffer, c, n);
 | |
|       return finish_create_fork(cb, std::move(c1), std::move(c2), n - c);
 | |
|     }
 | |
|     // one of children is empty, have to merge root edges
 | |
|     key_buffer[c] = !sw;
 | |
|     if (!sw) {
 | |
|       std::swap(c1, c2);
 | |
|     }
 | |
|     assert(!c1.is_null() && c2.is_null());
 | |
|     LabelParser label3{std::move(c1), n - c - 1, label_mode()};
 | |
|     label3.extract_label_to(key_buffer + c + 1);
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key_buffer, c + 1 + label3.l_bits, n);
 | |
|     // store payload
 | |
|     if (!cell_builder_add_slice_bool(cb, *label3.remainder)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old dictionary cell while merging edges"};
 | |
|     }
 | |
|     return cb.finalize();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const combine_func_t& combine_func, int mode) {
 | |
|   force_validate();
 | |
|   dict2.force_validate();
 | |
|   int key_len = get_key_bits();
 | |
|   if (key_len != dict2.get_key_bits()) {
 | |
|     throw VmError{Excno::dict_err, "cannot combine dictionaries with different key lengths"};
 | |
|   }
 | |
|   unsigned char key_buffer[max_key_bytes];
 | |
|   try {
 | |
|     auto res = dict_combine_with(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len,
 | |
|                                  combine_func, mode);
 | |
|     set_root_cell(std::move(res));
 | |
|     return true;
 | |
|   } catch (CombineError) {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::combine_with(DictionaryFixed& dict2, const simple_combine_func_t& simple_combine_func, int mode) {
 | |
|   using namespace std::placeholders;
 | |
|   combine_func_t combine_func = std::bind(simple_combine_func, _1, _2, _3);
 | |
|   return combine_with(dict2, combine_func, mode);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::combine_with(DictionaryFixed& dict2) {
 | |
|   return combine_with(dict2,
 | |
|                       [](CellBuilder&, Ref<CellSlice>, Ref<CellSlice>, td::ConstBitPtr key, int key_len) -> bool {
 | |
|                         LOG(WARNING) << "dictionary merge conflict for key " << key.to_hex(key_len);
 | |
|                         throw CombineError{};
 | |
|                       });
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::dict_check_for_each(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
 | |
|                                           const DictionaryFixed::foreach_func_t& foreach_func,
 | |
|                                           bool invert_first) const {
 | |
|   if (dict.is_null()) {
 | |
|     return true;
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, label_mode()};
 | |
|   int l = label.l_bits;
 | |
|   label.extract_label_to(key_buffer);
 | |
|   if (l == n) {
 | |
|     // leaf node, value left in label.remainder
 | |
|     return foreach_func(std::move(label.remainder), key_buffer + n - total_key_len, total_key_len);
 | |
|   }
 | |
|   assert(l >= 0 && l < n);
 | |
|   // a fork with two children, c1 and c2
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   label.remainder.clear();
 | |
|   key_buffer += l + 1;
 | |
|   if (l) {
 | |
|     invert_first = false;
 | |
|   } else if (invert_first) {
 | |
|     std::swap(c1, c2);
 | |
|   }
 | |
|   key_buffer[-1] = invert_first;
 | |
|   // recursive check_foreach applied to both children
 | |
|   if (!dict_check_for_each(std::move(c1), key_buffer, n - l - 1, total_key_len, foreach_func)) {
 | |
|     return false;
 | |
|   }
 | |
|   key_buffer[-1] = !invert_first;
 | |
|   return dict_check_for_each(std::move(c2), key_buffer, n - l - 1, total_key_len, foreach_func);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::check_for_each(const foreach_func_t& foreach_func, bool invert_first) {
 | |
|   force_validate();
 | |
|   if (is_empty()) {
 | |
|     return true;
 | |
|   }
 | |
|   int key_len = get_key_bits();
 | |
|   unsigned char key_buffer[max_key_bytes];
 | |
|   return dict_check_for_each(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first);
 | |
| }
 | |
| 
 | |
| static inline bool set_bit(td::BitPtr ptr, bool value = true) {
 | |
|   *ptr = value;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // mode: +1 = check augmentation of dict1, +2 = ... of dict2
 | |
| bool DictionaryFixed::dict_scan_diff(Ref<Cell> dict1, Ref<Cell> dict2, td::BitPtr key_buffer, int n, int total_key_len,
 | |
|                                      const scan_diff_func_t& diff_func, int mode, int skip1, int skip2) const {
 | |
|   // skip1: remove that much first bits from all keys in dictionary dict1 (its keys are actually n + skip1 bits long)
 | |
|   // skip2: similar for dict2
 | |
|   // pretending to compare subdictionaries with n-bit keys
 | |
|   if (dict1.is_null()) {
 | |
|     if (dict2.is_null()) {
 | |
|       return true;  // both dictionaries are empty
 | |
|     }
 | |
|     assert(!skip2);
 | |
|     // dict1 empty, dict2 non-empty -> parse label of dict2
 | |
|     LabelParser label{dict2, n, label_mode()};
 | |
|     label.extract_label_to(key_buffer);
 | |
|     if (label.l_bits >= n) {
 | |
|       assert(label.l_bits == n);
 | |
|       // leaf in dict2, empty dict1
 | |
|       auto key = key_buffer + label.l_bits - total_key_len;
 | |
|       if ((mode & 2) && !check_leaf(label.remainder, key, total_key_len)) {
 | |
|         throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
 | |
|       }
 | |
|       return diff_func(key, total_key_len, {}, std::move(label.remainder));
 | |
|     }
 | |
|     n -= label.l_bits + 1;
 | |
|     key_buffer += label.l_bits + 1;
 | |
|     if ((mode & 2) && !check_fork_raw(label.remainder, n + 1)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
 | |
|     }
 | |
|     // compare {} with each of children of dict2
 | |
|     for (unsigned sw = 0; sw < 2; sw++) {
 | |
|       key_buffer[-1] = (bool)sw;
 | |
|       if (!dict_scan_diff({}, label.remainder->prefetch_ref(sw), key_buffer, n, total_key_len, diff_func, mode)) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   } else if (dict2.is_null()) {
 | |
|     assert(!skip1);
 | |
|     // dict2 empty, dict1 non-empty -> parse label of dict1
 | |
|     LabelParser label{dict1, n, label_mode()};
 | |
|     label.extract_label_to(key_buffer);
 | |
|     if (label.l_bits >= n) {
 | |
|       assert(label.l_bits == n);
 | |
|       // leaf in dict1, empty dict2
 | |
|       auto key = key_buffer + label.l_bits - total_key_len;
 | |
|       if ((mode & 1) && !check_leaf(label.remainder, key, total_key_len)) {
 | |
|         throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
 | |
|       }
 | |
|       return diff_func(key, total_key_len, std::move(label.remainder), {});
 | |
|     }
 | |
|     n -= label.l_bits + 1;
 | |
|     key_buffer += label.l_bits + 1;
 | |
|     if ((mode & 1) && !check_fork_raw(label.remainder, n + 1)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
 | |
|     }
 | |
|     // compare each of children of dict1 with {}
 | |
|     for (unsigned sw = 0; sw < 2; sw++) {
 | |
|       key_buffer[-1] = (bool)sw;
 | |
|       if (!dict_scan_diff(label.remainder->prefetch_ref(sw), {}, key_buffer, n, total_key_len, diff_func, mode)) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
|   // both dictionaries non-empty
 | |
|   if (skip1 == skip2 && (dict1 == dict2 || dict1->get_hash() == dict2->get_hash())) {
 | |
|     // dictionaries match, subtree comparison not necessary
 | |
|     return true;
 | |
|   }
 | |
|   LabelParser label1{dict1, n + skip1, label_mode()}, label2{dict2, n + skip2, label_mode()};
 | |
|   int l1 = label1.l_bits - skip1, l2 = label2.l_bits - skip2;
 | |
|   assert(l1 >= 0 && l2 >= 0);
 | |
|   assert(!skip1 || label1.common_prefix_len(key_buffer - skip1, skip1) == skip1);
 | |
|   assert(!skip2 || label2.common_prefix_len(key_buffer - skip2, skip2) == skip2);
 | |
|   label1.extract_label_to(key_buffer - skip1);
 | |
|   int c = label2.common_prefix_len(key_buffer - skip2, skip2 + l1) - skip2;
 | |
|   assert(c >= 0 && c <= l1 && c <= l2);
 | |
|   if (c < l1 && c < l2) {
 | |
|     // the two dictionaries have disjoint keys
 | |
|     if (!key_buffer[c]) {
 | |
|       // all keys of dict1 are before dict2
 | |
|       return dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode) &&
 | |
|              dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode);
 | |
|     } else {
 | |
|       // all keys of dict2 are before dict1
 | |
|       return dict_scan_diff({}, std::move(dict2), key_buffer - skip2, n + skip2, total_key_len, diff_func, mode) &&
 | |
|              dict_scan_diff(std::move(dict1), {}, key_buffer - skip1, n + skip1, total_key_len, diff_func, mode);
 | |
|     }
 | |
|   }
 | |
|   if (c == l1 && c == l2) {
 | |
|     // funny enough, the non-skipped parts of labels of l1 and l2 match
 | |
|     dict1.clear();
 | |
|     dict2.clear();
 | |
|     label2.skip_label();
 | |
|     if (c == n) {
 | |
|       // our two dictionaries are in fact leafs with matching edge labels (keys)
 | |
|       auto key = key_buffer + n - total_key_len;
 | |
|       if ((mode & 1) && !check_leaf(label1.remainder, key, total_key_len)) {
 | |
|         throw VmError{Excno::dict_err, "invalid leaf in the first dictionary being compared"};
 | |
|       }
 | |
|       if ((mode & 2) && !check_leaf(label2.remainder, key, total_key_len)) {
 | |
|         throw VmError{Excno::dict_err, "invalid leaf in the second dictionary being compared"};
 | |
|       }
 | |
|       return label1.remainder->contents_equal(*label2.remainder) ||
 | |
|              diff_func(key, total_key_len, std::move(label1.remainder), std::move(label2.remainder));
 | |
|     }
 | |
|     assert(c < n);
 | |
|     key_buffer += c + 1;
 | |
|     n -= c + 1;
 | |
|     if ((mode & 1) && !check_fork_raw(label1.remainder, n + 1)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
 | |
|     }
 | |
|     if ((mode & 2) && !check_fork_raw(label2.remainder, n + 1)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
 | |
|     }
 | |
|     for (unsigned sw = 0; sw <= 1; sw++) {
 | |
|       key_buffer[-1] = (bool)sw;
 | |
|       // compare left and then right subtrees
 | |
|       if (!dict_scan_diff(label1.remainder->prefetch_ref(sw), label2.remainder->prefetch_ref(sw), key_buffer, n,
 | |
|                           total_key_len, diff_func, mode)) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
|   if (c == l1) {
 | |
|     assert(c < l2);
 | |
|     dict1.clear();
 | |
|     if ((mode & 1) && !check_fork_raw(label1.remainder, n - c)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the first dictionary being compared"};
 | |
|     }
 | |
|     // children of root node of dict1
 | |
|     auto c1 = label1.remainder->prefetch_ref(0);
 | |
|     auto c2 = label1.remainder->prefetch_ref(1);
 | |
|     label1.remainder.clear();
 | |
|     // have to compare dict2 with one of the children of dict1
 | |
|     label2.extract_label_to(key_buffer - skip2);  // dict2 has longer label, extract it
 | |
|     key_buffer += c + 1;
 | |
|     n -= c + 1;
 | |
|     bool sw = key_buffer[-1];
 | |
|     key_buffer[-1] = false;
 | |
|     if (!sw) {
 | |
|       // compare c1 with dict2, then c2 with {}
 | |
|       return dict_scan_diff(std::move(c1), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
 | |
|                             skip2 + c + 1) &&
 | |
|              set_bit(key_buffer - 1) &&
 | |
|              dict_scan_diff(std::move(c2), {}, key_buffer, n, total_key_len, diff_func, mode);
 | |
|     } else {
 | |
|       // compare c1 with {}, then c2 with dict2
 | |
|       return dict_scan_diff(std::move(c1), {}, key_buffer, n, total_key_len, diff_func, mode) &&
 | |
|              set_bit(key_buffer - 1) &&
 | |
|              dict_scan_diff(std::move(c2), std::move(dict2), key_buffer, n, total_key_len, diff_func, mode, 0,
 | |
|                             skip2 + c + 1);
 | |
|     }
 | |
|   } else {
 | |
|     assert(c == l2 && c < l1);
 | |
|     dict2.clear();
 | |
|     label2.skip_label();  // dict2 had shorter label anyway, label1 is already unpacked
 | |
|     if ((mode & 2) && !check_fork_raw(label2.remainder, n - c)) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork in the second dictionary being compared"};
 | |
|     }
 | |
|     // children of root node of dict2
 | |
|     auto c1 = label2.remainder->prefetch_ref(0);
 | |
|     auto c2 = label2.remainder->prefetch_ref(1);
 | |
|     label2.remainder.clear();
 | |
|     // have to compare dict1 with one of the children of dict2
 | |
|     key_buffer += c + 1;
 | |
|     n -= c + 1;
 | |
|     bool sw = key_buffer[-1];
 | |
|     key_buffer[-1] = false;
 | |
|     if (!sw) {
 | |
|       // compare dict1 with c1, then {} with c2
 | |
|       return dict_scan_diff(std::move(dict1), std::move(c1), key_buffer, n, total_key_len, diff_func, mode,
 | |
|                             skip1 + c + 1, 0) &&
 | |
|              set_bit(key_buffer - 1) &&
 | |
|              dict_scan_diff({}, std::move(c2), key_buffer, n, total_key_len, diff_func, mode);
 | |
|     } else {
 | |
|       // compare {} with c1, then dict1 with c2
 | |
|       return dict_scan_diff({}, std::move(c1), key_buffer, n, total_key_len, diff_func, mode) &&
 | |
|              set_bit(key_buffer - 1) &&
 | |
|              dict_scan_diff(std::move(dict1), std::move(c2), key_buffer, n, total_key_len, diff_func, mode,
 | |
|                             skip1 + c + 1, 0);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::scan_diff(DictionaryFixed& dict2, const scan_diff_func_t& diff_func, int check_augm) {
 | |
|   force_validate();
 | |
|   dict2.force_validate();
 | |
|   int key_len = get_key_bits();
 | |
|   if (key_len != dict2.get_key_bits()) {
 | |
|     throw VmError{Excno::dict_err, "cannot compare dictionaries with different key lengths"};
 | |
|   }
 | |
|   unsigned char key_buffer[max_key_bytes];
 | |
|   try {
 | |
|     return dict_scan_diff(get_root_cell(), dict2.get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, diff_func,
 | |
|                           check_augm);
 | |
|   } catch (CombineError) {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::dict_validate_check(Ref<Cell> dict, td::BitPtr key_buffer, int n, int total_key_len,
 | |
|                                           const DictionaryFixed::foreach_func_t& foreach_func,
 | |
|                                           bool invert_first) const {
 | |
|   //LOG(DEBUG) << "dict_validate_check for " << total_key_len - n << "-bit key prefix " << (key_buffer - n + total_key_len).to_hex(total_key_len - n);
 | |
|   if (dict.is_null()) {
 | |
|     return true;
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, label_mode()};
 | |
|   int l = label.l_bits;
 | |
|   label.extract_label_to(key_buffer);
 | |
|   if (l == n) {
 | |
|     // leaf node, value left in label.remainder
 | |
|     vm::CellSlice cs{*label.remainder};
 | |
|     auto key = key_buffer + n - total_key_len;
 | |
|     if (!(check_leaf(cs, key, total_key_len) && foreach_func(std::move(label.remainder), key, total_key_len))) {
 | |
|       LOG(DEBUG) << "invalid dictionary leaf node with " << total_key_len << "-bit key " << key.to_hex(total_key_len);
 | |
|       return false;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
|   assert(l >= 0 && l < n);
 | |
|   // a fork with two children, c1 and c2
 | |
|   auto c1 = label.remainder.write().fetch_ref();
 | |
|   auto c2 = label.remainder.unique_write().fetch_ref();
 | |
|   key_buffer += l + 1;
 | |
|   n -= l + 1;
 | |
|   if (!check_fork(label.remainder.write(), c1, c2, n + 1)) {
 | |
|     LOG(DEBUG) << "invalid dictionary fork augmentation for fork node with " << total_key_len - n - 1
 | |
|                << "-bit key prefix " << (key_buffer + n - total_key_len).to_hex(total_key_len - n - 1);
 | |
|     return false;
 | |
|   }
 | |
|   label.remainder.clear();
 | |
|   if (l) {
 | |
|     invert_first = false;
 | |
|   } else if (invert_first) {
 | |
|     std::swap(c1, c2);
 | |
|   }
 | |
|   key_buffer[-1] = invert_first;
 | |
|   // recursive check_foreach applied to both children
 | |
|   if (!dict_validate_check(std::move(c1), key_buffer, n, total_key_len, foreach_func)) {
 | |
|     return false;
 | |
|   }
 | |
|   key_buffer[-1] = !invert_first;
 | |
|   return dict_validate_check(std::move(c2), key_buffer, n, total_key_len, foreach_func);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::validate_check(const DictionaryFixed::foreach_func_t& foreach_func, bool invert_first) {
 | |
|   if (!validate()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (is_empty()) {
 | |
|     return true;
 | |
|   }
 | |
|   int key_len = get_key_bits();
 | |
|   unsigned char key_buffer[max_key_bytes];
 | |
|   return dict_validate_check(get_root_cell(), td::BitPtr{key_buffer}, key_len, key_len, foreach_func, invert_first);
 | |
| }
 | |
| 
 | |
| bool DictionaryFixed::validate_all() {
 | |
|   return validate_check([](Ref<CellSlice> value, td::ConstBitPtr key, int n) { return true; }) || invalidate();
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 
 | |
|  *   PREFIX DICTIONARIES
 | |
|  * 
 | |
|  */
 | |
| 
 | |
| std::pair<Ref<CellSlice>, int> PrefixDictionary::lookup_prefix(td::ConstBitPtr key, int key_len) {
 | |
|   force_validate();
 | |
|   int n = get_key_bits();
 | |
|   if (is_empty()) {
 | |
|     return std::make_pair(Ref<CellSlice>{}, 0);
 | |
|   }
 | |
|   //std::cerr << "dictionary lookup for key = " << key.to_hex(key_len) << std::endl;
 | |
|   Ref<Cell> cell = get_root_cell();
 | |
|   int m = key_len;
 | |
|   while (true) {
 | |
|     LabelParser label{std::move(cell), n, 1};
 | |
|     int l = label.common_prefix_len(key, m);
 | |
|     if (l < label.l_bits) {
 | |
|       //std::cerr << "(not a prefix)\n";
 | |
|       return std::make_pair(Ref<CellSlice>{}, key_len - m + l);
 | |
|     }
 | |
|     n -= label.l_bits;
 | |
|     m -= label.l_bits;
 | |
|     assert(m >= 0);
 | |
|     label.skip_label();
 | |
|     Ref<CellSlice> cs = std::move(label.remainder);
 | |
|     if (!cs->have(1)) {
 | |
|       throw VmError{Excno::dict_err, "no node constructor in a prefix code dictionary"};
 | |
|     }
 | |
|     if (!cs.unique_write().fetch_ulong(1)) {
 | |
|       return std::make_pair(std::move(cs), key_len - m);
 | |
|     }
 | |
|     if (!n) {
 | |
|       throw VmError{Excno::dict_err, "a fork node in a prefix code dictionary with zero remaining key length"};
 | |
|     }
 | |
|     if (cs->size() != 0 || cs->size_refs() != 2) {
 | |
|       throw VmError{Excno::dict_err, "invalid fork node in a prefix code dictionary"};
 | |
|     }
 | |
|     if (!m) {
 | |
|       return std::make_pair(Ref<CellSlice>{}, key_len);
 | |
|     }
 | |
|     key += label.l_bits;
 | |
|     bool sw = *key++;
 | |
|     //std::cerr << "key bit at position " << key_bits - n << " equals " << sw << std::endl;
 | |
|     --n;
 | |
|     --m;
 | |
|     cell = cs->prefetch_ref(sw);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> PrefixDictionary::lookup(td::ConstBitPtr key, int key_len) {
 | |
|   force_validate();
 | |
|   if (key_len > get_key_bits()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto res = lookup_prefix(key, key_len);
 | |
|   return res.second == key_len ? std::move(res.first) : Ref<CellSlice>{};
 | |
| }
 | |
| 
 | |
| bool PrefixDictionary::set_gen(td::ConstBitPtr key, int key_len, const std::function<bool(CellBuilder&)>& store_val,
 | |
|                                SetMode mode) {
 | |
|   force_validate();
 | |
|   if (key_len > get_key_bits() || key_len < 0) {
 | |
|     return false;
 | |
|   }
 | |
|   auto res = pfx_dict_set(get_root_cell(), key, key_len, get_key_bits(), store_val, mode);
 | |
|   if (res.second) {
 | |
|     set_root_cell(std::move(res.first));
 | |
|   }
 | |
|   return res.second;
 | |
| }
 | |
| 
 | |
| bool PrefixDictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
 | |
|   return set_gen(key, key_len, [value](CellBuilder& cb) { return cell_builder_add_slice_bool(cb, *value); }, mode);
 | |
| }
 | |
| 
 | |
| bool PrefixDictionary::set_builder(td::ConstBitPtr key, int key_len, Ref<CellBuilder> val_b, SetMode mode) {
 | |
|   return set_gen(key, key_len, [val_b](CellBuilder& cb) { return cb.append_builder_bool(val_b); }, mode);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> PrefixDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
 | |
|   force_validate();
 | |
|   if (key_len > get_key_bits() || key_len < 0) {
 | |
|     return {};
 | |
|   }
 | |
|   auto res = pfx_dict_lookup_delete(get_root_cell(), key, key_len, get_key_bits());
 | |
|   if (res.first.not_null()) {
 | |
|     set_root_cell(std::move(res.second));
 | |
|   }
 | |
|   return std::move(res.first);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 
 | |
|  *   AUGMENTED DICTIONARIES
 | |
|  * 
 | |
|  */
 | |
| 
 | |
| namespace dict {
 | |
| 
 | |
| bool AugmentationData::check_empty(vm::CellSlice& cs) const {
 | |
|   vm::CellBuilder cb;
 | |
|   return eval_empty(cb) && cb.contents_equal(cs);
 | |
| }
 | |
| 
 | |
| bool AugmentationData::check_leaf(vm::CellSlice& cs, vm::CellSlice& val_cs) const {
 | |
|   vm::CellBuilder cb;
 | |
|   return eval_leaf(cb, val_cs) && cb.contents_equal(cs);
 | |
| }
 | |
| 
 | |
| bool AugmentationData::check_fork(vm::CellSlice& cs, vm::CellSlice& left_cs, vm::CellSlice& right_cs) const {
 | |
|   vm::CellBuilder cb;
 | |
|   return eval_fork(cb, left_cs, right_cs) && cb.contents_equal(cs);
 | |
| }
 | |
| 
 | |
| Ref<vm::CellSlice> AugmentationData::extract_extra(vm::CellSlice& cs) const {
 | |
|   Ref<CellSlice> res{true, cs};
 | |
|   return skip_extra(cs) && res.write().cut_tail(cs) ? std::move(res) : Ref<CellSlice>{};
 | |
| }
 | |
| 
 | |
| Ref<vm::CellSlice> AugmentationData::extract_extra(Ref<vm::CellSlice> cs_ref) const {
 | |
|   CellSlice cs{*cs_ref};
 | |
|   return skip_extra(cs) && cs_ref.write().cut_tail(cs) ? std::move(cs_ref) : Ref<CellSlice>{};
 | |
| }
 | |
| 
 | |
| bool AugmentationData::extract_extra_to(vm::CellSlice& cs, vm::CellSlice& extra) const {
 | |
|   extra = cs;
 | |
|   return cs.is_valid() && skip_extra(cs) && extra.cut_tail(cs);
 | |
| }
 | |
| 
 | |
| }  // namespace dict
 | |
| 
 | |
| using dict::AugmentationData;
 | |
| using dict::LabelParser;
 | |
| 
 | |
| AugmentedDictionary::AugmentedDictionary(int _n, const AugmentationData& _aug, bool validate)
 | |
|     : DictionaryFixed(_n, false), aug(_aug) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| AugmentedDictionary::AugmentedDictionary(Ref<CellSlice> _root, int _n, const AugmentationData& _aug, bool validate)
 | |
|     : DictionaryFixed(std::move(_root), _n, false), aug(_aug) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| AugmentedDictionary::AugmentedDictionary(Ref<Cell> cell, int _n, const AugmentationData& _aug, bool validate)
 | |
|     : DictionaryFixed(std::move(cell), _n, false), aug(_aug) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| AugmentedDictionary::AugmentedDictionary(DictNonEmpty, Ref<CellSlice> _root, int _n, const AugmentationData& _aug,
 | |
|                                          bool validate)
 | |
|     : DictionaryFixed(DictNonEmpty{}, std::move(_root), _n, false), aug(_aug) {
 | |
|   if (validate) {
 | |
|     force_validate();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::validate() {
 | |
|   if (is_valid()) {
 | |
|     return true;
 | |
|   }
 | |
|   if (flags & f_invalid) {
 | |
|     return false;
 | |
|   }
 | |
|   if (key_bits < 0 || key_bits > max_key_bits) {
 | |
|     return invalidate();
 | |
|   }
 | |
|   if (flags & f_root_cached) {
 | |
|     if (root.is_null() || !root->size()) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     bool non_empty = root->prefetch_ulong(1);
 | |
|     if (non_empty && !root->size_refs()) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     if (root_cell.not_null()) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     vm::CellSlice cs{*root};
 | |
|     if (!cs.advance(1)) {
 | |
|       return invalidate();
 | |
|     }
 | |
|     if (non_empty) {
 | |
|       root_cell = cs.fetch_ref();
 | |
|       auto root_extra = get_root_extra();
 | |
|       if (!(root_extra.not_null() && root_extra->contents_equal(cs))) {
 | |
|         return invalidate();
 | |
|       }
 | |
|     } else {
 | |
|       if (!aug.check_empty(cs)) {
 | |
|         return invalidate();
 | |
|       }
 | |
|     }
 | |
|   } else if (root.not_null()) {
 | |
|     return invalidate();
 | |
|   }
 | |
|   flags |= f_valid;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::get_root() const {
 | |
|   if (!(flags & f_root_cached) && !compute_root()) {
 | |
|     return {};
 | |
|   }
 | |
|   return root;
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::extract_root() && {
 | |
|   if (!(flags & f_root_cached) && !compute_root()) {
 | |
|     return {};
 | |
|   }
 | |
|   flags = f_invalid;
 | |
|   return std::move(root);
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) const & {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (root_cell.is_null()) {
 | |
|     return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
 | |
|   } else {
 | |
|     return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::append_dict_to_bool(CellBuilder& cb) && {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   flags = f_invalid;
 | |
|   if (root_cell.is_null()) {
 | |
|     return cb.store_zeroes_bool(1) && aug.eval_empty(cb);
 | |
|   } else {
 | |
|     return cb.store_ones_bool(1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra());
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::compute_root() const {
 | |
|   if (!is_valid()) {
 | |
|     return false;
 | |
|   }
 | |
|   if (root_cell.is_null()) {
 | |
|     root = get_empty_dictionary();
 | |
|     flags |= f_root_cached;
 | |
|     return true;
 | |
|   }
 | |
|   CellBuilder cb;
 | |
|   if (cb.store_long_bool(1, 1) && cb.store_ref_bool(root_cell) && cb.append_cellslice_bool(get_root_extra())) {
 | |
|     root = Ref<CellSlice>{true, cb.finalize()};
 | |
|     flags |= f_root_cached;
 | |
|     return true;
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::get_empty_dictionary() const {
 | |
|   CellBuilder cb;
 | |
|   cb.store_long(0, 1);
 | |
|   return aug.eval_empty(cb) ? Ref<CellSlice>{true, cb.finalize()} : Ref<CellSlice>{};
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::get_node_extra(Ref<Cell> cell_ref, int n) const {
 | |
|   if (cell_ref.is_null()) {
 | |
|     CellBuilder cb;
 | |
|     if (!aug.eval_empty(cb)) {
 | |
|       return {};
 | |
|     }
 | |
|     return Ref<CellSlice>{true, cb.finalize()};
 | |
|   }
 | |
|   LabelParser label{std::move(cell_ref), n, 2};
 | |
|   label.skip_label();
 | |
|   if (label.l_bits == n) {
 | |
|     return aug.extract_extra(std::move(label.remainder));
 | |
|   } else if (label.remainder.write().advance_refs(2)) {
 | |
|     vm::CellSlice cs{*label.remainder};
 | |
|     if (aug.skip_extra(cs) && cs.empty_ext()) {
 | |
|       return std::move(label.remainder);
 | |
|     }
 | |
|   }
 | |
|   return {};
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::extract_leaf_value(Ref<CellSlice> leaf) const {
 | |
|   if (leaf.not_null() && aug.skip_extra(leaf.write())) {
 | |
|     return std::move(leaf);
 | |
|   } else {
 | |
|     return Ref<CellSlice>{};
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::get_root_extra() const {
 | |
|   return get_node_extra(root_cell, key_bits);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::extract_value(Ref<CellSlice> value_extra) const {
 | |
|   if (value_extra.not_null() && aug.skip_extra(value_extra.write())) {
 | |
|     return value_extra;
 | |
|   } else {
 | |
|     return {};
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<Cell> AugmentedDictionary::extract_value_ref(Ref<CellSlice> value_extra) const {
 | |
|   if (value_extra.not_null() && aug.skip_extra(value_extra.write()) && value_extra->size_ext() == 0x10000) {
 | |
|     return value_extra->prefetch_ref();
 | |
|   } else {
 | |
|     return {};
 | |
|   }
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::decompose_value_extra(Ref<CellSlice> value_extra) const {
 | |
|   if (value_extra.is_null()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto extra = aug.extract_extra(value_extra.write());
 | |
|   if (extra.is_null()) {
 | |
|     return {};
 | |
|   } else {
 | |
|     return {std::move(value_extra), std::move(extra)};
 | |
|   }
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, Ref<CellSlice>> AugmentedDictionary::decompose_value_ref_extra(Ref<CellSlice> value_extra) const {
 | |
|   if (value_extra.is_null()) {
 | |
|     return {};
 | |
|   }
 | |
|   auto extra = aug.extract_extra(value_extra.write());
 | |
|   if (extra.is_null() || value_extra->size_ext() != 0x10000) {
 | |
|     return {};
 | |
|   } else {
 | |
|     return {value_extra->prefetch_ref(), std::move(extra)};
 | |
|   }
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::lookup_with_extra(td::ConstBitPtr key, int key_len) {
 | |
|   return DictionaryFixed::lookup(key, key_len);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::lookup(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value(lookup_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| Ref<Cell> AugmentedDictionary::lookup_ref(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value_ref(lookup_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::lookup_extra(td::ConstBitPtr key, int key_len) {
 | |
|   return decompose_value_extra(lookup_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, Ref<CellSlice>> AugmentedDictionary::lookup_ref_extra(td::ConstBitPtr key, int key_len) {
 | |
|   return decompose_value_ref_extra(lookup_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::lookup_delete_with_extra(td::ConstBitPtr key, int key_len) {
 | |
|   return DictionaryFixed::lookup_delete(key, key_len);
 | |
| }
 | |
| 
 | |
| Ref<CellSlice> AugmentedDictionary::lookup_delete(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value(lookup_delete_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| Ref<Cell> AugmentedDictionary::lookup_delete_ref(td::ConstBitPtr key, int key_len) {
 | |
|   return extract_value_ref(lookup_delete_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::lookup_delete_extra(td::ConstBitPtr key, int key_len) {
 | |
|   return decompose_value_extra(lookup_delete_with_extra(key, key_len));
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::check_leaf(CellSlice& cs, td::ConstBitPtr key, int key_len) const {
 | |
|   vm::CellSlice extra;
 | |
|   return aug.extract_extra_to(cs, extra) && aug.check_leaf_key_extra(cs, extra, key, key_len);
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::check_fork(CellSlice& cs, Ref<Cell> c1, Ref<Cell> c2, int n) const {
 | |
|   if (n <= 0) {
 | |
|     return false;
 | |
|   }
 | |
|   auto extra1 = get_node_extra(std::move(c1), n - 1);
 | |
|   auto extra2 = get_node_extra(std::move(c2), n - 1);
 | |
|   return extra1.not_null() && extra2.not_null() && aug.check_fork(cs, extra1.write(), extra2.write());
 | |
| }
 | |
| 
 | |
| Ref<Cell> AugmentedDictionary::finish_create_leaf(CellBuilder& cb, const CellSlice& value) const {
 | |
|   CellSlice value_copy{value};
 | |
|   if (!aug.eval_leaf(cb, value_copy)) {
 | |
|     throw VmError{Excno::dict_err, "cannot compute and store extra value into an augmented dictionary cell"};
 | |
|   }
 | |
|   if (!cb.append_cellslice_bool(value)) {
 | |
|     throw VmError{Excno::dict_err, "cannot store new value into an augmented dictionary cell"};
 | |
|   }
 | |
|   return cb.finalize();
 | |
| }
 | |
| 
 | |
| Ref<Cell> AugmentedDictionary::finish_create_fork(CellBuilder& cb, Ref<Cell> c1, Ref<Cell> c2, int n) const {
 | |
|   assert(n > 0);
 | |
|   if (!(cb.store_ref_bool(c1) && cb.store_ref_bool(c2))) {
 | |
|     throw VmError{Excno::dict_err, "cannot store branch references into an augmented dictionary cell"};
 | |
|   }
 | |
|   auto extra1 = get_node_extra(std::move(c1), n - 1);
 | |
|   auto extra2 = get_node_extra(std::move(c2), n - 1);
 | |
|   if (extra1.is_null()) {
 | |
|     throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
 | |
|   }
 | |
|   if (extra2.is_null()) {
 | |
|     throw VmError{Excno::dict_err, "cannot extract extra value from left branch of an augmented dictionary fork node"};
 | |
|   }
 | |
|   if (!aug.eval_fork(cb, extra1.write(), extra2.write())) {
 | |
|     throw VmError{Excno::dict_err, "cannot compute extra value for an augmented dictionary fork node"};
 | |
|   }
 | |
|   return cb.finalize();
 | |
| }
 | |
| 
 | |
| std::pair<Ref<Cell>, bool> AugmentedDictionary::dict_set(Ref<Cell> dict, td::ConstBitPtr key, int n,
 | |
|                                                          const CellSlice& value, Dictionary::SetMode mode) const {
 | |
|   //std::cerr << "augmented dictionary modification for " << n << "-bit key = " << key.to_hex(n) << std::endl;
 | |
|   if (dict.is_null()) {
 | |
|     // the dictionary is very empty
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       return std::make_pair<Ref<Cell>, bool>({}, false);
 | |
|     }
 | |
|     // create an one-element dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     return std::make_pair(finish_create_leaf(cb, value), true);
 | |
|   }
 | |
|   LabelParser label{std::move(dict), n, 2};
 | |
|   label.validate();
 | |
|   int pfx_len = label.common_prefix_len(key, n);
 | |
|   assert(pfx_len >= 0 && pfx_len <= label.l_bits && label.l_bits <= n);
 | |
|   if (pfx_len < label.l_bits) {
 | |
|     // have to insert a new node (fork) inside the current edge
 | |
|     if (mode == Dictionary::SetMode::Replace) {
 | |
|       // key not found, return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // first, create the edge + new leaf cell
 | |
|     int m = n - pfx_len - 1;
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key + (pfx_len + 1), m, m);
 | |
|     Ref<Cell> c1 = finish_create_leaf(cb, value);  // new leaf cell corresponding to `key`
 | |
|     //cb.reset();
 | |
|     // create the lower portion of the old edge
 | |
|     int t = label.l_bits - pfx_len - 1;
 | |
|     auto cs = std::move(label.remainder);
 | |
|     if (label.l_same) {
 | |
|       append_dict_label_same(cb, label.l_same & 1, t, m);
 | |
|     } else {
 | |
|       cs.write().advance(pfx_len + 1);
 | |
|       append_dict_label(cb, cs->data_bits(), t, m);
 | |
|       cs.unique_write().advance(t);
 | |
|     }
 | |
|     // now cs is the old payload of the edge, either a value or two subdictionary references
 | |
|     if (!cell_builder_add_slice_bool(cb, *cs)) {
 | |
|       throw VmError{Excno::cell_ov, "cannot change label of an old augmented dictionary cell (?)"};
 | |
|     }
 | |
|     Ref<Cell> c2 = cb.finalize();  // the other child of the new fork
 | |
|     // cb.reset();
 | |
|     append_dict_label(cb, key, pfx_len, n);
 | |
|     bool sw_bit = key[pfx_len];
 | |
|     if (sw_bit) {
 | |
|       c1.swap(c2);
 | |
|     }
 | |
|     return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - pfx_len), true);
 | |
|   }
 | |
|   if (label.l_bits == n) {
 | |
|     // the edge leads to a leaf node
 | |
|     // this leaf node already contains a value for the key wanted
 | |
|     if (mode == Dictionary::SetMode::Add) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     // replace the value of the only element of the dictionary
 | |
|     CellBuilder cb;
 | |
|     append_dict_label(cb, key, n, n);
 | |
|     return std::make_pair(finish_create_leaf(cb, value), true);
 | |
|   }
 | |
|   // main case: the edge leads to a fork, have to insert new value either in the right or in the left subtree
 | |
|   auto c1 = label.remainder->prefetch_ref(0);
 | |
|   auto c2 = label.remainder->prefetch_ref(1);
 | |
|   label.remainder.clear();
 | |
|   if (key[label.l_bits]) {
 | |
|     // insert key into the right child (c2)
 | |
|     auto res = dict_set(std::move(c2), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c2 = std::move(res.first);
 | |
|   } else {
 | |
|     // insert key into the left child (c1)
 | |
|     auto res = dict_set(std::move(c1), key + (label.l_bits + 1), n - label.l_bits - 1, value, mode);
 | |
|     if (!res.second) {
 | |
|       // return unchanged dictionary
 | |
|       return std::make_pair(Ref<Cell>{}, false);
 | |
|     }
 | |
|     c1 = std::move(res.first);
 | |
|   }
 | |
|   // create a new label with the same content
 | |
|   CellBuilder cb;
 | |
|   append_dict_label(cb, key, label.l_bits, n);
 | |
|   return std::make_pair(finish_create_fork(cb, std::move(c1), std::move(c2), n - label.l_bits), true);
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, Ref<CellSlice> value, SetMode mode) {
 | |
|   return value.not_null() && set(key, key_len, *value, mode);
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::set(td::ConstBitPtr key, int key_len, const CellSlice& value, SetMode mode) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits()) {
 | |
|     return false;
 | |
|   }
 | |
|   auto res = dict_set(get_root_cell(), key, key_len, value, mode);
 | |
|   if (res.second) {
 | |
|     //vm::CellSlice cs{vm::NoVmOrd(), res.first};
 | |
|     //std::cerr << "new augmented dictionary root is:\n";
 | |
|     //cs.print_rec(std::cerr);
 | |
|     set_root_cell(std::move(res.first));
 | |
|   }
 | |
|   return res.second;
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::set_ref(td::ConstBitPtr key, int key_len, Ref<Cell> value_ref, SetMode mode) {
 | |
|   if (value_ref.not_null()) {
 | |
|     CellBuilder cb;
 | |
|     cb.store_ref(std::move(value_ref));
 | |
|     return set(key, key_len, load_cell_slice(cb.finalize()));
 | |
|   } else {
 | |
|     return false;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::set_builder(td::ConstBitPtr key, int key_len, const CellBuilder& value, SetMode mode) {
 | |
|   return set(key, key_len, load_cell_slice(value.finalize_copy()));
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::check_for_each_extra(const foreach_extra_func_t& foreach_extra_func, bool invert_first) {
 | |
|   force_validate();
 | |
|   const auto& augm = aug;
 | |
|   foreach_func_t foreach_func = [&foreach_extra_func, &augm](Ref<vm::CellSlice> value_extra, td::ConstBitPtr key,
 | |
|                                                              int key_len) {
 | |
|     auto extra = augm.extract_extra(value_extra.write());
 | |
|     return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
 | |
|   };
 | |
|   return DictionaryFixed::check_for_each(foreach_func, invert_first);
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::dict_traverse_extra(
 | |
|     Ref<Cell> dict, td::BitPtr key_buffer, int n, const traverse_func_t& traverse_node) const {
 | |
|   int m = get_key_bits();
 | |
|   while (true) {
 | |
|     CHECK(dict.not_null());
 | |
|     LabelParser label{std::move(dict), n, 2};
 | |
|     label.extract_label_to(key_buffer);
 | |
|     key_buffer += label.l_bits;
 | |
|     n -= label.l_bits;
 | |
|     if (n <= 0) {
 | |
|       // reached a leaf, check it
 | |
|       assert(!n);
 | |
|       auto pair = decompose_value_extra(std::move(label.remainder));
 | |
|       if (pair.first.is_null()) {
 | |
|         throw VmError{Excno::dict_err, "invalid leaf value/extra in an augmented dictionary"};
 | |
|       }
 | |
|       int r = traverse_node(key_buffer - m, m, pair.second /* extra */, pair.first /* value */);
 | |
|       if (r < 0) {
 | |
|         throw CombineErrorValue{r};
 | |
|       } else if (r > 0) {
 | |
|         return pair;
 | |
|       } else {
 | |
|         return {};
 | |
|       }
 | |
|     }
 | |
|     // visit (traverse) fork
 | |
|     auto c1 = label.remainder.write().fetch_ref(), c2 = label.remainder.write().fetch_ref();
 | |
|     int r = traverse_node(key_buffer + n - m, m - n, std::move(label.remainder) /* extra */, {});
 | |
|     if (r < 0 || (r & 3) == 3) {
 | |
|       throw CombineErrorValue{r};
 | |
|     } else if (!(r & 3)) {
 | |
|       return {};
 | |
|     }
 | |
|     // r = 1 : visit only left, 2 = visit only right, 5 = visit right, then left, 6 = visit left, then right
 | |
|     ++key_buffer;
 | |
|     --n;
 | |
|     bool sw = r & 1;
 | |
|     if (sw) {
 | |
|       std::swap(c1, c2);
 | |
|     }
 | |
|     if (r & 4) {
 | |
|       // have to visit both children in some order; do a recursive call to visit the first child
 | |
|       key_buffer[-1] = sw;
 | |
|       auto tmp = dict_traverse_extra(std::move(c1), key_buffer, n, traverse_node);
 | |
|       if (tmp.first.not_null()) {
 | |
|         return tmp;
 | |
|       }
 | |
|     }
 | |
|     // visit the remaining child
 | |
|     key_buffer[-1] = !sw;
 | |
|     dict = std::move(c2);
 | |
|   }
 | |
| }
 | |
| 
 | |
| std::pair<Ref<CellSlice>, Ref<CellSlice>> AugmentedDictionary::traverse_extra(td::BitPtr key_buffer, int key_len,
 | |
|                                                                               const traverse_func_t& traverse_node) {
 | |
|   force_validate();
 | |
|   if (key_len != get_key_bits() || is_empty()) {
 | |
|     return {};
 | |
|   }
 | |
|   return dict_traverse_extra(get_root_cell(), key_buffer, key_len, traverse_node);
 | |
| }
 | |
| 
 | |
| bool AugmentedDictionary::validate_check_extra(const AugmentedDictionary::foreach_extra_func_t& foreach_extra_func,
 | |
|                                                bool invert_first) {
 | |
|   const AugmentationData& augm = aug;
 | |
|   int key_len = get_key_bits();
 | |
|   return validate_check(
 | |
|       [&foreach_extra_func, &augm, key_len](Ref<CellSlice> value_extra, td::ConstBitPtr key, int value) {
 | |
|         auto extra = augm.extract_extra(value_extra.write());
 | |
|         return extra.not_null() && foreach_extra_func(std::move(value_extra), std::move(extra), key, key_len);
 | |
|       },
 | |
|       invert_first);
 | |
| }
 | |
| 
 | |
| }  // namespace vm
 |