mirror of
https://github.com/Z3Prover/z3
synced 2025-04-23 17:15:31 +00:00
hide new datatype plugin
Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
parent
09386e43e3
commit
a3dba5b2f9
24 changed files with 211 additions and 191 deletions
|
@ -907,7 +907,6 @@ public:
|
|||
void pp_dt(ast_mark& mark, sort* s) {
|
||||
SASSERT(s->is_sort_of(m_dt_fid, DATATYPE_SORT));
|
||||
datatype_util util(m_manager);
|
||||
ptr_vector<func_decl> const* decls;
|
||||
ptr_vector<sort> rec_sorts;
|
||||
|
||||
rec_sorts.push_back(s);
|
||||
|
@ -916,10 +915,10 @@ public:
|
|||
// collect siblings and sorts that have not already been printed.
|
||||
for (unsigned h = 0; h < rec_sorts.size(); ++h) {
|
||||
s = rec_sorts[h];
|
||||
decls = util.get_datatype_constructors(s);
|
||||
ptr_vector<func_decl> const& decls = util.get_datatype_constructors(s);
|
||||
|
||||
for (unsigned i = 0; i < decls->size(); ++i) {
|
||||
func_decl* f = (*decls)[i];
|
||||
for (unsigned i = 0; i < decls.size(); ++i) {
|
||||
func_decl* f = decls[i];
|
||||
for (unsigned j = 0; j < f->get_arity(); ++j) {
|
||||
sort* s2 = f->get_domain(j);
|
||||
if (!mark.is_marked(s2)) {
|
||||
|
@ -955,11 +954,11 @@ public:
|
|||
m_out << "(";
|
||||
m_out << m_renaming.get_symbol(s->get_name());
|
||||
m_out << " ";
|
||||
decls = util.get_datatype_constructors(s);
|
||||
ptr_vector<func_decl> const& decls = util.get_datatype_constructors(s);
|
||||
|
||||
for (unsigned i = 0; i < decls->size(); ++i) {
|
||||
func_decl* f = (*decls)[i];
|
||||
ptr_vector<func_decl> const& accs = *util.get_constructor_accessors(f);
|
||||
for (unsigned i = 0; i < decls.size(); ++i) {
|
||||
func_decl* f = decls[i];
|
||||
ptr_vector<func_decl> const& accs = util.get_constructor_accessors(f);
|
||||
if (m_is_smt2 || accs.size() > 0) {
|
||||
m_out << "(";
|
||||
}
|
||||
|
@ -976,7 +975,7 @@ public:
|
|||
}
|
||||
if (m_is_smt2 || accs.size() > 0) {
|
||||
m_out << ")";
|
||||
if (i + 1 < decls->size()) {
|
||||
if (i + 1 < decls.size()) {
|
||||
m_out << " ";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ Revision History:
|
|||
#include "util/warning.h"
|
||||
#include "ast/ast_smt2_pp.h"
|
||||
|
||||
#ifndef DATATYPE_V2
|
||||
|
||||
/**
|
||||
\brief Auxiliary class used to declare inductive datatypes.
|
||||
|
@ -802,11 +803,11 @@ func_decl * datatype_util::get_constructor(sort * ty, unsigned c_id) {
|
|||
return d;
|
||||
}
|
||||
|
||||
ptr_vector<func_decl> const * datatype_util::get_datatype_constructors(sort * ty) {
|
||||
ptr_vector<func_decl> const & datatype_util::get_datatype_constructors(sort * ty) {
|
||||
SASSERT(is_datatype(ty));
|
||||
ptr_vector<func_decl> * r = 0;
|
||||
if (m_datatype2constructors.find(ty, r))
|
||||
return r;
|
||||
return *r;
|
||||
r = alloc(ptr_vector<func_decl>);
|
||||
m_asts.push_back(ty);
|
||||
m_vectors.push_back(r);
|
||||
|
@ -819,7 +820,7 @@ ptr_vector<func_decl> const * datatype_util::get_datatype_constructors(sort * ty
|
|||
m_asts.push_back(c);
|
||||
r->push_back(c);
|
||||
}
|
||||
return r;
|
||||
return *r;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -854,12 +855,12 @@ func_decl * datatype_util::get_non_rec_constructor_core(sort * ty, ptr_vector<so
|
|||
// 1) T_i's are not recursive
|
||||
// If there is no such constructor, then we select one that
|
||||
// 2) each type T_i is not recursive or contains a constructor that does not depend on T
|
||||
ptr_vector<func_decl> const * constructors = get_datatype_constructors(ty);
|
||||
ptr_vector<func_decl> const & constructors = get_datatype_constructors(ty);
|
||||
// step 1)
|
||||
unsigned sz = constructors->size();
|
||||
unsigned sz = constructors.size();
|
||||
++m_start;
|
||||
for (unsigned j = 0; j < sz; ++j) {
|
||||
func_decl * c = (*constructors)[(j + m_start) % sz];
|
||||
func_decl * c = constructors[(j + m_start) % sz];
|
||||
unsigned num_args = c->get_arity();
|
||||
unsigned i = 0;
|
||||
for (; i < num_args; i++) {
|
||||
|
@ -872,7 +873,7 @@ func_decl * datatype_util::get_non_rec_constructor_core(sort * ty, ptr_vector<so
|
|||
}
|
||||
// step 2)
|
||||
for (unsigned j = 0; j < sz; ++j) {
|
||||
func_decl * c = (*constructors)[(j + m_start) % sz];
|
||||
func_decl * c = constructors[(j + m_start) % sz];
|
||||
TRACE("datatype_util_bug", tout << "non_rec_constructor c: " << c->get_name() << "\n";);
|
||||
unsigned num_args = c->get_arity();
|
||||
unsigned i = 0;
|
||||
|
@ -915,11 +916,11 @@ func_decl * datatype_util::get_constructor_recognizer(func_decl * constructor) {
|
|||
return d;
|
||||
}
|
||||
|
||||
ptr_vector<func_decl> const * datatype_util::get_constructor_accessors(func_decl * constructor) {
|
||||
ptr_vector<func_decl> const & datatype_util::get_constructor_accessors(func_decl * constructor) {
|
||||
SASSERT(is_constructor(constructor));
|
||||
ptr_vector<func_decl> * res = 0;
|
||||
if (m_constructor2accessors.find(constructor, res))
|
||||
return res;
|
||||
return *res;
|
||||
res = alloc(ptr_vector<func_decl>);
|
||||
m_asts.push_back(constructor);
|
||||
m_vectors.push_back(res);
|
||||
|
@ -938,7 +939,7 @@ ptr_vector<func_decl> const * datatype_util::get_constructor_accessors(func_decl
|
|||
m_asts.push_back(d);
|
||||
res->push_back(d);
|
||||
}
|
||||
return res;
|
||||
return *res;
|
||||
}
|
||||
|
||||
func_decl * datatype_util::get_accessor_constructor(func_decl * accessor) {
|
||||
|
@ -988,7 +989,7 @@ bool datatype_util::is_enum_sort(sort* s) {
|
|||
bool r = false;
|
||||
if (m_is_enum.find(s, r))
|
||||
return r;
|
||||
ptr_vector<func_decl> const& cnstrs = *get_datatype_constructors(s);
|
||||
ptr_vector<func_decl> const& cnstrs = get_datatype_constructors(s);
|
||||
r = true;
|
||||
for (unsigned i = 0; r && i < cnstrs.size(); ++i) {
|
||||
r = cnstrs[i]->get_arity() == 0;
|
||||
|
@ -1048,14 +1049,14 @@ void datatype_util::display_datatype(sort *s0, std::ostream& strm) {
|
|||
todo.pop_back();
|
||||
strm << s->get_name() << " =\n";
|
||||
|
||||
ptr_vector<func_decl> const * cnstrs = get_datatype_constructors(s);
|
||||
for (unsigned i = 0; i < cnstrs->size(); ++i) {
|
||||
func_decl* cns = (*cnstrs)[i];
|
||||
ptr_vector<func_decl> const & cnstrs = get_datatype_constructors(s);
|
||||
for (unsigned i = 0; i < cnstrs.size(); ++i) {
|
||||
func_decl* cns = cnstrs[i];
|
||||
func_decl* rec = get_constructor_recognizer(cns);
|
||||
strm << " " << cns->get_name() << " :: " << rec->get_name() << " :: ";
|
||||
ptr_vector<func_decl> const * accs = get_constructor_accessors(cns);
|
||||
for (unsigned j = 0; j < accs->size(); ++j) {
|
||||
func_decl* acc = (*accs)[j];
|
||||
ptr_vector<func_decl> const & accs = get_constructor_accessors(cns);
|
||||
for (unsigned j = 0; j < accs.size(); ++j) {
|
||||
func_decl* acc = accs[j];
|
||||
sort* s1 = acc->get_range();
|
||||
strm << "(" << acc->get_name() << ": " << s1->get_name() << ") ";
|
||||
if (is_datatype(s1) && are_siblings(s1, s0) && !mark.is_marked(s1)) {
|
||||
|
@ -1090,3 +1091,5 @@ bool datatype_util::is_constructor_of(unsigned num_params, parameter const* para
|
|||
params[1] == f->get_parameter(1);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,9 +16,15 @@ Author:
|
|||
Revision History:
|
||||
|
||||
--*/
|
||||
// define DATATYPE_V2
|
||||
#ifdef DATATYPE_V2
|
||||
#include "ast/datatype_decl_plugin2.h"
|
||||
#else
|
||||
|
||||
#ifndef DATATYPE_DECL_PLUGIN_H_
|
||||
#define DATATYPE_DECL_PLUGIN_H_
|
||||
|
||||
|
||||
#include "ast/ast.h"
|
||||
#include "util/tptr.h"
|
||||
#include "util/buffer.h"
|
||||
|
@ -210,7 +216,7 @@ public:
|
|||
bool is_recognizer(app * f) const { return is_app_of(f, m_family_id, OP_DT_RECOGNISER); }
|
||||
bool is_accessor(app * f) const { return is_app_of(f, m_family_id, OP_DT_ACCESSOR); }
|
||||
bool is_update_field(app * f) const { return is_app_of(f, m_family_id, OP_DT_UPDATE_FIELD); }
|
||||
ptr_vector<func_decl> const * get_datatype_constructors(sort * ty);
|
||||
ptr_vector<func_decl> const & get_datatype_constructors(sort * ty);
|
||||
unsigned get_datatype_num_constructors(sort * ty) {
|
||||
SASSERT(is_datatype(ty));
|
||||
unsigned tid = ty->get_parameter(1).get_int();
|
||||
|
@ -230,7 +236,7 @@ public:
|
|||
unsigned get_recognizer_constructor_idx(func_decl * f) const { SASSERT(is_recognizer(f)); return f->get_parameter(1).get_int(); }
|
||||
func_decl * get_non_rec_constructor(sort * ty);
|
||||
func_decl * get_constructor_recognizer(func_decl * constructor);
|
||||
ptr_vector<func_decl> const * get_constructor_accessors(func_decl * constructor);
|
||||
ptr_vector<func_decl> const & get_constructor_accessors(func_decl * constructor);
|
||||
func_decl * get_accessor_constructor(func_decl * accessor);
|
||||
func_decl * get_recognizer_constructor(func_decl * recognizer);
|
||||
family_id get_family_id() const { return m_family_id; }
|
||||
|
@ -245,3 +251,4 @@ public:
|
|||
|
||||
#endif /* DATATYPE_DECL_PLUGIN_H_ */
|
||||
|
||||
#endif /* DATATYPE_V2 */
|
||||
|
|
|
@ -22,6 +22,7 @@ Revision History:
|
|||
#include "ast/ast_smt2_pp.h"
|
||||
|
||||
|
||||
#ifdef DATATYPE_V2
|
||||
namespace datatype {
|
||||
|
||||
void accessor::fix_range(sort_ref_vector const& dts) {
|
||||
|
@ -49,6 +50,10 @@ namespace datatype {
|
|||
def const& accessor::get_def() const { return m_constructor->get_def(); }
|
||||
util& accessor::u() const { return m_constructor->u(); }
|
||||
|
||||
constructor::~constructor() {
|
||||
for (accessor* a : m_accessors) dealloc(a);
|
||||
m_accessors.reset();
|
||||
}
|
||||
util& constructor::u() const { return m_def->u(); }
|
||||
|
||||
func_decl_ref constructor::instantiate(sort_ref_vector const& ps) const {
|
||||
|
@ -164,7 +169,7 @@ namespace datatype {
|
|||
sort* r = to_sort(parameters[i].get_ast());
|
||||
S.insert(d->params()[i], r->get_num_elements());
|
||||
}
|
||||
sort_size ts = d->sort_size()->fold(S);
|
||||
sort_size ts = d->sort_size()->eval(S);
|
||||
s->set_num_elements(ts);
|
||||
}
|
||||
return s;
|
||||
|
@ -278,7 +283,9 @@ namespace datatype {
|
|||
|
||||
def& plugin::add(symbol const& name, unsigned n, sort * const * params) {
|
||||
ast_manager& m = *m_manager;
|
||||
def* d = alloc(def, m, u(), name, m_class_id, n, params);
|
||||
def* d = 0;
|
||||
if (m_defs.find(name, d)) dealloc(d);
|
||||
d = alloc(def, m, u(), name, m_class_id, n, params);
|
||||
m_defs.insert(name, d);
|
||||
m_def_block.push_back(name);
|
||||
return *d;
|
||||
|
@ -895,3 +902,4 @@ namespace datatype {
|
|||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -24,17 +24,12 @@ Revision History:
|
|||
#include "util/symbol_table.h"
|
||||
#include "util/obj_hashtable.h"
|
||||
|
||||
namespace datatype {
|
||||
|
||||
class util;
|
||||
class def;
|
||||
class accessor;
|
||||
class constructor;
|
||||
#ifdef DATATYPE_V2
|
||||
|
||||
enum sort_kind {
|
||||
DATATYPE_SORT
|
||||
};
|
||||
|
||||
|
||||
enum op_kind {
|
||||
OP_DT_CONSTRUCTOR,
|
||||
OP_DT_RECOGNISER,
|
||||
|
@ -43,6 +38,14 @@ namespace datatype {
|
|||
LAST_DT_OP
|
||||
};
|
||||
|
||||
namespace datatype {
|
||||
|
||||
class util;
|
||||
class def;
|
||||
class accessor;
|
||||
class constructor;
|
||||
|
||||
|
||||
class accessor {
|
||||
symbol m_name;
|
||||
sort* m_range;
|
||||
|
@ -109,7 +112,7 @@ namespace datatype {
|
|||
static size* mk_power(size* a1, size* a2);
|
||||
|
||||
virtual size* subst(obj_map<sort, size*>& S) = 0;
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) = 0;
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) = 0;
|
||||
|
||||
};
|
||||
struct offset : public size {
|
||||
|
@ -117,16 +120,16 @@ namespace datatype {
|
|||
offset(sort_size const& s): m_offset(s) {}
|
||||
virtual ~offset() {}
|
||||
virtual size* subst(obj_map<sort,size*>& S) { return this; }
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) { return m_offset; }
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) { return m_offset; }
|
||||
};
|
||||
struct plus : public size {
|
||||
size* m_arg1, *m_arg2;
|
||||
plus(size* a1, size* a2): m_arg1(a1), m_arg2(a2) { a1->inc_ref(); a2->inc_ref();}
|
||||
virtual ~plus() { m_arg1->dec_ref(); m_arg2->dec_ref(); }
|
||||
virtual size* subst(obj_map<sort,size*>& S) { return mk_plus(m_arg1->subst(S), m_arg2->subst(S)); }
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->fold(S);
|
||||
sort_size s2 = m_arg2->fold(S);
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->eval(S);
|
||||
sort_size s2 = m_arg2->eval(S);
|
||||
if (s1.is_infinite()) return s1;
|
||||
if (s2.is_infinite()) return s2;
|
||||
if (s1.is_very_big()) return s1;
|
||||
|
@ -140,9 +143,9 @@ namespace datatype {
|
|||
times(size* a1, size* a2): m_arg1(a1), m_arg2(a2) { a1->inc_ref(); a2->inc_ref(); }
|
||||
virtual ~times() { m_arg1->dec_ref(); m_arg2->dec_ref(); }
|
||||
virtual size* subst(obj_map<sort,size*>& S) { return mk_times(m_arg1->subst(S), m_arg2->subst(S)); }
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->fold(S);
|
||||
sort_size s2 = m_arg2->fold(S);
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->eval(S);
|
||||
sort_size s2 = m_arg2->eval(S);
|
||||
if (s1.is_infinite()) return s1;
|
||||
if (s2.is_infinite()) return s2;
|
||||
if (s1.is_very_big()) return s1;
|
||||
|
@ -156,9 +159,9 @@ namespace datatype {
|
|||
power(size* a1, size* a2): m_arg1(a1), m_arg2(a2) { a1->inc_ref(); a2->inc_ref(); }
|
||||
virtual ~power() { m_arg1->dec_ref(); m_arg2->dec_ref(); }
|
||||
virtual size* subst(obj_map<sort,size*>& S) { return mk_power(m_arg1->subst(S), m_arg2->subst(S)); }
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->fold(S);
|
||||
sort_size s2 = m_arg2->fold(S);
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) {
|
||||
sort_size s1 = m_arg1->eval(S);
|
||||
sort_size s2 = m_arg2->eval(S);
|
||||
// s1^s2
|
||||
if (s1.is_infinite()) return s1;
|
||||
if (s2.is_infinite()) return s2;
|
||||
|
@ -176,7 +179,7 @@ namespace datatype {
|
|||
sparam(sort_ref& p): m_param(p) {}
|
||||
virtual ~sparam() {}
|
||||
virtual size* subst(obj_map<sort,size*>& S) { return S[m_param]; }
|
||||
virtual sort_size fold(obj_map<sort, sort_size> const& S) { return S[m_param]; }
|
||||
virtual sort_size eval(obj_map<sort, sort_size> const& S) { return S[m_param]; }
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -201,6 +204,8 @@ namespace datatype {
|
|||
{}
|
||||
~def() {
|
||||
if (m_sort_size) m_sort_size->dec_ref();
|
||||
for (constructor* c : m_constructors) dealloc(c);
|
||||
m_constructors.reset();
|
||||
}
|
||||
void add(constructor* c) {
|
||||
m_constructors.push_back(c);
|
||||
|
@ -361,5 +366,36 @@ namespace datatype {
|
|||
|
||||
};
|
||||
|
||||
#ifdef DATATYPE_V2
|
||||
typedef datatype::accessor accessor_decl;
|
||||
typedef datatype::constructor constructor_decl;
|
||||
typedef datatype::def datatype_decl;
|
||||
typedef datatype::decl::plugin datatype_decl_plugin;
|
||||
typedef datatype::util datatype_util;
|
||||
|
||||
class type_ref {
|
||||
void * m_data;
|
||||
public:
|
||||
type_ref():m_data(TAG(void *, static_cast<void*>(0), 1)) {}
|
||||
type_ref(int idx):m_data(BOXINT(void *, idx)) {}
|
||||
type_ref(sort * s):m_data(TAG(void *, s, 1)) {}
|
||||
|
||||
bool is_idx() const { return GET_TAG(m_data) == 0; }
|
||||
bool is_sort() const { return GET_TAG(m_data) == 1; }
|
||||
sort * get_sort() const { return UNTAG(sort *, m_data); }
|
||||
int get_idx() const { return UNBOXINT(m_data); }
|
||||
};
|
||||
|
||||
inline accessor_decl * mk_accessor_decl(symbol const & n, type_ref const & t) {
|
||||
if (t.is_idx()) {
|
||||
return alloc(accessor_decl, n, t.get_idx());
|
||||
}
|
||||
else {
|
||||
return alloc(accessor_decl, n, t.get_sort());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* DATATYPE_DECL_PLUGIN_H_ */
|
||||
|
||||
#endif DATATYPE_V2
|
||||
|
|
|
@ -28,9 +28,9 @@ void decl_collector::visit_sort(sort * n) {
|
|||
|
||||
unsigned num_cnstr = m_dt_util.get_datatype_num_constructors(n);
|
||||
for (unsigned i = 0; i < num_cnstr; i++) {
|
||||
func_decl * cnstr = m_dt_util.get_datatype_constructors(n)->get(i);
|
||||
func_decl * cnstr = m_dt_util.get_datatype_constructors(n).get(i);
|
||||
m_decls.push_back(cnstr);
|
||||
ptr_vector<func_decl> const & cnstr_acc = *m_dt_util.get_constructor_accessors(cnstr);
|
||||
ptr_vector<func_decl> const & cnstr_acc = m_dt_util.get_constructor_accessors(cnstr);
|
||||
unsigned num_cas = cnstr_acc.size();
|
||||
for (unsigned j = 0; j < num_cas; j++) {
|
||||
func_decl * accsr = cnstr_acc.get(j);
|
||||
|
|
|
@ -47,11 +47,11 @@ br_status datatype_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr
|
|||
func_decl * c_decl = a->get_decl();
|
||||
if (c_decl != m_util.get_accessor_constructor(f))
|
||||
return BR_FAILED;
|
||||
ptr_vector<func_decl> const * acc = m_util.get_constructor_accessors(c_decl);
|
||||
SASSERT(acc && acc->size() == a->get_num_args());
|
||||
unsigned num = acc->size();
|
||||
ptr_vector<func_decl> const & acc = m_util.get_constructor_accessors(c_decl);
|
||||
SASSERT(acc.size() == a->get_num_args());
|
||||
unsigned num = acc.size();
|
||||
for (unsigned i = 0; i < num; ++i) {
|
||||
if (f == (*acc)[i]) {
|
||||
if (f == acc[i]) {
|
||||
// found it.
|
||||
result = a->get_arg(i);
|
||||
return BR_DONE;
|
||||
|
@ -70,13 +70,13 @@ br_status datatype_rewriter::mk_app_core(func_decl * f, unsigned num_args, expr
|
|||
result = a;
|
||||
return BR_DONE;
|
||||
}
|
||||
ptr_vector<func_decl> const * acc = m_util.get_constructor_accessors(c_decl);
|
||||
SASSERT(acc && acc->size() == a->get_num_args());
|
||||
unsigned num = acc->size();
|
||||
ptr_vector<func_decl> const & acc = m_util.get_constructor_accessors(c_decl);
|
||||
SASSERT(acc.size() == a->get_num_args());
|
||||
unsigned num = acc.size();
|
||||
ptr_buffer<expr> new_args;
|
||||
for (unsigned i = 0; i < num; ++i) {
|
||||
|
||||
if (f == (*acc)[i]) {
|
||||
if (f == acc[i]) {
|
||||
new_args.push_back(args[1]);
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -130,7 +130,7 @@ struct enum2bv_rewriter::imp {
|
|||
m_imp.m_bounds.push_back(m_bv.mk_ule(result, m_bv.mk_numeral(nc-1, bv_size)));
|
||||
}
|
||||
expr_ref f_def(m);
|
||||
ptr_vector<func_decl> const& cs = *m_dt.get_datatype_constructors(s);
|
||||
ptr_vector<func_decl> const& cs = m_dt.get_datatype_constructors(s);
|
||||
f_def = m.mk_const(cs[nc-1]);
|
||||
for (unsigned i = nc - 1; i > 0; ) {
|
||||
--i;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue