/*
    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 .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "tlbc-gen-cpp.h"
#include "td/utils/bits.h"
#include "td/utils/filesystem.h"
namespace tlbc {
/*
 * 
 *   C++ CODE GENERATION
 * 
 */
CppIdentSet global_cpp_ids;
std::vector> cpp_type;
bool add_type_members;
std::set forbidden_cpp_idents, local_forbidden_cpp_idents;
std::vector const_type_expr_cpp_idents;
std::vector const_type_expr_simple;
void init_forbidden_cpp_idents() {
  std::set& f = forbidden_cpp_idents;
  f.insert("true");
  f.insert("false");
  f.insert("int");
  f.insert("bool");
  f.insert("unsigned");
  f.insert("long");
  f.insert("short");
  f.insert("char");
  f.insert("void");
  f.insert("class");
  f.insert("struct");
  f.insert("enum");
  f.insert("union");
  f.insert("public");
  f.insert("private");
  f.insert("protected");
  f.insert("extern");
  f.insert("static");
  f.insert("final");
  f.insert("if");
  f.insert("else");
  f.insert("while");
  f.insert("do");
  f.insert("for");
  f.insert("break");
  f.insert("continue");
  f.insert("return");
  f.insert("virtual");
  f.insert("explicit");
  f.insert("override");
  f.insert("new");
  f.insert("delete");
  f.insert("operator");
  f.insert("Ref");
  f.insert("Cell");
  f.insert("CellSlice");
  f.insert("Anything");
  f.insert("RefAnything");
  f.insert("Nat");
  f.insert("t_Nat");
  f.insert("t_RefCell");
  f.insert("t_Anything");
  f.insert("TLB");
  f.insert("TLB_Complex");
  f.insert("PrettyPrinter");
  std::set& l = local_forbidden_cpp_idents;
  l.insert("cons_len");
  l.insert("cons_len_exact");
  l.insert("cons_tag");
  l.insert("skip");
  l.insert("validate_skip");
  l.insert("get_size");
  l.insert("pack");
  l.insert("unpack");
  l.insert("ops");
  l.insert("cs");
  l.insert("cb");
  l.insert("cell_ref");
  l.insert("type_class");
  l.insert("pp");
  l.insert("weak");
}
std::string CppIdentSet::compute_cpp_ident(std::string orig_ident, int count) {
  std::ostringstream os;
  int a, r = 0, cnt = 0;
  bool prev_skip = false;
  for (int c : orig_ident) {
    bool pp = prev_skip;
    prev_skip = true;
    if (c & 0x80) {
      if ((c & 0xe0) == 0xc0) {
        a = (c & 0x1f);
        r = 1;
        continue;
      } else if ((c & 0xf0) == 0xe0) {
        a = (c & 0x0f);
        r = 2;
        continue;
      }
      if ((c & 0xc0) != 0x80) {
        continue;
      }
      if (!r) {
        continue;
      }
      a = (a << 6) | (c & 0x3f);
      if (--r) {
        continue;
      }
      c = a;
    }
    prev_skip = false;
    if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
      os << (char)c;
      cnt++;
      continue;
    }
    if (c >= '0' && c <= '9') {
      if (!cnt) {
        os << '_';
        cnt++;
      }
      os << (char)c;
      cnt++;
      continue;
    }
    if (c >= 0x410 && c < 0x450) {
      os << (char)(0xc0 + (c >> 6)) << (char)(0x80 + (c & 0x3f));
      cnt++;
      continue;
    }
    prev_skip = true;
    if (!pp) {
      os << '_';
    }
  }
  if (!cnt) {
    os << '_';
    prev_skip = true;
  }
  if (count) {
    os << count;
  }
  return os.str();
}
bool CppIdentSet::is_good_ident(std::string ident) {
  return !defined(ident) && !forbidden_cpp_idents.count(ident) &&
         !(extra_forbidden_idents && extra_forbidden_idents->count(ident));
}
std::string CppIdentSet::new_ident(std::string orig_ident, int count, std::string suffix) {
  while (true) {
    std::string ident = compute_cpp_ident(orig_ident, count) + suffix;
    if (is_good_ident(ident)) {
      cpp_idents.insert(ident);
      return ident;
    }
    ++count;
  }
}
struct SizeWriter {
  int sz;
  explicit SizeWriter(int _sz) : sz(_sz) {
  }
  void write(std::ostream& os) const;
};
void SizeWriter::write(std::ostream& os) const {
  if (sz < 0x10000) {
    os << sz;
  } else {
    os << "0x" << std::hex << sz << std::dec;
  }
}
std::ostream& operator<<(std::ostream& os, SizeWriter w) {
  w.write(os);
  return os;
}
unsigned long long CppTypeCode::compute_selector_mask() const {
  unsigned long long z = 0, w = 1;
  int c = 0;
  for (int v : cons_tag_map) {
    if (v > c) {
      c = v;
      z |= w;
    }
    w <<= 1;
  }
  return z;
}
struct HexConstWriter {
  unsigned long long mask;
  explicit HexConstWriter(unsigned long long _mask) : mask(_mask){};
  void write(std::ostream& os) const;
};
void HexConstWriter::write(std::ostream& os) const {
  if (mask < 32) {
    os << mask;
  } else {
    os << "0x" << std::hex << mask << std::dec;
  }
  if (mask >= (1ULL << 31)) {
    os << (mask >= (1ULL << 32) ? "ULL" : "U");
  }
}
std::ostream& operator<<(std::ostream& os, HexConstWriter w) {
  w.write(os);
  return os;
}
cpp_val_type detect_cpp_type(const TypeExpr* expr) {
  if (expr->tp == TypeExpr::te_Ref) {
    return ct_cell;
  }
  if (expr->is_nat) {
    return ct_int32;
  }
  MinMaxSize sz = expr->compute_size();
  int l = sz.fixed_bit_size();
  if (expr->is_nat_subtype) {
    return l == 1 ? ct_bool : ct_int32;
  }
  if (expr->tp == TypeExpr::te_CondType) {
    cpp_val_type subtype = detect_cpp_type(expr->args.at(1));
    if (subtype == ct_slice || subtype == ct_cell || subtype == ct_integer || subtype == ct_bitstring ||
        subtype == ct_enum) {
      return subtype;
    }
    if ((subtype == ct_int32 || subtype == ct_int64) && expr->args[1]->is_integer() > 0) {
      return subtype;
    }
    return ct_slice;
  }
  int x = expr->is_integer();
  if (sz.max_size() & 0xff) {
    return ct_slice;
  }
  if (!x) {
    const Type* ta = expr->type_applied;
    if (expr->tp == TypeExpr::te_Apply && ta && ta->is_simple_enum) {
      return ct_enum;
    }
    if (expr->tp == TypeExpr::te_Apply && ta && ta->type_idx < builtin_types_num &&
        (ta == Bits_type || ta->get_name().at(0) == 'b')) {
      return (l >= 0 && l <= 256) ? ct_bits : ct_bitstring;
    }
    if (expr->tp == TypeExpr::te_Tuple && expr->args[1]->tp == TypeExpr::te_Apply &&
        expr->args[1]->type_applied->is_bool) {
      return (l >= 0 && l <= 256) ? ct_bits : ct_bitstring;
    }
    return ct_slice;
  }
  l = (sz.max_size() >> 8);
  if (x > 0 && l == 1) {
    return ct_bool;
  }
  if (l < 32) {
    return ct_int32;
  }
  if (l == 32) {
    return (x < 0 ? ct_int32 : ct_uint32);
  }
  if (l < 64) {
    return ct_int64;
  }
  if (l == 64) {
    return (x < 0 ? ct_int64 : ct_uint64);
  }
  return ct_integer;
}
cpp_val_type detect_field_cpp_type(const Field& field) {
  return field.subrec ? ct_subrecord : detect_cpp_type(field.type);
}
void show_valtype(std::ostream& os, cpp_val_type x, int size = -1, bool pass_value = false) {
  switch (x) {
    case ct_void:
      os << "void";
      break;
    case ct_slice:
      os << "Ref";
      break;
    case ct_cell:
      os << "Ref| ";
      break;
    case ct_typeptr:
      os << "const TLB*";
      break;
    case ct_typeref:
      os << "const TLB&";
      break;
    case ct_bitstring:
      os << "Ref";
      break;
    case ct_bits:
      if (pass_value) {
        os << "const ";
      }
      os << "td::BitArray<" << size << ">";
      if (pass_value) {
        os << "&";
      }
      break;
    case ct_integer:
      os << "RefInt256";
      break;
    case ct_bool:
      os << "bool";
      break;
    case ct_enum:
      os << "char";
      break;
    case ct_int32:
      os << "int";
      break;
    case ct_uint32:
      os << "unsigned";
      break;
    case ct_int64:
      os << "long long";
      break;
    case ct_uint64:
      os << "unsigned long long";
      break;
    case ct_subrecord:
      if (pass_value) {
        os << "const ";
      }
      os << "::Record";
      if (pass_value) {
        os << "&";
      }
      break;
    default:
      os << "";
  }
}
bool CppValType::needs_move() const {
  return (vt == ct_cell || vt == ct_slice || vt == ct_bitstring || vt == ct_integer);
}
void CppValType::show(std::ostream& os, bool pass_value) const {
  show_valtype(os, vt, size, pass_value);
}
std::ostream& operator<<(std::ostream& os, CppValType cvt) {
  cvt.show(os);
  return os;
}
void CppTypeCode::ConsField::print_type(std::ostream& os, bool pass_value) const {
  if (ctype != ct_subrecord) {
    get_cvt().show(os, pass_value);
  } else {
    assert(subrec);
    if (pass_value) {
      os << "const ";
    }
    subrec->print_full_name(os);
    if (pass_value) {
      os << "&";
    }
  }
}
void CppTypeCode::ConsRecord::print_full_name(std::ostream& os) const {
  os << cpp_type.cpp_type_class_name << "::" << cpp_name;
}
void CppTypeCode::assign_class_name() {
  std::string type_name = type.get_name();
  sym_idx_t name = type.type_name;
  if (!name && type.parent_type_idx >= 0) {
    int i = type.parent_type_idx;
    while (true) {
      name = types.at(i).type_name;
      if (name || types.at(i).parent_type_idx < 0) {
        break;
      }
      i = types.at(i).parent_type_idx;
    }
    if (name) {
      type_name = sym::symbols.get_name(name) + "_aux";
    }
  }
  cpp_type_class_name = global_cpp_ids.new_ident(type_name);
  if (params) {
    cpp_type_template_name = global_cpp_ids.new_ident(cpp_type_class_name + "T");
  } else {
    cpp_type_var_name = global_cpp_ids.new_ident(std::string{"t_"} + cpp_type_class_name);
  }
}
void CppTypeCode::assign_cons_names() {
  cons_enum_name.resize(cons_num);
  for (int i = 0; i < cons_num; i++) {
    sym_idx_t cons = type.constructors.at(i)->constr_name;
    if (cons) {
      cons_enum_name[i] = local_cpp_ids.new_ident(sym::symbols.get_name(cons));
    } else if (type.const_param_idx >= 0) {
      int pv = type.constructors[i]->get_const_param(type.const_param_idx);
      cons_enum_name[i] = local_cpp_ids.new_ident(pv ? "cons" : "cons0", pv);
    } else {
      cons_enum_name[i] = local_cpp_ids.new_ident("cons", i + 1);
    }
  }
}
void CppTypeCode::assign_cons_values() {
  std::vector> a;
  a.reserve(cons_num);
  for (int i = 0; i < cons_num; i++) {
    a.emplace_back(type.constructors[i]->begins_with.min(), i);
  }
  std::sort(a.begin(), a.end());
  cons_enum_value.resize(cons_num);
  cons_idx_by_enum.resize(cons_num);
  int i = 0;
  for (auto z : a) {
    cons_enum_value[z.second] = i;
    cons_idx_by_enum[i++] = z.second;
  }
}
std::vector std_field_names = {"x", "y", "z", "t", "u", "v", "w"};
void CppTypeCode::assign_record_cons_names() {
  for (int i = 0; i < cons_num; i++) {
    const Constructor& ctor = *type.constructors.at(i);
    records.emplace_back(*this, ctor, i);
    ConsRecord& record = records.back();
    record.has_trivial_name = (cons_num <= 1 || !ctor.constr_name);
    record.declared = false;
    record.cpp_name = local_cpp_ids.new_ident(cons_num <= 1 ? "Record" : std::string{"Record_"} + cons_enum_name[i]);
    CppIdentSet rec_cpp_ids;
    rec_cpp_ids.insert("type_class");
    rec_cpp_ids.insert(record.cpp_name);
    // maybe : add field identifiers from type class context (?)
    for (int j = 0; j < ctor.fields_num; j++) {
      const Field& field = ctor.fields.at(j);
      if (field.constraint) {
      } else if (!field.implicit) {
        MinMaxSize sz = field.type->compute_size();
        if (!sz.max_size()) {
          continue;
        }
        std::string field_name;
        const ConsRecord* subrec = nullptr;
        if (field.name) {
          field_name = rec_cpp_ids.new_ident(field.get_name());
        } else if (field.subrec) {
          field_name = rec_cpp_ids.new_ident("r", 1);
          subrec = &cpp_type.at(field.type->args.at(0)->type_applied->type_idx)->records.at(0);
        } else if (field.type->tp == TypeExpr::te_Ref) {
          field_name = rec_cpp_ids.new_ident("ref", 1);
        }
        record.cpp_fields.emplace_back(field, field_name, detect_field_cpp_type(field), sz.fixed_bit_size(), j, subrec);
      } else if (field.used && (add_type_members || field.type->is_nat_subtype)) {
        std::string field_name = rec_cpp_ids.new_ident(field.get_name());
        record.cpp_fields.emplace_back(field, field_name, field.type->is_nat_subtype ? ct_int32 : ct_typeptr, -1, j,
                                       nullptr, true);
      }
    }
    auto q = std_field_names.cbegin();
    for (auto& fi : record.cpp_fields) {
      if (fi.name.empty()) {
        bool is_ok = false;
        while (q < std_field_names.cend()) {
          if (!rec_cpp_ids.defined(*q)) {
            fi.name = rec_cpp_ids.new_ident(*q++);
            is_ok = true;
            break;
          }
        }
        if (!is_ok) {
          fi.name = rec_cpp_ids.new_ident("f", 1);
        }
      }
    }
    record.is_trivial = (record.cpp_fields.size() <= 1);
    record.is_small = (record.cpp_fields.size() <= 3);
    record.inline_record = (record.cpp_fields.size() <= 2);
    cpp_val_type t = ct_unknown;
    if (record.is_trivial) {
      t = (record.cpp_fields.size() == 1) ? record.cpp_fields.at(0).ctype : ct_void;
    }
    std::vector tv;
    for (const auto& f : record.cpp_fields) {
      if (f.ctype == ct_subrecord) {
        record.is_trivial = record.is_small = false;
      } else if (!f.implicit) {
        tv.push_back(f.ctype);
      }
    }
    record.equiv_cpp_type = t;
    record.equiv_cpp_types = tv;
    record.triv_conflict = false;
    for (int j = 0; j < i; j++) {
      if (records[j].equiv_cpp_types == tv) {
        record.triv_conflict = records[j].triv_conflict = true;
        break;
      }
    }
  }
}
bool CppTypeCode::ConsRecord::recover_idents(CppIdentSet& idents) const {
  bool is_ok = idents.insert(cpp_name) && idents.insert("type_class");
  for (const auto& f : cpp_fields) {
    is_ok &= idents.insert(f.name);
  }
  return is_ok;
}
void CppTypeCode::assign_class_field_names() {
  char cn = 'm', ct = 'X';
  int c = 0;
  for (int z : type.args) {
    bool f = z & Type::_IsNat;
    bool neg = (z & Type::_IsNeg);
    type_param_is_nat.push_back(f);
    type_param_is_neg.push_back(neg);
    std::string id;
    if (!neg && !c++) {
      template_args += ", ";
      constructor_args += ", ";
    }
    if (f) {
      id = local_cpp_ids.new_ident(std::string{cn}, 0, "_");
      if (cn != 't') {
        ++cn;
      }
      if (!neg) {
        template_args += "int ";
        constructor_args += "int ";
      } else {
        skip_extra_args += ", int& ";
        skip_extra_args_pass += ", ";
      }
    } else {
      id = local_cpp_ids.new_ident(std::string{ct}, 0, "_");
      if (ct != 'Z') {
        ++ct;
      } else {
        ct = 'T';
      }
      assert(!neg);
      template_args += "typename ";
      constructor_args += "const TLB& ";
    }
    type_param_name.push_back(id);
    if (!neg) {
      template_args += id;
      constructor_args += id;
    } else {
      skip_extra_args += id;
      skip_extra_args_pass += id;
    }
  }
}
bool CppTypeCode::compute_simple_cons_tags() {
  if (!type.is_pfx_determ || type.useful_depth > 8) {
    return false;
  }
  int d = type.useful_depth;
  int n = (1 << d);
  cons_tag_map.resize(n, 0);
  //std::cerr << "compute_simple_cons_tags() for `" << type.get_name() << "` (d=" << d << ")\n";
  for (int i = 0; i < cons_num; i++) {
    int t = cons_enum_value.at(i) + 1;
    for (unsigned long long z : type.constructors[i]->begins_with.pfx) {
      int l = std::min(63 - td::count_trailing_zeroes_non_zero64(z), d);
      assert(l <= d);
      int a = d ? (int)((z & (z - 1)) >> (64 - d)) : 0;
      int b = (1 << (d - l));
      while (b-- > 0) {
        assert(!cons_tag_map.at(a) || cons_tag_map[a] == t);
        cons_tag_map[a++] = t;
      }
    }
  }
  int c = 0;
  for (int v : cons_tag_map) {
    if (v && v != c && v != ++c) {
      return false;
    }
  }
  return true;
}
bool CppTypeCode::check_incremental_cons_tags() const {
  if (!cons_num || common_cons_len < 0) {
    return false;
  }
  int l = common_cons_len;
  if (!l || l > 32) {
    return true;
  }
  for (int i = 0; i < cons_num; i++) {
    unsigned long long tag = (type.constructors.at(i)->tag >> (64 - l));
    if (tag != (unsigned)cons_enum_value.at(i)) {
      return false;
    }
  }
  return true;
}
bool CppTypeCode::init() {
  builtin = type.is_builtin;
  cons_num = type.constr_num;
  params = ret_params = tot_params = 0;
  for (int z : type.args) {
    if ((z & Type::_IsNeg)) {
      ++ret_params;
    } else {
      ++params;
    }
    ++tot_params;
  }
  assign_class_name();
  assign_cons_names();
  assign_class_field_names();
  assign_cons_values();
  assign_record_cons_names();
  simple_get_size = type.has_fixed_size;
  inline_skip = simple_get_size;
  inline_validate_skip = (inline_skip && type.any_bits && !(type.size.min_size() & 0xff));
  inline_get_tag = (type.is_pfx_determ && type.useful_depth <= 6);
  simple_cons_tags = compute_simple_cons_tags();
  common_cons_len = type.cons_common_len();
  incremental_cons_tags = check_incremental_cons_tags();
  return true;
}
void CppTypeCode::generate_cons_enum(std::ostream& os) {
  os << "  enum { ";
  for (int i = 0; i < cons_num; i++) {
    if (i) {
      os << ", ";
    }
    int k = cons_idx_by_enum.at(i);
    os << cons_enum_name.at(k);
    assert(cons_enum_value.at(k) == i);
  }
  os << " };\n";
}
void CppTypeCode::generate_cons_len_array(std::ostream& os, std::string nl, int options) {
  bool f = (options & 2);
  os << nl << (f ? "" : "static ") << ((options & 3) ? "constexpr " : "") << "char ";
  if (f) {
    os << cpp_type_class_name << "::";
  }
  os << "cons_len[" << cons_num << "]";
  if (f) {
    os << ";\n";
    return;
  }
  os << " = { ";
  for (int i = 0; i < cons_num; i++) {
    int k = cons_idx_by_enum.at(i);
    const Constructor& constr = *type.constructors.at(k);
    if (i > 0) {
      os << ", ";
    }
    os << constr.tag_bits;
  }
  os << " };\n";
}
void CppTypeCode::generate_cons_tag_array(std::ostream& os, std::string nl, int options) {
  int m = -1;
  for (int i = 0; i < cons_num; i++) {
    int k = cons_idx_by_enum.at(i);
    const Constructor& constr = *type.constructors.at(k);
    if (constr.tag_bits > m) {
      m = constr.tag_bits;
    }
  }
  bool f = (options & 2);
  os << nl << (f ? "" : "static ") << ((options & 3) ? "constexpr " : "");
  if (m <= 8) {
    os << "unsigned char ";
  } else if (m <= 16) {
    os << "unsigned short ";
  } else if (m <= 32) {
    os << "unsigned ";
  } else {
    os << "unsigned long long ";
  }
  if (f) {
    os << cpp_type_class_name << "::";
  }
  os << "cons_tag[" << cons_num << "]";
  if (f) {
    os << ";\n";
    return;
  }
  os << " = { ";
  for (int i = 0; i < cons_num; i++) {
    int k = cons_idx_by_enum.at(i);
    const Constructor& constr = *type.constructors.at(k);
    if (i > 0) {
      os << ", ";
    }
    os << HexConstWriter{constr.tag_bits ? (constr.tag >> (64 - constr.tag_bits)) : 0};
  }
  os << " };\n";
}
void CppTypeCode::generate_cons_tag_info(std::ostream& os, std::string nl, int options) {
  if (cons_num) {
    if (common_cons_len == -1) {
      generate_cons_len_array(os, nl, options);
    } else if (options & 1) {
      os << "  static constexpr int cons_len_exact = " << common_cons_len << ";\n";
    }
    if (common_cons_len != 0 && !incremental_cons_tags) {
      generate_cons_tag_array(os, nl, options);
    }
  }
}
void CppTypeCode::generate_get_tag_subcase(std::ostream& os, std::string nl, const BinTrie* trie, int depth) const {
  if (!trie || !trie->down_tag) {
    os << nl << "return -1; // ???";
    return;
  }
  if (trie->is_unique()) {
    os << nl << "return " << cons_enum_name.at(trie->unique_value()) << ";";
    return;
  }
  if (!trie->useful_depth) {
    generate_get_tag_param(os, nl, trie->down_tag);
    return;
  }
  assert(trie->left || trie->right);
  if (!trie->right) {
    generate_get_tag_subcase(os, nl, trie->left.get(), depth + 1);
    return;
  }
  if (!trie->left) {
    generate_get_tag_subcase(os, nl, trie->right.get(), depth + 1);
    return;
  }
  if (trie->left->is_unique() && trie->right->is_unique()) {
    os << nl << "return cs.bit_at(" << depth << ") ? ";
    int a = trie->right->unique_value(), b = trie->left->unique_value();
    os << (a >= 0 ? cons_enum_name.at(a) : "-1") << " : ";
    os << (b >= 0 ? cons_enum_name.at(b) : "-1") << ";";
    return;
  }
  os << nl << "if (cs.bit_at(" << depth << ")) {";
  generate_get_tag_subcase(os, nl + "  ", trie->right.get(), depth + 1);
  os << nl << "} else {";
  generate_get_tag_subcase(os, nl + "  ", trie->left.get(), depth + 1);
  os << nl << "}";
}
void CppTypeCode::generate_get_tag_param(std::ostream& os, std::string nl, unsigned long long tag,
                                         unsigned long long tag_params) const {
  if (!tag) {
    os << nl << "return -1; // ???";
    return;
  }
  if (!(tag & (tag - 1))) {
    os << nl << "return " << cons_enum_name.at(td::count_trailing_zeroes64(tag)) << ";";
    return;
  }
  int cnt = td::count_bits64(tag);
  DCHECK(cnt >= 2);
  int mdim = 0, mmdim = 0;
  for (int c = 0; c < 64; c++) {
    if ((tag >> c) & 1) {
      int dim = type.constructors.at(c)->admissible_params.dim;
      if (dim > mdim) {
        mmdim = mdim;
        mdim = dim;
      } else if (dim > mmdim) {
        mmdim = dim;
      }
    }
  }
  assert(mmdim > 0);
  for (int p1 = 0; p1 < mmdim; p1++) {
    char A[4];
    std::memset(A, 0, sizeof(A));
    int c;
    for (c = 0; c < 64; c++) {
      if ((tag >> c) & 1) {
        if (!type.constructors[c]->admissible_params.extract1(A, (char)(c + 1), p1)) {
          break;
        }
      }
    }
    if (c == 64) {
      std::string param_name = get_nat_param_name(p1);
      generate_get_tag_param1(os, nl, A, ¶m_name);
      return;
    }
  }
  for (int p2 = 0; p2 < mmdim; p2++) {
    for (int p1 = 0; p1 < p2; p1++) {
      char A[4][4];
      std::memset(A, 0, sizeof(A));
      int c;
      for (c = 0; c < 64; c++) {
        if ((tag >> c) & 1) {
          if (!type.constructors[c]->admissible_params.extract2(A, (char)(c + 1), p1, p2)) {
            break;
          }
        }
      }
      if (c == 64) {
        std::string param_names[2];
        param_names[0] = get_nat_param_name(p1);
        param_names[1] = get_nat_param_name(p2);
        generate_get_tag_param2(os, nl, A, param_names);
        return;
      }
    }
  }
  for (int p3 = 0; p3 < mmdim; p3++) {
    for (int p2 = 0; p2 < p3; p2++) {
      for (int p1 = 0; p1 < p2; p1++) {
        char A[4][4][4];
        std::memset(A, 0, sizeof(A));
        int c;
        for (c = 0; c < 64; c++) {
          if ((tag >> c) & 1) {
            if (!type.constructors[c]->admissible_params.extract3(A, (char)(c + 1), p1, p2, p3)) {
              break;
            }
          }
        }
        if (c == 64) {
          std::string param_names[3];
          param_names[0] = get_nat_param_name(p1);
          param_names[1] = get_nat_param_name(p2);
          param_names[2] = get_nat_param_name(p3);
          generate_get_tag_param3(os, nl, A, param_names);
          return;
        }
      }
    }
  }
  os << nl << "// ??? cannot distinguish constructors for this type using up to three parameters\n";
  throw src::Fatal{std::string{"cannot generate `"} + cpp_type_class_name + "::get_tag()` method for type `" +
                   type.get_name() + "`"};
}
bool CppTypeCode::match_param_pattern(std::ostream& os, std::string nl, const char A[4], int mask, std::string pattern,
                                      std::string param_name) const {
  int v = 0, w = 0;
  for (int i = 0; i < 4; i++) {
    if (A[i]) {
      if ((mask >> i) & 1) {
        v = (v && v != A[i] ? -1 : A[i]);
      } else {
        w = (w && w != A[i] ? -1 : A[i]);
      }
    }
  }
  if (v <= 0 || w <= 0) {
    return false;
  }
  os << nl << "return ";
  for (char c : pattern) {
    if (c != '#') {
      os << c;
    } else {
      os << param_name;
    }
  }
  os << " ? " << cons_enum_name.at(v - 1) << " : " << cons_enum_name.at(w - 1) << ";";
  return true;
}
void CppTypeCode::generate_get_tag_param1(std::ostream& os, std::string nl, const char A[4],
                                          const std::string param_names[1]) const {
  os << nl << "// distinguish by parameter `" << param_names[0] << "` using";
  for (int i = 0; i < 4; i++) {
    os << ' ' << (int)A[i];
  }
  if (match_param_pattern(os, nl, A, 14, "#", param_names[0]) ||
      match_param_pattern(os, nl, A, 2, "# == 1", param_names[0]) ||
      match_param_pattern(os, nl, A, 3, "# <= 1", param_names[0]) ||
      match_param_pattern(os, nl, A, 10, "(# & 1)", param_names[0]) ||
      match_param_pattern(os, nl, A, 4, "# && !(# & 1)", param_names[0]) ||
      match_param_pattern(os, nl, A, 8, "# > 1 && (# & 1)", param_names[0])) {
    return;
  }
  os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
  os << nl << "static signed char ctab[4] = { ";
  for (int i = 0; i < 4; i++) {
    if (i > 0) {
      os << ", ";
    }
    os << (A[i] ? cons_enum_name.at(A[i] - 1) : "-1");
  }
  os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")];";
}
void CppTypeCode::generate_get_tag_param2(std::ostream& os, std::string nl, const char A[4][4],
                                          const std::string param_names[2]) const {
  os << nl << "// distinguish by parameters `" << param_names[0] << "`, `" << param_names[1] << "` using";
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      os << ' ' << (int)A[i][j];
    }
  }
  os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
  os << nl << "static signed char ctab[4][4] = { ";
  for (int i = 0; i < 16; i++) {
    if (i > 0) {
      os << ", ";
    }
    int v = A[i >> 2][i & 3];
    os << (v ? cons_enum_name.at(v - 1) : "-1");
  }
  os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")][nat_abs(" << param_names[1] << ")];";
}
void CppTypeCode::generate_get_tag_param3(std::ostream& os, std::string nl, const char A[4][4][4],
                                          const std::string param_names[3]) const {
  os << nl << "// distinguish by parameters `" << param_names[0] << "`, `" << param_names[1] << "`, `" << param_names[2]
     << "` using";
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      for (int k = 0; k < 4; k++) {
        os << ' ' << (int)A[i][j][k];
      }
    }
  }
  os << nl << "// static inline size_t nat_abs(int x) { return (x > 1) * 2 + (x & 1); }";
  os << nl << "static signed char ctab[4][4][4] = { ";
  for (int i = 0; i < 64; i++) {
    if (i > 0) {
      os << ", ";
    }
    int v = A[i >> 4][(i >> 2) & 3][i & 3];
    os << (v ? cons_enum_name.at(v - 1) : "-1");
  }
  os << " };" << nl << "return ctab[nat_abs(" << param_names[0] << ")][nat_abs(" << param_names[1] << ")][nat_abs("
     << param_names[2] << ")];";
}
std::string CppTypeCode::get_nat_param_name(int idx) const {
  for (int i = 0; i < tot_params; i++) {
    if (!type_param_is_neg.at(i) && type_param_is_nat.at(i) && !idx--) {
      return type_param_name.at(i);
    }
  }
  return "???";
}
void CppTypeCode::generate_tag_pfx_selector(std::ostream& os, std::string nl, const BinTrie& trie, int d,
                                            int min_size) const {
  assert(d >= 0 && d <= 6);
  int n = (1 << d);
  unsigned long long A[64];
  int c[65];
  unsigned long long mask = trie.build_submap(d, A);
  int l = 1;
  c[0] = -1;
  for (int i = 0; i < n; i++) {
    assert(!(A[i] & (A[i] - 1)));
    if ((mask >> l) & 1) {
      c[l++] = A[i] ? td::count_trailing_zeroes_non_zero64(A[i]) : -1;
    }
  }
  bool simple = (l > n / 2);
  if (simple) {
    l = n + 1;
    for (int i = 0; i < n; i++) {
      c[i + 1] = A[i] ? td::count_trailing_zeroes_non_zero64(A[i]) : -1;
    }
  }
  os << nl << "static signed char ctab[" << l << "] = {";
  for (int i = 0; i < l; i++) {
    if (i > 0) {
      os << ", ";
    }
    if (c[i] < 0) {
      os << c[i];
    } else {
      os << cons_enum_name.at(c[i]);
    }
  }
  os << "};" << nl << "return ctab[1 + ";
  if (simple) {
    os << "(long long)cs.prefetch_ulong(" << d << ")];";
  } else {
    os << "(long long)cs.bselect" << (d >= min_size ? "(" : "_ext(") << d << ", " << HexConstWriter{mask} << ")];";
  }
}
bool CppTypeCode::generate_get_tag_pfx_distinguisher(std::ostream& os, std::string nl,
                                                     const std::vector& constr_list, bool in_block) const {
  if (constr_list.empty()) {
    os << nl << "  return -1;";
    return false;
  }
  if (constr_list.size() == 1) {
    os << nl << "  return " << cons_enum_name.at(constr_list[0]) << ";";
    return false;
  }
  std::unique_ptr trie;
  for (int i : constr_list) {
    trie = BinTrie::insert_paths(std::move(trie), type.constructors.at(i)->begins_with, 1ULL << i);
  }
  if (!trie) {
    os << nl << "  return -1;";
    return false;
  }
  int d = trie->compute_useful_depth();
  bool is_pfx_determ = !trie->find_conflict_path();
  assert(is_pfx_determ);
  if (!in_block) {
    os << " {";
  }
  generate_tag_pfx_selector(os, nl, *trie, d, (int)(type.size.min_size() >> 8));
  return !in_block;
}
void CppTypeCode::generate_get_tag_body(std::ostream& os, std::string nl) {
  int d = type.useful_depth;
  cons_tag_exact.resize(cons_num, false);
  if (type.is_pfx_determ) {
    if (!cons_num) {
      os << nl << "return -1;";
      return;
    }
    if (!d) {
      assert(simple_cons_tags && cons_num == 1);
      cons_tag_exact[0] = !(type.constructors.at(0)->tag_bits);
      os << nl << "return 0;";
      return;
    }
    int min_size = (int)(type.size.min_size() >> 8);
    bool always_has = (d <= min_size);
    if (d <= 6 && simple_cons_tags) {
      unsigned long long sm = compute_selector_mask();
      if (always_has && sm + 1 == (2ULL << ((1 << d) - 1))) {
        for (int i = 0; i < cons_num; i++) {
          cons_tag_exact[i] = (type.constructors.at(i)->tag_bits <= d);
        }
        os << nl << "return (int)cs.prefetch_ulong(" << d << ");";
        return;
      }
      for (int i = 0; i < cons_num; i++) {
        unsigned long long tag = type.constructors.at(i)->tag;
        int l = 63 - td::count_trailing_zeroes_non_zero64(tag);
        if (l <= d) {
          int a = (int)((tag & (tag - 1)) >> (64 - d));
          int b = a + (1 << (d - l));
          cons_tag_exact[i] = ((sm >> a) & 1) && (b == (1 << d) || ((sm >> b) & 1));
        }
      }
      os << nl << "return cs.bselect" << (always_has ? "(" : "_ext(") << d << ", " << HexConstWriter{sm} << ");";
      return;
    }
    if (d <= 6) {
      generate_tag_pfx_selector(os, nl, *(type.cs_trie), d, min_size);
      return;
    }
  }
  if (type.is_const_param_determ || type.is_const_param_pfx_determ) {
    int p = type.const_param_idx;
    assert(p >= 0);
    std::vector param_values = type.get_all_param_values(p);
    assert(param_values.size() > 1 && param_values.at(0) >= 0);
    os << nl << "switch (" << type_param_name.at(p) << ") {";
    for (int pv : param_values) {
      assert(pv >= 0);
      os << nl << "case " << pv << ":";
      std::vector constr_list = type.get_constr_by_param_value(p, pv);
      assert(!constr_list.empty());
      if (constr_list.size() == 1) {
        os << nl << "  return " << cons_enum_name.at(constr_list[0]) << ";";
        continue;
      }
      bool opbr = generate_get_tag_pfx_distinguisher(os, nl + "  ", constr_list, false);
      if (opbr) {
        os << nl << "}";
      }
    }
    os << nl << "default:" << nl << "  return -1;" << nl << "}";
    return;
  }
  if (d) {
    int d1 = std::min(6, d);
    int n = (1 << d1);
    bool always_has = (d1 <= (int)(type.size.min_size() >> 8));
    unsigned long long A[64], B[64];
    unsigned long long mask = type.cs_trie->build_submap(d1, A);
    int l = td::count_bits64(mask);
    bool simple = (l > n / 2 || n <= 8);
    if (!simple) {
      int j = 0;
      for (int i = 0; i < n; i++) {
        if ((mask >> i) & 1) {
          //std::cerr << i << ',' << std::hex << A[i] << std::dec << std::endl;
          B[j] = (2 * i + 1ULL) << (63 - d1);
          A[j++] = A[i];
        }
      }
      assert(j == l);
    } else {
      for (int i = 0; i < n; i++) {
        B[i] = (2 * i + 1ULL) << (63 - d1);
      }
      l = n;
    }
    os << nl << "switch (";
    if (simple) {
      os << "(int)cs.prefetch_ulong(" << d1;
    } else {
      os << "cs.bselect" << (always_has ? "(" : "_ext(") << d1 << ", " << HexConstWriter{mask};
    }
    os << ")) {";
    for (int i = 0; i < l; i++) {
      if (A[i] != 0) {
        if ((long long)A[i] > 0) {
          int j;
          for (j = 0; j < i; j++) {
            if (A[j] == A[i]) {
              break;
            }
          }
          if (j < i) {
            continue;
          }
        }
        os << nl << "case " << i << ":";
        if ((long long)A[i] > 0) {
          int j;
          for (j = i + 1; j < l; j++) {
            if (A[j] == A[i]) {
              os << "  case " << j << ":";
            }
          }
          if (!(A[i] & (A[i] - 1))) {
            os << nl << "  return " << cons_enum_name.at(td::count_trailing_zeroes_non_zero64(A[i])) << ";";
          }
        } else {
          generate_get_tag_subcase(os, nl + "  ", type.cs_trie->lookup_node_const(B[i]), d1);
        }
      }
    }
    os << nl << "default:" << nl << "  return -1;" << nl << "}";
  } else {
    generate_get_tag_subcase(os, nl, type.cs_trie.get(), 0);
  }
}
void CppTypeCode::generate_type_fields(std::ostream& os, int options) {
  int st = -1;
  for (int i = 0; i < tot_params; i++) {
    if (type_param_is_neg[i]) {
      continue;
    }
    int nst = type_param_is_nat[i];
    if (st != nst) {
      if (st >= 0) {
        os << ";\n";
      }
      os << (nst ? "  int " : "  const TLB ");
      st = nst;
    } else {
      os << ", ";
    }
    if (!nst) {
      os << '&';
    }
    os << type_param_name[i];
  }
  if (st >= 0) {
    os << ";\n";
  }
}
static std::string constr_arg_name(std::string type_field_name) {
  if (type_field_name.size() <= 1 || type_field_name.back() != '_') {
    return std::string{"_"} + type_field_name;
  } else {
    return {type_field_name, 0, type_field_name.size() - 1};
  }
}
void CppTypeCode::generate_type_constructor(std::ostream& os, int options) {
  os << "  " << cpp_type_class_name << "(";
  for (int i = 0, j = 0; i < tot_params; i++) {
    if (type_param_is_neg[i]) {
      continue;
    }
    if (j++ > 0) {
      os << ", ";
    }
    os << (type_param_is_nat[i] ? "int " : "const TLB& ");
    os << constr_arg_name(type_param_name[i]);
  }
  os << ")";
  for (int i = 0, j = 0; i < tot_params; i++) {
    if (type_param_is_neg[i]) {
      continue;
    }
    if (j++ > 0) {
      os << ", ";
    } else {
      os << " : ";
    }
    os << type_param_name[i] << "(" << constr_arg_name(type_param_name[i]) << ")";
  }
  os << " {}\n";
}
void Action::show(std::ostream& os) const {
  if (fixed_size >= 0) {
    if (!fixed_size) {
      os << "true";
    } else if (fixed_size < 0x10000) {
      os << "cs.advance(" << fixed_size << ")";
    } else if (!(fixed_size & 0xffff)) {
      os << "cs.advance_refs(" << (fixed_size >> 16) << ")";
    } else {
      os << "cs.advance_ext(0x" << std::hex << fixed_size << std::dec << ")";
    }
  } else {
    os << action;
  }
}
bool Action::may_combine(const Action& next) const {
  return !fixed_size || !next.fixed_size || (fixed_size >= 0 && next.fixed_size >= 0);
}
bool Action::operator+=(const Action& next) {
  if (!next.fixed_size) {
    return true;
  }
  if (!fixed_size) {
    fixed_size = next.fixed_size;
    action = next.action;
    return true;
  }
  if (fixed_size >= 0 && next.fixed_size >= 0) {
    fixed_size += next.fixed_size;
    return true;
  }
  return false;
}
void operator+=(std::vector& av, const Action& next) {
  if (av.empty() || !(av.back() += next)) {
    if (next.is_constraint && !av.empty() && av.back().fixed_size >= 0) {
      Action last = av.back();
      av.pop_back();
      av.push_back(next);
      av.push_back(last);
    } else {
      av.push_back(next);
    }
  }
}
void CppTypeCode::clear_context() {
  actions.clear();
  incomplete = 0;
  tmp_ints = 0;
  needs_tmp_cell = false;
  tmp_vars.clear();
  field_vars.clear();
  field_var_set.clear();
  param_var_set.clear();
  param_constraint_used.clear();
  tmp_cpp_ids.clear();
  tmp_cpp_ids.new_ident("cs");
  tmp_cpp_ids.new_ident("cb");
  tmp_cpp_ids.new_ident("cell_ref");
  tmp_cpp_ids.new_ident("t");
}
std::string CppTypeCode::new_tmp_var() {
  char buffer[16];
  while (true) {
    sprintf(buffer, "t%d", ++tmp_ints);
    if (tmp_cpp_ids.is_good_ident(buffer) && local_cpp_ids.is_good_ident(buffer)) {
      break;
    }
  }
  std::string s{buffer};
  s = tmp_cpp_ids.new_ident(s);
  tmp_vars.push_back(s);
  return s;
}
std::string CppTypeCode::new_tmp_var(std::string hint) {
  if (hint.empty() || hint == "_") {
    return new_tmp_var();
  }
  int count = 0;
  while (true) {
    std::string s = local_cpp_ids.compute_cpp_ident(hint, count++);
    if (tmp_cpp_ids.is_good_ident(s) && local_cpp_ids.is_good_ident(s)) {
      s = tmp_cpp_ids.new_ident(s);
      tmp_vars.push_back(s);
      return s;
    }
  }
}
std::string compute_type_class_name(const Type* typ, int& fake_arg) {
  fake_arg = -1;
  int idx = typ->type_idx;
  if (idx >= builtin_types_num) {
    return cpp_type[idx]->cpp_type_class_name;
  } else if (typ->produces_nat) {
    if (typ == Nat_type) {
      return "Nat";
    } else if (typ == NatWidth_type) {
      return "NatWidth";
    } else if (typ == NatLeq_type) {
      return "NatLeq";
    } else if (typ == NatLess_type) {
      return "NatLess";
    }
    // ...
  } else if (typ == Any_type) {
    return "Anything";
  } else if (typ->has_fixed_size) {
    fake_arg = (typ->size.min_size() >> 8);
    int c = typ->get_name()[0];
    return (c == 'b') ? "Bits" : ((c == 'u') ? "UInt" : "Int");
  } else if (typ == Int_type) {
    return "Int";
  } else if (typ == UInt_type) {
    return "UInt";
  } else if (typ == Bits_type) {
    return "Bits";
  }
  return "";
}
std::string compute_type_expr_class_name(const TypeExpr* expr, int& fake_arg) {
  switch (expr->tp) {
    case TypeExpr::te_Apply:
      return compute_type_class_name(expr->type_applied, fake_arg);
    case TypeExpr::te_Ref:
      return "RefT";
    case TypeExpr::te_Tuple:
      return "TupleT";
    case TypeExpr::te_CondType:
      return "CondT";
  }
  return "";
}
void CppTypeCode::output_cpp_expr(std::ostream& os, const TypeExpr* expr, int prio, bool allow_type_neg) const {
  if (expr->negated) {
    if (!allow_type_neg || expr->tp != TypeExpr::te_Apply) {
      throw src::Fatal{static_cast(std::ostringstream{} << "cannot convert negated expression `"
                                                                              << expr << "` into C++ code")
                           .str()};
    }
  }
  int pos_args = 0;
  for (const TypeExpr* arg : expr->args) {
    pos_args += !arg->negated;
  }
  switch (expr->tp) {
    case TypeExpr::te_Param: {
      int i = expr->value;
      assert(field_var_set.at(i));
      std::string fv = field_vars.at(i);
      assert(!fv.empty());
      os << fv;
      return;
    }
    case TypeExpr::te_Apply:
      if (!pos_args && expr->type_applied->type_idx >= builtin_types_num) {
        int type_idx = expr->type_applied->type_idx;
        const CppTypeCode& cc = *cpp_type.at(type_idx);
        assert(!cc.cpp_type_var_name.empty());
        os << cc.cpp_type_var_name;
        return;
      }
      // fall through
    case TypeExpr::te_Ref:
    case TypeExpr::te_CondType:
    case TypeExpr::te_Tuple:
      if (expr->is_constexpr > 0) {
        os << const_type_expr_cpp_idents.at(expr->is_constexpr);
        return;
      } else {
        int fake_arg = -1;
        os << compute_type_expr_class_name(expr, fake_arg);
        os << "{";
        int c = 0;
        if (fake_arg >= 0) {
          os << fake_arg;
          c = 1;
        }
        for (const TypeExpr* arg : expr->args) {
          if (!arg->negated) {
            os << (c++ ? ", " : "");
            output_cpp_expr(os, arg);
          }
        }
        os << '}';
        return;
      }
    case TypeExpr::te_Add:
      if (prio > 10) {
        os << "(";
      }
      output_cpp_expr(os, expr->args[0], 10);
      os << " + ";
      output_cpp_expr(os, expr->args[1], 10);
      if (prio > 10) {
        os << ")";
      }
      return;
    case TypeExpr::te_MulConst:
      if (prio > 20) {
        os << "(";
      }
      os << expr->value;
      os << " * ";
      output_cpp_expr(os, expr->args[0], 20);
      if (prio > 20) {
        os << ")";
      }
      return;
    case TypeExpr::te_GetBit:
      if (prio > 0) {
        os << "(";
      }
      output_cpp_expr(os, expr->args[0], 5);
      os << " & ";
      if (expr->args[1]->tp == TypeExpr::te_IntConst && (unsigned)expr->args[1]->value <= 31) {
        int v = expr->args[1]->value;
        if (v > 1024) {
          os << "0x" << std::hex << (1 << v) << std::dec;
        } else {
          os << (1 << v);
        }
      } else {
        os << "(1 << ";
        output_cpp_expr(os, expr->args[1], 5);
        os << ")";
      }
      if (prio > 0) {
        os << ")";
      }
      return;
    case TypeExpr::te_IntConst:
      os << expr->value;
      return;
  }
  os << "";
}
bool CppTypeCode::can_compute_sizeof(const TypeExpr* expr) const {
  if (expr->negated || expr->is_nat) {
    return false;
  }
  MinMaxSize sz = expr->compute_size();
  if (sz.is_fixed()) {
    return !(sz.min_size() & 0xff);
  }
  if (expr->tp == TypeExpr::te_Apply && (expr->type_applied == Int_type || expr->type_applied == UInt_type ||
                                         expr->type_applied == NatWidth_type || expr->type_applied == Bits_type)) {
    return true;
  }
  if (expr->tp != TypeExpr::te_CondType && expr->tp != TypeExpr::te_Tuple) {
    return false;
  }
  return can_compute_sizeof(expr->args[1]);
}
void CppTypeCode::output_cpp_sizeof_expr(std::ostream& os, const TypeExpr* expr, int prio) const {
  if (expr->negated) {
    throw src::Fatal{static_cast(std::ostringstream{}
                                                       << "cannot compute size of negated type expression `" << expr
                                                       << "` in C++ code")
                         .str()};
  }
  if (expr->is_nat) {
    throw src::Fatal{static_cast(std::ostringstream{}
                                                       << "cannot compute size of non-type expression `" << expr
                                                       << "` in C++ code")
                         .str()};
  }
  MinMaxSize sz = expr->compute_size();
  if (sz.is_fixed()) {
    os << SizeWriter{(int)sz.convert_min_size()};
    return;
  }
  switch (expr->tp) {
    case TypeExpr::te_CondType:
      if (prio > 5) {
        os << '(';
      }
      output_cpp_expr(os, expr->args[0], 5);
      os << " ? ";
      output_cpp_sizeof_expr(os, expr->args[1], 6);
      os << " : 0";
      if (prio > 5) {
        os << ')';
      }
      return;
    case TypeExpr::te_Tuple:
      if (expr->args[0]->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
        output_cpp_sizeof_expr(os, expr->args[1], prio);
        return;
      }
      sz = expr->args[1]->compute_size();
      if (sz.is_fixed() && sz.convert_min_size() == 1) {
        output_cpp_expr(os, expr->args[0], prio);
        return;
      }
      if (prio > 20) {
        os << '(';
      }
      output_cpp_expr(os, expr->args[0], 20);
      os << " * ";
      output_cpp_sizeof_expr(os, expr->args[1], 20);
      if (prio > 20) {
        os << ')';
      }
      return;
    case TypeExpr::te_Apply:
      if (expr->type_applied == Int_type || expr->type_applied == UInt_type || expr->type_applied == NatWidth_type ||
          expr->type_applied == Bits_type) {
        output_cpp_expr(os, expr->args[0], prio);
        return;
      }
      // no break
  }
  os << "";
}
bool CppTypeCode::can_compute(const TypeExpr* expr) const {
  if (expr->negated) {
    return false;
  }
  if (expr->tp == TypeExpr::te_Param) {
    return field_var_set.at(expr->value);
  }
  for (const TypeExpr* arg : expr->args) {
    if (!can_compute(arg)) {
      return false;
    }
  }
  return true;
}
bool CppTypeCode::can_use_to_compute(const TypeExpr* expr, int i) const {
  if (!expr->negated || !expr->is_nat) {
    return false;
  }
  if (expr->tp == TypeExpr::te_Param) {
    return expr->value == i;
  }
  for (const TypeExpr* arg : expr->args) {
    if (!(arg->negated ? can_use_to_compute(arg, i) : can_compute(arg))) {
      return false;
    }
  }
  return true;
}
void CppTypeCode::add_compute_actions(const TypeExpr* expr, int i, std::string bind_to) {
  assert(expr->negated && expr->is_nat);
  switch (expr->tp) {
    case TypeExpr::te_MulConst: {
      assert(expr->args.size() == 1 && expr->value > 0);
      const TypeExpr* x = expr->args[0];
      assert(x->negated);
      std::string tmp;
      if (x->tp != TypeExpr::te_Param || (x->value != i && i >= 0)) {
        tmp = new_tmp_var();
      } else {
        i = x->value;
        tmp = field_vars.at(i);
        assert(!tmp.empty());
        assert(!field_var_set[i]);
        field_var_set[i] = true;
        x = nullptr;
      }
      std::ostringstream ss;
      ss << "mul_r1(" << tmp << ", " << expr->value << ", " << bind_to << ")";
      actions += Action{std::move(ss), true};
      if (x) {
        add_compute_actions(x, i, tmp);
      }
      return;
    }
    case TypeExpr::te_Add: {
      assert(expr->args.size() == 2);
      const TypeExpr *x = expr->args[0], *y = expr->args[1];
      assert(x->negated ^ y->negated);
      if (!x->negated) {
        std::swap(x, y);
      }
      std::string tmp;
      if (x->tp != TypeExpr::te_Param || (x->value != i && i >= 0)) {
        tmp = new_tmp_var();
      } else {
        i = x->value;
        tmp = field_vars.at(i);
        assert(!tmp.empty());
        assert(!field_var_set[i]);
        field_var_set[i] = true;
        x = nullptr;
      }
      std::ostringstream ss;
      ss << "add_r1(" << tmp << ", ";
      output_cpp_expr(ss, y);
      ss << ", " << bind_to << ")";
      actions += Action{std::move(ss), true};
      if (x) {
        add_compute_actions(x, i, tmp);
      }
      return;
    }
    case TypeExpr::te_Param:
      assert(expr->value == i || i < 0);
      i = expr->value;
      assert(!field_vars.at(i).empty());
      if (!field_var_set.at(i)) {
        actions += Action{std::string{"("} + field_vars.at(i) + " = " + bind_to + ") >= 0"};
        field_var_set[i] = true;
      } else {
        actions += Action{field_vars.at(i) + " == " + bind_to};
      }
      return;
  }
  throw src::Fatal{static_cast(std::ostringstream{} << "cannot use expression `" << expr << "` = "
                                                                          << bind_to << " to set field variable "
                                                                          << (i >= 0 ? field_vars.at(i) : ""))
                       .str()};
}
bool CppTypeCode::is_self(const TypeExpr* expr, const Constructor& constr) const {
  if (expr->tp != TypeExpr::te_Apply || expr->type_applied != &type || (int)expr->args.size() != tot_params) {
    return false;
  }
  assert(constr.params.size() == expr->args.size());
  for (int i = 0; i < tot_params; i++) {
    assert(type_param_is_neg[i] == expr->args[i]->negated);
    assert(type_param_is_neg[i] == constr.param_negated[i]);
    if (!type_param_is_neg[i] && !expr->args[i]->equal(*constr.params[i])) {
      return false;
    }
  }
  return true;
}
void CppTypeCode::init_cons_context(const Constructor& constr) {
  clear_context();
  field_vars.resize(constr.fields.size());
  field_var_set.resize(constr.fields.size(), false);
  param_var_set.resize(params + ret_params, false);
  param_constraint_used.resize(params + ret_params, false);
}
void CppTypeCode::identify_cons_params(const Constructor& constr, int options) {
  int j = 0;
  for (const TypeExpr* pexpr : constr.params) {
    if (pexpr->tp == TypeExpr::te_Param) {
      if (!type_param_is_neg.at(j)) {
        int i = pexpr->value;
        if (field_var_set.at(i)) {
          // field i and parameter j must be equal
          actions += Action{type_param_name.at(j) + " == " + field_vars.at(i)};
          param_constraint_used[j] = true;
        } else if (field_vars.at(i).empty()) {
          // identify field i with parameter j
          field_vars[i] = type_param_name.at(j);
          field_var_set[i] = true;
          param_constraint_used[j] = true;
        }
      } else if (!(options & 2)) {
        tmp_vars.push_back(type_param_name.at(j));
      }
    }
    j++;
  }
}
void CppTypeCode::identify_cons_neg_params(const Constructor& constr, int options) {
  int j = 0;
  for (const TypeExpr* pexpr : constr.params) {
    if (pexpr->tp == TypeExpr::te_Param && type_param_is_neg.at(j)) {
      int i = pexpr->value;
      if (!field_var_set.at(i) && field_vars.at(i).empty()) {
        // identify field i with parameter j
        field_vars[i] = type_param_name.at(j);
        param_constraint_used[j] = true;
      }
    }
    j++;
  }
}
void CppTypeCode::add_cons_tag_check(const Constructor& constr, int cidx, int options) {
  if (constr.tag_bits) {
    if ((options & 1) && ((options & 8) || cons_num == 1 || !cons_tag_exact.at(cidx))) {
      std::ostringstream ss;
      int l = constr.tag_bits;
      unsigned long long tag = (constr.tag >> (64 - l));
      if (l < 64) {
        ss << "cs.fetch_ulong(" << l << ") == " << HexConstWriter{tag};
      } else {
        ss << "cs.begins_with_skip(" << l << ", " << HexConstWriter{tag} << ")";
      }
      actions.emplace_back(std::move(ss));
    } else {
      actions.emplace_back(constr.tag_bits);
    }
  }
}
void CppTypeCode::add_cons_tag_store(const Constructor& constr, int cidx) {
  if (constr.tag_bits) {
    std::ostringstream ss;
    int l = constr.tag_bits;
    unsigned long long tag = (constr.tag >> (64 - l));
    ss << "cb.store_long_bool(" << HexConstWriter{tag} << ", " << l << ")";
    actions.emplace_back(std::move(ss));
  }
}
void CppTypeCode::add_remaining_param_constraints_check(const Constructor& constr, int options) {
  int j = 0;
  for (const TypeExpr* pexpr : constr.params) {
    if (!param_constraint_used.at(j)) {
      std::ostringstream ss;
      if (!type_param_is_neg.at(j)) {
        ss << type_param_name.at(j) << " == ";
        output_cpp_expr(ss, pexpr);
        actions += Action{std::move(ss)};
      } else if (options & 2) {
        ss << "(" << type_param_name.at(j) << " = ";
        output_cpp_expr(ss, pexpr);
        ss << ") >= 0";
        actions += Action{std::move(ss), true};
      }
    }
    ++j;
  }
}
void CppTypeCode::output_actions(std::ostream& os, std::string nl, int options) {
  bool opbr = false;
  if (tmp_vars.size() || needs_tmp_cell) {
    if (!(options & 4)) {
      opbr = true;
      os << " {";
    }
    if (tmp_vars.size()) {
      os << nl << "int";
      int c = 0;
      for (auto t : tmp_vars) {
        if (c++) {
          os << ",";
        }
        os << " " << t;
      }
      os << ";";
    }
    if (needs_tmp_cell) {
      os << nl << "Ref tmp_cell;";
    }
  }
  if (!actions.size()) {
    os << nl << "return true;";
  } else {
    for (std::size_t i = 0; i < actions.size(); i++) {
      os << nl << (i ? "    && " : "return ");
      actions[i].show(os);
    }
    os << ";";
  }
  if (incomplete) {
    os << nl << "// ???";
  }
  if (opbr) {
    os << nl << "}";
  }
}
void CppTypeCode::compute_implicit_field(const Constructor& constr, const Field& field, int options) {
  int i = field.field_idx;
  if (field_vars.at(i).empty()) {
    assert(!field_var_set.at(i));
    assert(field.type->is_nat_subtype);
    std::string id = new_tmp_var(field.get_name());
    field_vars[i] = id;
  }
  int j = -1;
  for (const TypeExpr* pexpr : constr.params) {
    ++j;
    if (!param_constraint_used.at(j) && !type_param_is_neg.at(j)) {
      // std::cerr << "can_use_to_compute(" << pexpr << ", " << i << ") = " << can_use_to_compute(pexpr, i) << std::endl;
      if (!field_var_set.at(i) && pexpr->tp == TypeExpr::te_Param && pexpr->value == i) {
        std::ostringstream ss;
        if (field.type->is_nat_subtype) {
          ss << "(" << field_vars[i] << " = " << type_param_name.at(j) << ") >= 0";
        } else {
          ss << "(" << field_vars[i] << " = &" << type_param_name.at(j) << ")";
        }
        actions += Action{std::move(ss)};
        field_vars[i] = type_param_name[j];
        field_var_set[i] = true;
        param_constraint_used[j] = true;
      } else if (can_compute(pexpr)) {
        std::ostringstream ss;
        ss << type_param_name.at(j) << " == ";
        output_cpp_expr(ss, pexpr);
        actions += Action{std::move(ss), true};
        param_constraint_used[j] = true;
      } else if (!field_var_set.at(i) && can_use_to_compute(pexpr, i)) {
        add_compute_actions(pexpr, i, type_param_name.at(j));
        param_constraint_used[j] = true;
      }
    }
  }
}
bool CppTypeCode::add_constraint_check(const Constructor& constr, const Field& field, int options) {
  const TypeExpr* expr = field.type;
  if (expr->tp == TypeExpr::te_Apply &&
      (expr->type_applied == Eq_type || expr->type_applied == Less_type || expr->type_applied == Leq_type)) {
    assert(expr->args.size() == 2);
    const TypeExpr *x = expr->args[0], *y = expr->args[1];
    if (x->negated || y->negated) {
      assert(expr->type_applied == Eq_type);
      assert(x->negated ^ y->negated);
      if (!x->negated) {
        std::swap(x, y);
      }
      std::ostringstream ss;
      output_cpp_expr(ss, y);
      add_compute_actions(x, -1, ss.str());
    } else {
      std::ostringstream ss;
      output_cpp_expr(ss, x);
      ss << (expr->type_applied == Eq_type ? " == " : (expr->type_applied == Less_type ? " < " : " <= "));
      output_cpp_expr(ss, y);
      actions += Action{std::move(ss), true};
    }
    return true;
  } else {
    // ...
    ++incomplete;
    actions += Action{"check_constraint_incomplete"};
    return false;
  }
}
void CppTypeCode::output_negative_type_arguments(std::ostream& os, const TypeExpr* expr) {
  assert(expr->tp == TypeExpr::te_Apply);
  for (const TypeExpr* arg : expr->args) {
    if (arg->negated) {
      int j = arg->value;
      if (arg->tp == TypeExpr::te_Param && !field_var_set.at(j)) {
        assert(!field_vars.at(j).empty());
        os << ", " << field_vars.at(j);
        field_var_set[j] = true;
      } else {
        std::string tmp = new_tmp_var();
        os << ", " << tmp;
        postponed_equate.emplace_back(tmp, arg);
      }
    }
  }
}
void CppTypeCode::add_postponed_equate_actions() {
  for (const auto& p : postponed_equate) {
    add_compute_actions(p.second, -1, p.first);
  }
  postponed_equate.clear();
}
std::string CppTypeCode::add_fetch_nat_field(const Constructor& constr, const Field& field, int options) {
  const TypeExpr* expr = field.type;
  int i = field.field_idx;
  std::string id = field_vars.at(i);
  if (id.empty()) {
    field_vars[i] = id = new_tmp_var(field.get_name());
  }
  const Type* ta = expr->type_applied;
  assert(expr->tp == TypeExpr::te_Apply &&
         (ta == Nat_type || ta == NatWidth_type || ta == NatLeq_type || ta == NatLess_type));
  std::ostringstream ss;
  ss << "cs.";
  if (ta == Nat_type) {
    ss << "fetch_uint_to(32, " << id << ")";
  } else if (ta == NatWidth_type && expr->args.at(0)->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
    ss << "fetch_bool_to(" << id << ")";
  } else {
    if (ta == NatWidth_type) {
      ss << "fetch_uint_to(";
    } else if (ta == NatLeq_type) {
      ss << "fetch_uint_leq(";
    } else if (ta == NatLess_type) {
      ss << "fetch_uint_less(";
    }
    output_cpp_expr(ss, expr->args[0]);
    ss << ", " << id << ")";
  }
  actions += Action{std::move(ss)};
  field_var_set[i] = true;
  return id;
}
void CppTypeCode::add_store_nat_field(const Constructor& constr, const Field& field, int options) {
  const TypeExpr* expr = field.type;
  int i = field.field_idx;
  std::string id = field_vars.at(i);
  assert(!id.empty());
  const Type* ta = expr->type_applied;
  assert(expr->tp == TypeExpr::te_Apply &&
         (ta == Nat_type || ta == NatWidth_type || ta == NatLeq_type || ta == NatLess_type));
  std::ostringstream ss;
  ss << "cb.";
  if (ta == Nat_type) {
    ss << "store_ulong_rchk_bool(" << id << ", 32)";
  } else if (ta == NatWidth_type) {
    if (expr->args.at(0)->tp == TypeExpr::te_IntConst && expr->args[0]->value == 1) {
      ss << "store_ulong_rchk_bool(" << id << ", 1)";
    } else {
      ss << "store_ulong_rchk_bool(" << id << ", ";
      output_cpp_expr(ss, expr->args[0]);
      ss << ")";
    }
  } else if (ta == NatLeq_type) {
    ss << "store_uint_leq(";
    output_cpp_expr(ss, expr->args[0]);
    ss << ", " << id << ")";
  } else if (ta == NatLess_type) {
    ss << "store_uint_less(";
    output_cpp_expr(ss, expr->args[0]);
    ss << ", " << id << ")";
  } else {
    ss << "(" << id << ")";
  }
  actions += Action{std::move(ss)};
  field_var_set[i] = true;
}
void CppTypeCode::generate_skip_field(const Constructor& constr, const Field& field, int options) {
  const TypeExpr* expr = field.type;
  MinMaxSize sz = expr->compute_size();
  bool any_bits = expr->compute_any_bits();
  bool validating = (options & 1);
  // std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
  if (field.used || (validating && expr->is_nat_subtype && !any_bits)) {
    // an explicit field of type # or ## which is used later or its value is not arbitrary
    // (must load the value into an integer variable and check)
    assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
    add_fetch_nat_field(constr, field, options);
    return;
  }
  if (sz.is_fixed() && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
    // field has fixed size, and either its bits can have arbitrary values (and it has no references)
    // ... or we are not validating
    // simply skip the necessary amount of bits
    // NB: if the field is a reference, and we are not validating, we arrive here
    actions += Action{(int)sz.convert_min_size()};
    return;
  }
  if (expr->negated) {
    // the field type has some "negative" parameters, which will be computed while checking this field
    // must invoke the correct validate_skip or skip method for the type in question
    std::ostringstream ss;
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100, true);
      ss << '.';
    }
    ss << (validating ? "validate_skip(ops, cs, weak" : "skip(cs");
    output_negative_type_arguments(ss, expr);
    ss << ")";
    actions += Action{std::move(ss)};
    add_postponed_equate_actions();
    return;
  }
  // at this point, if the field type is a reference, we must be validating
  if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
    // field type is a reference to a cell with arbitrary contents
    actions += Action{0x10000};
    return;
  }
  // remaining case: general positive type expression
  std::ostringstream ss;
  std::string tail;
  while (expr->tp == TypeExpr::te_CondType) {
    // optimization for (chains of) conditional types ( x?type )
    assert(expr->args.size() == 2);
    ss << "(!";
    output_cpp_expr(ss, expr->args[0], 30);
    ss << " || ";
    expr = expr->args[1];
    tail = std::string{")"} + tail;
  }
  if ((!validating || any_bits) && can_compute_sizeof(expr)) {
    // field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
    ss << "cs.advance(";
    output_cpp_sizeof_expr(ss, expr, 0);
    ss << ")" << tail;
    actions += Action{std::move(ss)};
    return;
  }
  if (expr->tp != TypeExpr::te_Ref) {
    // field type is not a reference, generate a type expression and invoke skip/validate_skip method
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100);
      ss << '.';
    }
    ss << (validating ? "validate_skip(ops, cs, weak)" : "skip(cs)") << tail;
    actions += Action{std::move(ss)};
    return;
  }
  // the (remaining) field type is a reference
  if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
                      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
    // the subcase when the field type is either a reference to a cell with arbitrary contents
    // or it is a reference, and we are not validating, so we simply skip the reference
    ss << "cs.advance_refs(1)" << tail;
    actions += Action{std::move(ss)};
    return;
  }
  // general reference type, invoke validate_skip_ref()
  // (notice that we are necessarily validating at this point)
  expr = expr->args[0];
  if (!is_self(expr, constr)) {
    output_cpp_expr(ss, expr, 100);
    ss << '.';
  }
  ss << "validate_skip_ref(ops, cs, weak)" << tail;
  actions += Action{ss.str()};
}
void CppTypeCode::generate_skip_cons_method(std::ostream& os, std::string nl, int cidx, int options) {
  const Constructor& constr = *(type.constructors.at(cidx));
  init_cons_context(constr);
  identify_cons_params(constr, options);
  identify_cons_neg_params(constr, options);
  add_cons_tag_check(constr, cidx, options);
  for (const Field& field : constr.fields) {
    if (!field.implicit) {
      generate_skip_field(constr, field, options);
    } else if (!field.constraint) {
      compute_implicit_field(constr, field, options);
    } else {
      add_constraint_check(constr, field, options);
    }
  }
  add_remaining_param_constraints_check(constr, options);
  output_actions(os, nl, options);
  clear_context();
}
void CppTypeCode::generate_skip_method(std::ostream& os, int options) {
  bool validate = options & 1;
  bool ret_ext = options & 2;
  os << "\nbool " << cpp_type_class_name
     << "::" << (validate ? "validate_skip(int* ops, vm::CellSlice& cs, bool weak" : "skip(vm::CellSlice& cs");
  if (ret_ext) {
    os << skip_extra_args;
  }
  os << ") const {";
  if (cons_num > 1) {
    os << "\n  switch (get_tag(cs)) {\n";
    for (int i = 0; i < cons_num; i++) {
      os << "  case " << cons_enum_name[i] << ":";
      generate_skip_cons_method(os, "\n    ", i, options & ~4);
      os << "\n";
    }
    os << "  }\n  return false;\n";
  } else if (cons_num == 1) {
    generate_skip_cons_method(os, "\n  ", 0, options | 4);
    os << "\n";
  } else {
    os << "\n  return false;\n";
  }
  os << "}\n";
}
void CppTypeCode::generate_cons_tag_check(std::ostream& os, std::string nl, int cidx, bool force) {
  const Constructor& constr = *(type.constructors.at(cidx));
  if (!constr.tag_bits) {
    os << nl << "return " << cons_enum_name[cidx] << ";";
  } else if (force || cons_num == 1 || !cons_tag_exact.at(cidx)) {
    os << nl << "return ";
    int l = constr.tag_bits;
    unsigned long long tag = (constr.tag >> (64 - l));
    if (l < 64 || tag != ~0ULL) {
      os << "cs.prefetch_ulong(" << l << ") == " << HexConstWriter{tag};
    } else {
      os << "cs.begins_with(" << l << ", " << HexConstWriter{tag} << ")";
    }
    os << " ? " << cons_enum_name[cidx] << " : -1;";
  } else {
    os << nl << "return cs.have(" << constr.tag_bits << ") ? " << cons_enum_name[cidx] << " : -1;";
  }
}
void CppTypeCode::generate_check_tag_method(std::ostream& os) {
  os << "\nint " << cpp_type_class_name << "::check_tag(const vm::CellSlice& cs) const {";
  if (cons_num > 1) {
    os << "\n  switch (get_tag(cs)) {\n";
    for (int i = 0; i < cons_num; i++) {
      os << "  case " << cons_enum_name[i] << ":";
      generate_cons_tag_check(os, "\n    ", i);
      os << "\n";
    }
    os << "  }\n  return -1;\n";
  } else if (cons_num == 1) {
    generate_cons_tag_check(os, "\n  ", 0);
    os << "\n";
  } else {
    os << "\n  return -1;\n";
  }
  os << "}\n";
}
bool CppTypeCode::output_print_simple_field(std::ostream& os, const Field& field, std::string field_name,
                                            const TypeExpr* expr) {
  cpp_val_type cvt = detect_cpp_type(expr);
  MinMaxSize sz = expr->compute_size();
  int i = expr->is_integer();
  int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
  switch (cvt) {
    case ct_bitstring:
    case ct_bits:
      assert(!(sz.max_size() & 0xff));
      os << "pp.fetch_bits_field(cs, ";
      output_cpp_sizeof_expr(os, expr, 0);
      if (!field_name.empty()) {
        os << ", \"" << field_name << '"';
      }
      os << ")";
      return true;
    case ct_bool:
    case ct_int32:
    case ct_uint32:
    case ct_int64:
    case ct_uint64:
      assert(i && l <= 64);
      os << "pp.fetch_" << (i > 0 ? "u" : "") << "int_field(cs, ";
      output_cpp_sizeof_expr(os, expr, 0);
      if (!field_name.empty()) {
        os << ", \"" << field_name << '"';
      }
      os << ")";
      return true;
    case ct_integer:
      assert(i);
      os << "pp.fetch_" << (i > 0 ? "u" : "") << "int256_field(cs, ";
      output_cpp_sizeof_expr(os, expr, 0);
      if (!field_name.empty()) {
        os << ", \"" << field_name << '"';
      }
      os << ")";
      return true;
    default:
      break;
  }
  return false;
}
void CppTypeCode::generate_print_field(const Constructor& constr, const Field& field, int options) {
  const TypeExpr* expr = field.type;
  MinMaxSize sz = expr->compute_size();
  cpp_val_type cvt = detect_cpp_type(expr);
  bool any_bits = expr->compute_any_bits();
  bool is_simple = (cvt >= ct_bits && cvt <= ct_uint64 && cvt != ct_enum);
  // std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
  std::string field_name = field.name ? field.get_name() : "";
  if (field.used || expr->is_nat_subtype) {
    // an explicit field of type # or ##
    assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
    std::ostringstream ss;
    ss << "pp.field_int(" << add_fetch_nat_field(constr, field, options);
    if (field.name) {
      ss << ", \"" << field_name << '"';
    }
    ss << ')';
    actions += Action{std::move(ss)};
    return;
  }
  if (sz.is_fixed() && !(sz.min_size() & 0xff) && any_bits) {
    // field has fixed size, and either its bits can have arbitrary values (and it has no references)
    std::ostringstream ss;
    if (output_print_simple_field(ss, field, field_name, expr)) {
      actions += Action{std::move(ss)};
      return;
    }
  }
  bool cond_chain = (expr->tp == TypeExpr::te_CondType);
  if (!cond_chain && !is_simple) {
    if (field.name) {
      actions += Action{std::string{"pp.field(\""} + field_name + "\")"};
    } else {
      actions += Action{"pp.field()"};
    }
  }
  if (expr->negated) {
    // the field type has some "negative" parameters, which will be computed while checking this field
    // must invoke the correct validate_skip or skip method for the type in question
    assert(!cond_chain);
    std::ostringstream ss;
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100, true);
      ss << '.';
    }
    ss << "print_skip(pp, cs";
    output_negative_type_arguments(ss, expr);
    ss << ")";
    actions += Action{std::move(ss)};
    add_postponed_equate_actions();
    return;
  }
  // remaining case: general positive type expression
  std::ostringstream ss;
  std::string tail;
  while (expr->tp == TypeExpr::te_CondType) {
    // optimization for (chains of) conditional types ( x?type )
    assert(expr->args.size() == 2);
    ss << "(!";
    output_cpp_expr(ss, expr->args[0], 30);
    ss << " || ";
    expr = expr->args[1];
    tail += ')';
  }
  if (output_print_simple_field(ss, field, field_name, expr)) {
    ss << tail;
    actions += Action{std::move(ss)};
    return;
  }
  if (cond_chain) {
    if (field.name) {
      ss << "(pp.field(\"" + field_name + "\") && ";
    } else {
      ss << "(pp.field() && ";
    }
    tail += ')';
  }
  if (expr->tp != TypeExpr::te_Ref) {
    // field type is not a reference, generate a type expression and invoke skip/validate_skip method
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100);
      ss << '.';
    }
    ss << "print_skip(pp, cs)" << tail;
    actions += Action{std::move(ss)};
    return;
  }
  // general reference type, invoke print_ref()
  expr = expr->args[0];
  if (!is_self(expr, constr)) {
    output_cpp_expr(ss, expr, 100);
    ss << '.';
  }
  ss << "print_ref(pp, cs.fetch_ref())" << tail;
  actions += Action{ss.str()};
}
void CppTypeCode::generate_print_cons_method(std::ostream& os, std::string nl, int cidx, int options) {
  const Constructor& constr = *(type.constructors.at(cidx));
  init_cons_context(constr);
  identify_cons_params(constr, options);
  identify_cons_neg_params(constr, options);
  add_cons_tag_check(constr, cidx, options);
  bool do_open = !constr.is_enum;
  if (do_open) {
    if (constr.constr_name) {
      actions += Action{std::string{"pp.open(\""} + constr.get_name() + "\")"};
    } else {
      actions += Action{"pp.open()"};
    }
  } else {
    actions += Action{std::string{"pp.cons(\""} + constr.get_name() + "\")"};
  }
  for (const Field& field : constr.fields) {
    if (!field.implicit) {
      generate_print_field(constr, field, options);
    } else if (!field.constraint) {
      compute_implicit_field(constr, field, options);
    } else {
      add_constraint_check(constr, field, options);
    }
  }
  add_remaining_param_constraints_check(constr, options);
  if (do_open) {
    actions += Action{"pp.close()"};
  }
  output_actions(os, nl, options);
  clear_context();
}
void CppTypeCode::generate_print_method(std::ostream& os, int options) {
  bool ret_ext = options & 2;
  os << "\nbool " << cpp_type_class_name << "::print_skip(PrettyPrinter& pp, vm::CellSlice& cs";
  if (ret_ext) {
    os << skip_extra_args;
  }
  os << ") const {";
  if (cons_num > 1) {
    os << "\n  switch (get_tag(cs)) {\n";
    for (int i = 0; i < cons_num; i++) {
      os << "  case " << cons_enum_name[i] << ":";
      generate_print_cons_method(os, "\n    ", i, options & ~4);
      os << "\n";
    }
    os << "  }\n  return pp.fail(\"unknown constructor for " << type.get_name() << "\");\n";
  } else if (cons_num == 1) {
    generate_print_cons_method(os, "\n  ", 0, options | 4);
    os << "\n";
  } else {
    os << "\n  return pp.fail(\"no constructors for " << type.get_name() << "\");\n";
  }
  os << "}\n";
}
void CppTypeCode::bind_record_fields(const CppTypeCode::ConsRecord& rec, int options) {
  bool direct = options & 8;
  bool read_only = options & 32;
  for (const ConsField& fi : rec.cpp_fields) {
    int i = fi.orig_idx;
    assert(field_vars.at(i).empty() && !field_var_set.at(i));
    if (!read_only || !rec.constr.fields.at(i).implicit) {
      field_vars[i] = direct ? fi.name : std::string{"data."} + fi.name;
      field_var_set[i] = read_only;
    }
  }
}
void CppTypeCode::output_fetch_field(std::ostream& os, std::string field_var, const TypeExpr* expr, cpp_val_type cvt) {
  int i = expr->is_integer();
  MinMaxSize sz = expr->compute_size();
  int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
  switch (cvt) {
    case ct_slice:
      os << "cs.fetch_subslice_" << (sz.max_size() & 0xff ? "ext_" : "") << "to(";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ", " << field_var << ")";
      return;
    case ct_bitstring:
      assert(!(sz.max_size() & 0xff));
      os << "cs.fetch_bitstring_to(";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ", " << field_var << ")";
      return;
    case ct_bits:
      assert(l >= 0 && l < 0x10000);
      os << "cs.fetch_bits_to(" << field_var << ".bits(), " << l << ")";
      return;
    case ct_cell:
      assert(l == 0x10000);
      os << "cs.fetch_ref_to(" << field_var << ")";
      return;
    case ct_bool:
      assert(i > 0 && l == 1);
      os << "cs.fetch_bool_to(" << field_var << ")";
      return;
    case ct_int32:
    case ct_uint32:
    case ct_int64:
    case ct_uint64:
      assert(i && l <= 64);
      os << "cs.fetch_" << (i > 0 ? "u" : "") << "int_to(";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ", " << field_var << ")";
      return;
    case ct_integer:
      assert(i);
      os << "cs.fetch_" << (i > 0 ? "u" : "") << "int256_to(";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ", " << field_var << ")";
      return;
    default:
      break;
  }
  throw src::Fatal{"cannot fetch a field of unknown scalar type"};
}
void CppTypeCode::output_fetch_subrecord(std::ostream& os, std::string field_name, const ConsRecord* subrec) {
  assert(subrec);
  os << subrec->cpp_type.cpp_type_var_name << ".cell_unpack(cs.fetch_ref(), " << field_name << ")";
}
void CppTypeCode::generate_unpack_field(const CppTypeCode::ConsField& fi, const Constructor& constr, const Field& field,
                                        int options) {
  int i = field.field_idx;
  const TypeExpr* expr = field.type;
  MinMaxSize sz = expr->compute_size();
  bool any_bits = expr->compute_any_bits();
  bool validating = (options & 1);
  cpp_val_type cvt = fi.ctype;
  // std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
  if (field.used || expr->is_nat_subtype) {
    assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
    assert(cvt == ct_int32 || cvt == ct_bool);
    add_fetch_nat_field(constr, field, options);
    return;
  }
  if (sz.is_fixed() && cvt != ct_enum && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
    // field has fixed size, and either its bits can have arbitrary values (and it has no references)
    // ... or we are not validating
    // simply skip the necessary amount of bits
    // NB: if the field is a reference, and we are not validating, we arrive here
    if (cvt == ct_cell) {
      assert(sz.min_size() == 1);
    }
    std::ostringstream ss;
    if (cvt == ct_subrecord && field.subrec) {
      output_fetch_subrecord(ss, field_vars.at(i), fi.subrec);
    } else {
      output_fetch_field(ss, field_vars.at(i), expr, cvt);
    }
    actions += Action{std::move(ss)};
    field_var_set[i] = true;
    return;
  }
  if (expr->negated) {
    // the field type has some "negative" parameters, which will be computed while checking this field
    // must invoke the correct validate_skip or skip method for the type in question
    std::ostringstream ss;
    assert(cvt == ct_slice);
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100, true);
      ss << '.';
    }
    ss << (validating ? "validate_fetch_to(ops, cs, weak, " : "fetch_to(cs, ") << field_vars.at(i);
    output_negative_type_arguments(ss, expr);
    ss << ")";
    actions += Action{std::move(ss)};
    add_postponed_equate_actions();
    field_var_set[i] = true;
    return;
  }
  // at this point, if the field type is a reference, we must be validating
  if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
    // field type is a reference to a cell with arbitrary contents
    assert(cvt == ct_cell);
    actions += Action{"cs.fetch_ref_to(" + field_vars.at(i) + ")"};
    field_var_set[i] = true;
    return;
  }
  // remaining case: general positive type expression
  std::ostringstream ss;
  std::string tail;
  while (expr->tp == TypeExpr::te_CondType) {
    // optimization for (chains of) conditional types ( x?type )
    assert(expr->args.size() == 2);
    ss << "(!";
    output_cpp_expr(ss, expr->args[0], 30);
    ss << " || ";
    expr = expr->args[1];
    tail = std::string{")"} + tail;
  }
  if ((!validating || any_bits) && can_compute_sizeof(expr) && cvt != ct_enum) {
    // field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
    output_fetch_field(ss, field_vars.at(i), expr, cvt);
    field_var_set[i] = true;
    ss << tail;
    actions += Action{std::move(ss)};
    return;
  }
  if (expr->tp != TypeExpr::te_Ref) {
    // field type is not a reference, generate a type expression and invoke skip/validate_skip method
    assert(cvt == ct_slice || cvt == ct_enum);
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100);
      ss << '.';
    }
    ss << (validating ? "validate_" : "") << "fetch_" << (cvt == ct_enum ? "enum_" : "")
       << (validating ? "to(ops, cs, weak, " : "to(cs, ") << field_vars.at(i) << ")" << tail;
    field_var_set[i] = true;
    actions += Action{std::move(ss)};
    return;
  }
  // the (remaining) field type is a reference
  if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
                      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
    // the subcase when the field type is either a reference to a cell with arbitrary contents
    // or it is a reference, and we are not validating, so we simply skip the reference
    assert(cvt == ct_cell);
    ss << "cs.fetch_ref_to(" << field_vars.at(i) << ")" << tail;
    field_var_set[i] = true;
    actions += Action{std::move(ss)};
    return;
  }
  // general reference type, invoke validate_skip_ref()
  // (notice that we are necessarily validating at this point)
  expr = expr->args[0];
  assert(cvt == ct_cell);
  ss << "(cs.fetch_ref_to(" << field_vars.at(i) << ") && ";
  if (!is_self(expr, constr)) {
    output_cpp_expr(ss, expr, 100);
    ss << '.';
  }
  ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail;
  actions += Action{ss.str()};
}
void CppTypeCode::generate_unpack_method(std::ostream& os, CppTypeCode::ConsRecord& rec, int options) {
  std::ostringstream tmp;
  if (!rec.declare_record_unpack(tmp, "", options)) {
    return;
  }
  tmp.clear();
  os << "\n";
  bool res = rec.declare_record_unpack(os, "", options | 3072);
  DCHECK(res);
  if (options & 16) {
    // cell unpack version
    os << "\n  if (cell_ref.is_null()) { return false; }"
       << "\n  auto cs = load_cell_slice(std::move(cell_ref));"
       << "\n  return " << (options & 1 ? "validate_" : "") << "unpack";
    if (!(options & 8)) {
      os << "(";
      if (options & 1) {
        os << "ops, ";
      }
      os << "cs, data";
    } else {
      os << "_" << cons_enum_name.at(rec.cons_idx) << "(cs";
      for (const auto& f : rec.cpp_fields) {
        os << ", " << f.name;
      }
    }
    if (options & 2) {
      os << skip_extra_args_pass;
    }
    os << ") && cs.empty_ext();\n}\n";
    return;
  }
  init_cons_context(rec.constr);
  bind_record_fields(rec, options);
  identify_cons_params(rec.constr, options);
  identify_cons_neg_params(rec.constr, options);
  add_cons_tag_check(rec.constr, rec.cons_idx, 9 /* (options & 1) | 8 */);
  auto it = rec.cpp_fields.cbegin(), end = rec.cpp_fields.cend();
  for (const Field& field : rec.constr.fields) {
    if (field.constraint) {
      add_constraint_check(rec.constr, field, options);
      continue;
    }
    if (!field.implicit) {
      assert(it < end && it->orig_idx == field.field_idx);
      generate_unpack_field(*it++, rec.constr, field, options);
    } else {
      if (it < end && it->orig_idx == field.field_idx) {
        ++it;
      }
      compute_implicit_field(rec.constr, field, options);
    }
  }
  assert(it == end);
  add_remaining_param_constraints_check(rec.constr, options);
  output_actions(os, "\n  ", options | 4);
  clear_context();
  os << "\n}\n";
}
void CppTypeCode::output_store_field(std::ostream& os, std::string field_var, const TypeExpr* expr, cpp_val_type cvt) {
  int i = expr->is_integer();
  MinMaxSize sz = expr->compute_size();
  int l = (sz.is_fixed() ? sz.convert_min_size() : -1);
  switch (cvt) {
    case ct_slice:
      os << "cb.append_cellslice_chk(" << field_var << ", ";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ")";
      return;
    case ct_bitstring:
      assert(!(sz.max_size() & 0xff));
      os << "cb.append_bitstring_chk(" << field_var << ", ";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ")";
      return;
    case ct_bits:
      assert(l >= 0 && l < 0x10000);
      os << "cb.store_bits_bool(" << field_var << ".cbits(), " << l << ")";
      return;
    case ct_cell:
      assert(l == 0x10000);
      os << "cb.store_ref_bool(" << field_var << ")";
      return;
    case ct_bool:
      assert(i > 0 && l == 1);
      // os << "cb.store_bool(" << field_var << ")";
      // return;
      // fall through
    case ct_int32:
    case ct_uint32:
    case ct_int64:
    case ct_uint64:
      assert(i && l <= 64);
      os << "cb.store_" << (i > 0 ? "u" : "") << "long_rchk_bool(" << field_var << ", ";
      output_cpp_sizeof_expr(os, expr, 0);
      os << ")";
      return;
    case ct_integer:
      assert(i);
      os << "cb.store_int256_bool(" << field_var << ", ";
      output_cpp_sizeof_expr(os, expr, 0);
      os << (i > 0 ? ", false)" : ")");
      return;
    default:
      break;
  }
  throw src::Fatal{"cannot store a field of unknown scalar type"};
}
void CppTypeCode::add_store_subrecord(std::string field_name, const ConsRecord* subrec) {
  assert(subrec);
  needs_tmp_cell = true;
  std::ostringstream ss;
  ss << subrec->cpp_type.cpp_type_var_name << ".cell_pack(tmp_cell, " << field_name << ")";
  actions += Action{std::move(ss)};
  actions += Action{"cb.store_ref_bool(std::move(tmp_cell))"};
}
void CppTypeCode::generate_pack_field(const CppTypeCode::ConsField& fi, const Constructor& constr, const Field& field,
                                      int options) {
  int i = field.field_idx;
  const TypeExpr* expr = field.type;
  MinMaxSize sz = expr->compute_size();
  bool any_bits = expr->compute_any_bits();
  bool validating = (options & 1);
  cpp_val_type cvt = fi.ctype;
  // std::cerr << "field `" << field.get_name() << "` size is " << sz << "; fixed=" << sz.is_fixed() << "; any=" << any_bits << std::endl;
  if (field.used || expr->is_nat_subtype) {
    assert(expr->is_nat_subtype && "cannot use fields of non-`#` type");
    assert(cvt == ct_int32 || cvt == ct_bool);
    add_store_nat_field(constr, field, options);
    return;
  }
  if (sz.is_fixed() && cvt != ct_enum && (!validating || (!(sz.min_size() & 0xff) && any_bits))) {
    // field has fixed size, and either its bits can have arbitrary values (and it has no references)
    // ... or we are not validating
    // simply skip the necessary amount of bits
    // NB: if the field is a reference, and we are not validating, we arrive here
    if (cvt == ct_cell) {
      assert(sz.min_size() == 1);
    }
    if (cvt == ct_subrecord && field.subrec) {
      add_store_subrecord(field_vars.at(i), fi.subrec);
    } else {
      std::ostringstream ss;
      output_store_field(ss, field_vars.at(i), expr, cvt);
      actions += Action{std::move(ss)};
    }
    field_var_set[i] = true;
    return;
  }
  if (expr->negated) {
    // the field type has some "negative" parameters, which will be computed while checking this field
    // must invoke the correct validate_skip or skip method for the type in question
    std::ostringstream ss;
    assert(cvt == ct_slice);
    ss << "tlb::" << (validating ? "validate_" : "") << "store_from(cb, ";
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 5, true);
    } else {
      ss << "*this";
    }
    ss << ", " << field_vars.at(i);
    output_negative_type_arguments(ss, expr);
    ss << ")";
    actions += Action{std::move(ss)};
    add_postponed_equate_actions();
    field_var_set[i] = true;
    return;
  }
  // at this point, if the field type is a reference, we must be validating
  if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type)) {
    // field type is a reference to a cell with arbitrary contents
    assert(cvt == ct_cell);
    actions += Action{"cb.store_ref_bool(" + field_vars.at(i) + ")"};
    field_var_set[i] = true;
    return;
  }
  // remaining case: general positive type expression
  std::ostringstream ss;
  std::string tail;
  while (expr->tp == TypeExpr::te_CondType) {
    // optimization for (chains of) conditional types ( x?type )
    assert(expr->args.size() == 2);
    ss << "(!";
    output_cpp_expr(ss, expr->args[0], 30);
    ss << " || ";
    expr = expr->args[1];
    tail = std::string{")"} + tail;
  }
  if ((!validating || any_bits) && can_compute_sizeof(expr) && cvt != ct_enum) {
    // field size can be computed at run-time, and either the contents is arbitrary, or we are not validating
    output_store_field(ss, field_vars.at(i), expr, cvt);
    field_var_set[i] = true;
    ss << tail;
    actions += Action{std::move(ss)};
    return;
  }
  if (expr->tp != TypeExpr::te_Ref) {
    // field type is not a reference, generate a type expression and invoke skip/validate_skip method
    assert(cvt == ct_slice || cvt == ct_enum);
    if (!is_self(expr, constr)) {
      output_cpp_expr(ss, expr, 100);
      ss << '.';
    }
    ss << (validating ? "validate_" : "") << "store_" << (cvt == ct_enum ? "enum_" : "") << "from(cb, "
       << field_vars.at(i) << ")" << tail;
    field_var_set[i] = true;
    actions += Action{std::move(ss)};
    return;
  }
  // the (remaining) field type is a reference
  if (!validating || (expr->args[0]->tp == TypeExpr::te_Apply &&
                      (expr->args[0]->type_applied == Cell_type || expr->args[0]->type_applied == Any_type))) {
    // the subcase when the field type is either a reference to a cell with arbitrary contents
    // or it is a reference, and we are not validating, so we simply skip the reference
    assert(cvt == ct_cell);
    ss << "cb.store_ref_bool(" << field_vars.at(i) << ")" << tail;
    field_var_set[i] = true;
    actions += Action{std::move(ss)};
    return;
  }
  // general reference type, invoke validate_skip_ref()
  // (notice that we are necessarily validating at this point)
  expr = expr->args[0];
  assert(cvt == ct_cell);
  ss << "(cb.store_ref_bool(" << field_vars.at(i) << ") && ";
  if (!is_self(expr, constr)) {
    output_cpp_expr(ss, expr, 100);
    ss << '.';
  }
  ss << "validate_ref(ops, " << field_vars.at(i) << "))" << tail;
  actions += Action{ss.str()};
}
void CppTypeCode::generate_pack_method(std::ostream& os, CppTypeCode::ConsRecord& rec, int options) {
  std::ostringstream tmp;
  if (!rec.declare_record_pack(tmp, "", options)) {
    return;
  }
  tmp.clear();
  os << "\n";
  bool res = rec.declare_record_pack(os, "", options | 3072);
  DCHECK(res);
  if (options & 16) {
    // cell pack version
    os << "\n  vm::CellBuilder cb;"
       << "\n  return " << (options & 1 ? "validate_" : "") << "pack";
    if (!(options & 8)) {
      os << "(cb, data";
    } else {
      os << "_" << cons_enum_name.at(rec.cons_idx) << "(cb";
      for (const auto& f : rec.cpp_fields) {
        // skip SOME implicit fields ???
        if (f.implicit) {
        } else if (f.get_cvt().needs_move()) {
          os << ", std::move(" << f.name << ")";
        } else {
          os << ", " << f.name;
        }
      }
    }
    if (options & 2) {
      os << skip_extra_args_pass;
    }
    os << ") && std::move(cb).finalize_to(cell_ref);\n}\n";
    return;
  }
  init_cons_context(rec.constr);
  bind_record_fields(rec, options | 32);
  identify_cons_params(rec.constr, options);
  identify_cons_neg_params(rec.constr, options);
  add_cons_tag_store(rec.constr, rec.cons_idx);
  auto it = rec.cpp_fields.cbegin(), end = rec.cpp_fields.cend();
  for (const Field& field : rec.constr.fields) {
    if (field.constraint) {
      add_constraint_check(rec.constr, field, options);
      continue;
    }
    if (!field.implicit) {
      assert(it < end && it->orig_idx == field.field_idx);
      generate_pack_field(*it++, rec.constr, field, options);
    } else {
      if (it < end && it->orig_idx == field.field_idx) {
        ++it;
      }
      compute_implicit_field(rec.constr, field, options);
    }
  }
  assert(it == end);
  add_remaining_param_constraints_check(rec.constr, options);
  output_actions(os, "\n  ", options | 4);
  clear_context();
  os << "\n}\n";
}
void CppTypeCode::generate_ext_fetch_to(std::ostream& os, int options) {
  std::string validate = (options & 1) ? "validate_" : "";
  os << "\nbool " << cpp_type_class_name << "::" << validate << "fetch_to(vm::CellSlice& cs, Ref& res"
     << skip_extra_args << ") const {\n"
     << "  res = Ref{true, cs};\n"
     << "  return " << validate << "skip(cs" << skip_extra_args_pass << ") && res.unique_write().cut_tail(cs);\n"
     << "}\n";
}
void CppTypeCode::ConsRecord::declare_record(std::ostream& os, std::string nl, int options) {
  bool force = options & 1024;
  if (declared) {
    return;
  }
  if (!force) {
    os << nl << "struct " << cpp_name;
    if (!inline_record) {
      os << ";\n";
      return;
    }
  } else {
    os << "\n" << nl << "struct " << cpp_type.cpp_type_class_name << "::" << cpp_name;
  }
  os << " {\n";
  os << nl << "  typedef " << cpp_type.cpp_type_class_name << " type_class;\n";
  CppIdentSet rec_cpp_ids;
  recover_idents(rec_cpp_ids);
  std::size_t n = cpp_fields.size();
  for (const ConsField& fi : cpp_fields) {
    os << nl << "  ";
    fi.print_type(os);
    os << " " << fi.name << ";  \t// ";
    if (fi.field.name) {
      os << fi.field.get_name() << " : ";
    }
    fi.field.type->show(os, &constr);
    os << std::endl;
  }
  if (n) {
    os << nl << "  " << cpp_name << "() = default;\n";
    std::vector ctor_args;
    os << nl << "  " << cpp_name << "(";
    int i = 0, j = 0;
    for (const ConsField& fi : cpp_fields) {
      if (!fi.implicit) {
        std::string arg = rec_cpp_ids.new_ident(std::string{"_"} + fi.name);
        ctor_args.push_back(arg);
        if (i++) {
          os << ", ";
        }
        fi.print_type(os, true);
        os << " " << arg;
      }
    }
    os << ") : ";
    i = 0;
    for (const ConsField& fi : cpp_fields) {
      if (i++) {
        os << ", ";
      }
      os << fi.name << "(";
      if (fi.implicit) {
        os << (fi.ctype == ct_int32 ? "-1" : "nullptr");
      } else if (fi.get_cvt().needs_move()) {
        os << "std::move(" << ctor_args.at(j++) << ")";
      } else {
        os << ctor_args.at(j++);
      }
      os << ")";
    }
    os << " {}\n";
  }
  os << nl << "};\n";
  declared = true;
}
bool CppTypeCode::ConsRecord::declare_record_unpack(std::ostream& os, std::string nl, int options) {
  bool is_ok = false;
  bool cell = options & 16;
  std::string slice_arg = cell ? "Ref cell_ref" : "vm::CellSlice& cs";
  std::string fun_name = (options & 1) ? "validate_unpack" : "unpack";
  if (cell) {
    fun_name = std::string{"cell_"} + fun_name;
  }
  std::string class_name;
  if (options & 2048) {
    class_name = cpp_type.cpp_type_class_name + "::";
  }
  if (!(options & 8)) {
    os << nl << "bool " << class_name << fun_name << "(" << slice_arg << ", " << class_name << cpp_name << "& data";
    is_ok = true;
  } else if (is_small) {
    os << nl << "bool " << class_name << fun_name << "_" << cpp_type.cons_enum_name.at(cons_idx) << "(" << slice_arg;
    for (const auto& f : cpp_fields) {
      os << ", " << f.get_cvt() << "& " << f.name;
    }
    is_ok = true;
  }
  if (is_ok) {
    if (options & 2) {
      os << cpp_type.skip_extra_args;
    }
    os << ") const" << (options & 1024 ? " {" : ";\n");
  }
  return is_ok;
}
bool CppTypeCode::ConsRecord::declare_record_pack(std::ostream& os, std::string nl, int options) {
  bool is_ok = false;
  bool cell = options & 16;
  std::string builder_arg = cell ? "Ref& cell_ref" : "vm::CellBuilder& cb";
  std::string fun_name = (options & 1) ? "validate_pack" : "pack";
  if (cell) {
    fun_name = std::string{"cell_"} + fun_name;
  }
  std::string class_name;
  if (options & 2048) {
    class_name = cpp_type.cpp_type_class_name + "::";
  }
  if (!(options & 8)) {
    os << nl << "bool " << class_name << fun_name << "(" << builder_arg << ", const " << class_name << cpp_name
       << "& data";
    is_ok = true;
  } else if (is_small) {
    os << nl << "bool " << class_name << fun_name << "_" << cpp_type.cons_enum_name.at(cons_idx) << "(" << builder_arg;
    for (const auto& f : cpp_fields) {
      // skip SOME implicit fields ???
      if (!f.implicit) {
        os << ", " << f.get_cvt() << " " << f.name;
      }
    }
    is_ok = true;
  }
  if (is_ok) {
    if (options & 2) {
      os << cpp_type.skip_extra_args;
    }
    os << ") const" << (options & 1024 ? " {" : ";\n");
  }
  return is_ok;
}
void CppTypeCode::generate_fetch_enum_method(std::ostream& os, int options) {
  int minl = type.size.convert_min_size(), maxl = type.size.convert_max_size();
  bool exact = type.cons_all_exact();
  std::string ctag = incremental_cons_tags ? "(unsigned)t" : "cons_tag[t]";
  os << "\nbool " << cpp_type_class_name << "::fetch_enum_to(vm::CellSlice& cs, char& value) const {\n";
  if (!cons_num) {
    os << "  value = -1;\n"
          "  return false;\n";
  } else if (!maxl) {
    os << "  value = 0;\n"
          "  return true;\n";
  } else if (cons_num == 1) {
    const Constructor& constr = *type.constructors.at(0);
    os << "  value = (cs.fetch_ulong(" << minl << ") == " << HexConstWriter{constr.tag >> (64 - constr.tag_bits)}
       << ") ? 0 : -1;\n";
    os << "  return !value;\n";
  } else if (minl == maxl) {
    if (exact) {
      os << "  value = (char)cs.fetch_ulong(" << minl << ");\n";
      os << "  return value >= 0;\n";
    } else {
      os << "  int t = get_tag(cs);\n";
      os << "  value = (char)t;\n";
      os << "  return t >= 0 && cs.fetch_ulong(" << minl << ") == " << ctag << ";\n";
    }
  } else if (exact) {
    os << "  int t = get_tag(cs);\n";
    os << "  value = (char)t;\n";
    os << "  return t >= 0 && cs.advance(cons_len[t]);\n";
  } else {
    os << "  int t = get_tag(cs);\n";
    os << "  value = (char)t;\n";
    os << "  return t >= 0 && cs.fetch_ulong(cons_len[t]) == " << ctag << ";\n";
  }
  os << "}\n";
}
void CppTypeCode::generate_store_enum_method(std::ostream& os, int options) {
  int minl = type.size.convert_min_size(), maxl = type.size.convert_max_size();
  bool exact = type.cons_all_exact();
  std::string ctag = incremental_cons_tags ? "value" : "cons_tag[value]";
  os << "\nbool " << cpp_type_class_name << "::store_enum_from(vm::CellBuilder& cb, int value) const {\n";
  if (!cons_num) {
    os << "  return false;\n";
  } else if (!maxl) {
    os << "  return !value;\n";
  } else if (cons_num == 1) {
    const Constructor& constr = *type.constructors.at(0);
    os << "  return !value && cb.store_long_bool(" << HexConstWriter{constr.tag >> (64 - constr.tag_bits)} << ", "
       << minl << ");\n";
  } else if (minl == maxl) {
    if (exact) {
      os << "  return cb.store_long_rchk_bool(value, " << minl << ");\n";
    } else if (incremental_cons_tags && cons_num > (1 << (minl - 1))) {
      os << "  return cb.store_uint_less(" << cons_num << ", value);\n";
    } else {
      os << "  return (unsigned)value < " << cons_num << " && cb.store_long_bool(" << ctag << ", " << minl << ");\n";
    }
  } else {
    os << "  return (unsigned)value < " << cons_num << " && cb.store_long_bool(" << ctag << ", cons_len[value]);\n";
  }
  os << "}\n";
}
void CppTypeCode::generate_print_type_body(std::ostream& os, std::string nl) {
  std::string name = type.type_name ? type.get_name() : cpp_type_class_name;
  if (!tot_params) {
    os << nl << "return os << \"" << name << "\";";
    return;
  }
  os << nl << "return os << \"(" << name;
  for (int i = 0; i < tot_params; i++) {
    if (type_param_is_neg[i]) {
      os << " ~" << type_param_name[i];
    } else {
      os << " \" << " << type_param_name[i] << " << \"";
    }
  }
  os << ")\";";
}
void CppTypeCode::generate_header(std::ostream& os, int options) {
  os << "\nstruct " << cpp_type_class_name << " final : TLB_Complex {\n";
  generate_cons_enum(os);
  generate_cons_tag_info(os, "  ", 1);
  if (params) {
    generate_type_fields(os, options);
    generate_type_constructor(os, options);
  }
  for (int i = 0; i < cons_num; i++) {
    records.at(i).declare_record(os, "  ", options);
  }
  if (type.is_special) {
    os << "  bool always_special() const override {\n";
    os << "    return true;\n  }\n";
  }
  int sz = type.size.min_size();
  sz = ((sz & 0xff) << 16) | (sz >> 8);
  if (simple_get_size) {
    os << "  int get_size(const vm::CellSlice& cs) const override {\n";
    os << "    return " << SizeWriter{sz} << ";\n  }\n";
  }
  os << "  bool skip(vm::CellSlice& cs) const override";
  if (!inline_skip) {
    os << ";\n";
  } else if (sz) {
    os << " {\n    return cs.advance" << (sz < 0x10000 ? "(" : "_ext(") << SizeWriter{sz} << ");\n  }\n";
  } else {
    os << " {\n    return true;\n  }\n";
  }
  if (ret_params) {
    os << "  bool skip(vm::CellSlice& cs" << skip_extra_args << ") const;\n";
  }
  os << "  bool validate_skip(int* ops, vm::CellSlice& cs, bool weak = false) const override";
  if (!inline_validate_skip) {
    os << ";\n";
  } else if (sz) {
    os << " {\n    return cs.advance(" << SizeWriter{sz} << ");\n  }\n";
  } else {
    os << " {\n    return true;\n  }\n";
  }
  if (ret_params) {
    os << "  bool validate_skip(int *ops, vm::CellSlice& cs, bool weak" << skip_extra_args << ") const;\n";
    os << "  bool fetch_to(vm::CellSlice& cs, Ref& res" << skip_extra_args << ") const;\n";
  }
  if (type.is_simple_enum) {
    os << "  bool fetch_enum_to(vm::CellSlice& cs, char& value) const;\n";
    os << "  bool store_enum_from(vm::CellBuilder& cb, int value) const;\n";
  }
  for (int i = 0; i < cons_num; i++) {
    records[i].declare_record_unpack(os, "  ", 2);
    records[i].declare_record_unpack(os, "  ", 10);
    records[i].declare_record_unpack(os, "  ", 18);
    records[i].declare_record_unpack(os, "  ", 26);
    records[i].declare_record_pack(os, "  ", 2);
    records[i].declare_record_pack(os, "  ", 10);
    records[i].declare_record_pack(os, "  ", 18);
    records[i].declare_record_pack(os, "  ", 26);
  }
  os << "  bool print_skip(PrettyPrinter& pp, vm::CellSlice& cs) const override;\n";
  if (ret_params) {
    os << "  bool print_skip(PrettyPrinter& pp, vm::CellSlice& cs" << skip_extra_args << ") const;\n";
  }
  os << "  std::ostream& print_type(std::ostream& os) const override {";
  generate_print_type_body(os, "\n    ");
  os << "\n  }\n";
  os << "  int check_tag(const vm::CellSlice& cs) const override;\n";
  os << "  int get_tag(const vm::CellSlice& cs) const override";
  if (inline_get_tag) {
    os << " {";
    generate_get_tag_body(os, "\n    ");
    os << "\n  }\n";
  } else {
    os << ";\n";
  }
  os << "};\n";
  for (int i = 0; i < cons_num; i++) {
    records.at(i).declare_record(os, "", options | 1024);
  }
  if (!cpp_type_var_name.empty()) {
    os << "\nextern const " << cpp_type_class_name << " " << cpp_type_var_name << ";\n";
  }
}
void CppTypeCode::generate_body(std::ostream& os, int options) {
  generate_cons_tag_info(os, "", 2);
  if (!inline_get_tag) {
    os << "\nint " << cpp_type_class_name << "::get_tag(const vm::CellSlice& cs) const {";
    generate_get_tag_body(os, "\n  ");
    os << "\n}\n";
  }
  generate_check_tag_method(os);
  options &= -4;
  if (!inline_skip) {
    generate_skip_method(os, options);
  }
  if (ret_params) {
    generate_skip_method(os, options + 2);
  }
  if (!inline_validate_skip) {
    generate_skip_method(os, options + 1);
  }
  if (ret_params) {
    generate_skip_method(os, options + 3);
    generate_ext_fetch_to(os, options);
  }
  if (type.is_simple_enum) {
    generate_fetch_enum_method(os, options);
    generate_store_enum_method(os, options);
  }
  for (int i = 0; i < cons_num; i++) {
    ConsRecord& rec = records.at(i);
    generate_unpack_method(os, rec, 2);
    generate_unpack_method(os, rec, 10);
    generate_unpack_method(os, rec, 18);
    generate_unpack_method(os, rec, 26);
  }
  for (int i = 0; i < cons_num; i++) {
    ConsRecord& rec = records.at(i);
    generate_pack_method(os, rec, 2);
    generate_pack_method(os, rec, 10);
    generate_pack_method(os, rec, 18);
    generate_pack_method(os, rec, 26);
  }
  generate_print_method(os, options + 1);
  if (ret_params) {
    generate_print_method(os, options + 3);
  }
  if (!cpp_type_var_name.empty()) {
    os << "\nconst " << cpp_type_class_name << " " << cpp_type_var_name << ";";
  }
  os << std::endl;
}
void CppTypeCode::generate(std::ostream& os, int options) {
  std::string type_name = type.get_name();
  if (!type.type_name && type.is_auto) {
    type_name = cpp_type_class_name;
  }
  if (options & 1) {
    os << "\n//\n// headers for " << (type.is_auto ? "auxiliary " : "") << "type `" << type_name << "`\n//\n";
    generate_header(os, options);
  } else if (options & 2) {
    std::ostringstream tmp;
    generate_header(tmp, options | 1);
  }
  if (options & 2) {
    os << "\n//\n// code for " << (type.is_auto ? "auxiliary " : "") << "type `" << type_name << "`\n//\n";
    generate_body(os, options);
  }
}
void generate_type_constant(std::ostream& os, int i, TypeExpr* expr, std::string cpp_name, int mode) {
  if (!mode) {
    os << "// " << expr << std::endl;
    os << "extern ";
  }
  std::string cls_name = "TLB";
  int fake_arg = -1;
  cls_name = compute_type_expr_class_name(expr, fake_arg);
  os << "const " << cls_name << ' ' << cpp_name;
  if (!mode) {
    os << ";\n";
    return;
  }
  int c = 0;
  if (fake_arg >= 0) {
    os << '{' << fake_arg;
    c++;
  }
  for (const TypeExpr* arg : expr->args) {
    if (!arg->negated) {
      assert(arg->is_constexpr);
      os << (c++ ? ", " : "{");
      if (arg->is_nat) {
        os << arg->value;
      } else {
        os << const_type_expr_cpp_idents.at(arg->is_constexpr);
      }
    }
  }
  if (c) {
    os << '}';
  }
  os << ";\n";
}
void generate_type_constants(std::ostream& os, int mode) {
  os << "\n// " << (mode ? "definitions" : "declarations") << " of constant types used\n\n";
  for (int i = 1; i <= const_type_expr_num; i++) {
    TypeExpr* expr = const_type_expr[i];
    if (!expr->is_nat && !const_type_expr_simple[i]) {
      generate_type_constant(os, i, expr, const_type_expr_cpp_idents[i], mode);
    }
  }
}
void generate_register_function(std::ostream& os, int mode) {
  os << "\n// " << (mode ? "definition" : "declaration") << " of type name registration function\n";
  if (!mode) {
    os << "extern bool register_simple_types(std::function func);\n";
    return;
  }
  os << "bool register_simple_types(std::function func) {\n";
  os << "  return ";
  int k = 0;
  for (int i = builtin_types_num; i < types_num; i++) {
    Type& type = types[i];
    CppTypeCode& cc = *cpp_type[i];
    if (!cc.cpp_type_var_name.empty() && type.type_name) {
      if (k++) {
        os << "\n      && ";
      }
      os << "func(\"" << type.get_name() << "\", &" << cc.cpp_type_var_name << ")";
    }
  }
  if (!k) {
    os << "true";
  }
  os << ";\n}\n\n";
}
void assign_const_type_cpp_idents() {
  const_type_expr_cpp_idents.resize(const_type_expr_num + 1, "");
  const_type_expr_simple.resize(const_type_expr_num + 1, false);
  for (int i = 1; i <= const_type_expr_num; i++) {
    const TypeExpr* expr = const_type_expr[i];
    if (!expr->is_nat) {
      if (expr->tp == TypeExpr::te_Ref && expr->args[0]->tp == TypeExpr::te_Apply &&
          (expr->args[0]->type_applied == Any_type || expr->args[0]->type_applied == Cell_type)) {
        const_type_expr_cpp_idents[i] = "t_RefCell";
        const_type_expr_simple[i] = true;
        continue;
      }
      if (expr->tp == TypeExpr::te_Apply) {
        const Type* typ = expr->type_applied;
        int idx = typ->type_idx;
        if (typ == Any_type || typ == Cell_type || typ == Nat_type) {
          const_type_expr_cpp_idents[i] = (typ == Nat_type ? "t_Nat" : "t_Anything");
          const_type_expr_simple[i] = true;
          continue;
        }
        if (idx >= builtin_types_num && idx < types_num && !cpp_type[idx]->params) {
          const_type_expr_cpp_idents[i] = cpp_type[idx]->cpp_type_var_name;
          const_type_expr_simple[i] = true;
          continue;
        }
      }
      std::ostringstream ss;
      ss << "t";
      expr->const_type_name(ss);
      const_type_expr_cpp_idents[i] = global_cpp_ids.new_ident(ss.str());
    }
  }
}
std::string cpp_namespace = "tlb";
std::vector cpp_namespace_list;
std::string tlb_library_header_name = "tl/tlblib.hpp";
void split_namespace_id() {
  auto prev_it = cpp_namespace.cbegin();
  for (auto it = cpp_namespace.cbegin(); it != cpp_namespace.cend(); ++it) {
    if (it[0] == ':' && it + 2 != cpp_namespace.cend() && it[1] == ':') {
      if (prev_it != it) {
        cpp_namespace_list.emplace_back(prev_it, it);
      }
      ++it;
      prev_it = it + 1;
    }
  }
  if (prev_it != cpp_namespace.cend()) {
    cpp_namespace_list.emplace_back(prev_it, cpp_namespace.cend());
  }
}
std::vector type_gen_order;
void prepare_generate_cpp(int options = 0) {
  std::vector> pairs;
  pairs.reserve(types_num - builtin_types_num);
  for (int i = builtin_types_num; i < types_num; i++) {
    pairs.emplace_back(types.at(i).last_declared, i);
  }
  std::sort(pairs.begin(), pairs.end());
  type_gen_order.reserve(pairs.size());
  for (auto z : pairs) {
    type_gen_order.push_back(z.second);
  }
  cpp_type.resize(types_num);
  for (int i : type_gen_order) {
    Type& type = types[i];
    cpp_type[i] = std::make_unique(type);
    CppTypeCode& cc = *cpp_type[i];
    if (!cpp_type[i] || !cc.is_ok()) {
      throw src::Fatal{std::string{"cannot generate c++ code for type `"} + type.get_name() + "`"};
    }
  }
  split_namespace_id();
  assign_const_type_cpp_idents();
}
bool generate_prepared;
bool gen_cpp;
bool gen_hpp;
bool append_suffix;
void generate_cpp_output_to(std::ostream& os, int options = 0, std::vector include_files = {}) {
  if (!generate_prepared) {
    prepare_generate_cpp(options);
    generate_prepared = true;
  }
  if (options & 1) {
    os << "#pragma once\n";
  }
  for (auto s : include_files) {
    if (s.size() >= 10 && s.substr(s.size() - 10) == "tlblib.hpp") {
      os << "#include <" << s << ">\n";
    } else {
      os << "#include \"" << s << "\"\n";
    }
  }
  os << "/*\n *\n *  AUTO-GENERATED FROM";
  for (auto s : source_list) {
    if (s.empty()) {
      os << " stdin";
    } else {
      os << " `" << s << "`";
    }
  }
  os << "\n *\n */\n";
  for (int i = 0; i < builtin_types_num; i++) {
    Type& type = types[i];
    if (type.used) {
      os << "// uses built-in type `" << type.get_name() << "`\n";
    }
  }
  for (auto cpp_nsp : cpp_namespace_list) {
    os << "\nnamespace " << cpp_nsp << " {" << std::endl;
  };
  if (cpp_namespace != "tlb") {
    os << "using namespace ::tlb;\n";
  }
  os << "using td::Ref;\n"
     << "using vm::CellSlice;\n"
     << "using vm::Cell;\n"
     << "using td::RefInt256;\n";
  for (int pass = 1; pass <= 2; pass++) {
    if (options & pass) {
      for (int i : type_gen_order) {
        CppTypeCode& cc = *cpp_type[i];
        cc.generate(os, (options & -4) | pass);
      }
      generate_type_constants(os, pass - 1);
      generate_register_function(os, pass - 1);
    }
  }
  for (auto it = cpp_namespace_list.rbegin(); it != cpp_namespace_list.rend(); ++it) {
    os << "\n} // namespace " << *it << std::endl;
  }
}
void generate_cpp_output_to(std::string filename, int options = 0, std::vector include_files = {}) {
  std::stringstream ss;
  generate_cpp_output_to(ss, options, std::move(include_files));
  auto new_content = ss.str();
  auto r_old_content = td::read_file_str(filename);
  if (r_old_content.is_ok() && r_old_content.ok() == new_content) {
    return;
  }
  std::ofstream os{filename};
  if (!os) {
    throw src::Fatal{std::string{"cannot create output file `"} + filename + "`"};
  }
  os << new_content;
}
void generate_cpp_output(std::string filename = "", int options = 0) {
  if (!gen_cpp && !gen_hpp) {
    gen_cpp = gen_hpp = true;
  }
  options &= ~3;
  options |= (gen_hpp ? 1 : 0) | (gen_cpp << 1);
  if (filename.empty()) {
    generate_cpp_output_to(std::cout, options, {tlb_library_header_name});
  } else if (!append_suffix) {
    generate_cpp_output_to(filename, options, {tlb_library_header_name});
  } else {
    if (gen_hpp) {
      generate_cpp_output_to(filename + ".h", options & ~2, {tlb_library_header_name});
    }
    if (gen_cpp) {
      generate_cpp_output_to(filename + ".cpp", options & ~1, {filename + ".h"});
    }
  }
}
}  // namespace tlbc |