/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Guido Tack */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include namespace MiniZinc { ASTString op_to_builtin(Expression* op_lhs, Expression* op_rhs, BinOpType bot) { std::string builtin; if (op_rhs->type().isint()) { switch (bot) { case BOT_PLUS: return constants().ids.int_.plus; case BOT_MINUS: return constants().ids.int_.minus; case BOT_MULT: return constants().ids.int_.times; case BOT_POW: return constants().ids.pow; case BOT_IDIV: return constants().ids.int_.div; case BOT_MOD: return constants().ids.int_.mod; case BOT_LE: return constants().ids.int_.lt; case BOT_LQ: return constants().ids.int_.le; case BOT_GR: return constants().ids.int_.gt; case BOT_GQ: return constants().ids.int_.ge; case BOT_EQ: return constants().ids.int_.eq; case BOT_NQ: return constants().ids.int_.ne; default: throw InternalError("not yet implemented"); } } else if (op_rhs->type().isbool()) { if (bot == BOT_EQ || bot == BOT_EQUIV) { return constants().ids.bool_eq; } builtin = "bool_"; } else if (op_rhs->type().isSet()) { builtin = "set_"; } else if (op_rhs->type().isfloat()) { switch (bot) { case BOT_PLUS: return constants().ids.float_.plus; case BOT_MINUS: return constants().ids.float_.minus; case BOT_MULT: return constants().ids.float_.times; case BOT_POW: return constants().ids.pow; case BOT_DIV: return constants().ids.float_.div; case BOT_MOD: return constants().ids.float_.mod; case BOT_LE: return constants().ids.float_.lt; case BOT_LQ: return constants().ids.float_.le; case BOT_GR: return constants().ids.float_.gt; case BOT_GQ: return constants().ids.float_.ge; case BOT_EQ: return constants().ids.float_.eq; case BOT_NQ: return constants().ids.float_.ne; default: throw InternalError("not yet implemented"); } } else if (op_rhs->type().isOpt() && (bot == BOT_EQUIV || bot == BOT_EQ)) { /// TODO: extend to all option type operators switch (op_lhs->type().bt()) { case Type::BT_BOOL: return constants().ids.bool_eq; case Type::BT_FLOAT: return constants().ids.float_.eq; case Type::BT_INT: if (op_lhs->type().st() == Type::ST_PLAIN) { return constants().ids.int_.eq; } else { return constants().ids.set_eq; } default: throw InternalError("not yet implemented"); } } else { throw InternalError("Operator not yet implemented"); } switch (bot) { case BOT_PLUS: return builtin + "plus"; case BOT_MINUS: return builtin + "minus"; case BOT_MULT: return builtin + "times"; case BOT_DIV: case BOT_IDIV: return builtin + "div"; case BOT_MOD: return builtin + "mod"; case BOT_LE: return builtin + "lt"; case BOT_LQ: return builtin + "le"; case BOT_GR: return builtin + "gt"; case BOT_GQ: return builtin + "ge"; case BOT_EQ: return builtin + "eq"; case BOT_NQ: return builtin + "ne"; case BOT_IN: return constants().ids.set_in; case BOT_SUBSET: return builtin + "subset"; case BOT_SUPERSET: return builtin + "superset"; case BOT_UNION: return builtin + "union"; case BOT_DIFF: return builtin + "diff"; case BOT_SYMDIFF: return builtin + "symdiff"; case BOT_INTERSECT: return builtin + "intersect"; case BOT_PLUSPLUS: case BOT_DOTDOT: throw InternalError("not yet implemented"); case BOT_EQUIV: return builtin + "eq"; case BOT_IMPL: return builtin + "le"; case BOT_RIMPL: return builtin + "ge"; case BOT_OR: return builtin + "or"; case BOT_AND: return builtin + "and"; case BOT_XOR: return constants().ids.bool_xor; default: assert(false); return ASTString(); } } ASTString op_to_id(BinOpType bot) { switch (bot) { case BOT_PLUS: return ASTString("'+'"); case BOT_MINUS: return ASTString("'-'"); case BOT_MULT: return ASTString("'*'"); case BOT_DIV: return ASTString("'/'"); case BOT_IDIV: return ASTString("'div'"); case BOT_MOD: return ASTString("'mod'"); case BOT_LE: return ASTString("'<'"); case BOT_LQ: return ASTString("'<='"); case BOT_GR: return ASTString("'>'"); case BOT_GQ: return ASTString("'>='"); case BOT_EQ: return ASTString("'='"); case BOT_NQ: return ASTString("'!='"); case BOT_IN: return ASTString("'in'"); case BOT_SUBSET: return ASTString("'subset'"); case BOT_SUPERSET: return ASTString("'superset'"); case BOT_UNION: return ASTString("'union'"); case BOT_DIFF: return ASTString("'diff'"); case BOT_SYMDIFF: return ASTString("'symdiff'"); case BOT_INTERSECT: return ASTString("'intersect'"); case BOT_PLUSPLUS: return ASTString("'++'"); case BOT_DOTDOT: return ASTString("'..'"); case BOT_EQUIV: return ASTString("'<->'"); case BOT_IMPL: return ASTString("'->'"); case BOT_RIMPL: return ASTString("'<-'"); case BOT_OR: return ASTString("'\\/'"); case BOT_AND: return ASTString("'/\\'"); case BOT_XOR: return ASTString("'xor'"); default: assert(false); return ASTString(""); } } bool is_reverse_map(BinOp* e) { return e->ann().contains(constants().ann.is_reverse_map); } template void collect_linexps(EnvI& env, typename LinearTraits::Val in_c, Expression* exp, std::vector::Val>& coeffs, std::vector& vars, typename LinearTraits::Val& constval) { typedef typename LinearTraits::Val Val; struct StackItem { Expression* e; Val c; StackItem(Expression* e0, Val c0) : e(e0), c(c0) {} }; std::vector stack; stack.push_back(StackItem(exp, in_c)); while (!stack.empty()) { Expression* e = stack.back().e; Val c = stack.back().c; stack.pop_back(); if (e == nullptr) { continue; } if (e->type().isPar()) { constval += c * LinearTraits::eval(env, e); } else if (Lit* l = e->dynamicCast()) { constval += c * l->v(); } else if (auto* bo = e->dynamicCast()) { switch (bo->op()) { case BOT_PLUS: stack.push_back(StackItem(bo->lhs(), c)); stack.push_back(StackItem(bo->rhs(), c)); break; case BOT_MINUS: stack.push_back(StackItem(bo->lhs(), c)); stack.push_back(StackItem(bo->rhs(), -c)); break; case BOT_MULT: if (bo->lhs()->type().isPar()) { stack.push_back(StackItem(bo->rhs(), c * LinearTraits::eval(env, bo->lhs()))); } else if (bo->rhs()->type().isPar()) { stack.push_back(StackItem(bo->lhs(), c * LinearTraits::eval(env, bo->rhs()))); } else { coeffs.push_back(c); vars.emplace_back(e); } break; case BOT_DIV: if (bo->rhs()->isa() && bo->rhs()->cast()->v() == 1.0) { stack.push_back(StackItem(bo->lhs(), c)); } else { coeffs.push_back(c); vars.emplace_back(e); } break; case BOT_IDIV: if (bo->rhs()->isa() && bo->rhs()->cast()->v() == 1) { stack.push_back(StackItem(bo->lhs(), c)); } else { coeffs.push_back(c); vars.emplace_back(e); } break; default: coeffs.push_back(c); vars.emplace_back(e); break; } // } else if (Call* call = e->dynamicCast()) { // /// TODO! Handle sum, lin_exp (maybe not that important?) } else { coeffs.push_back(c); vars.emplace_back(e); } } } template KeepAlive mklinexp(EnvI& env, typename LinearTraits::Val c0, typename LinearTraits::Val c1, Expression* e0, Expression* e1) { typedef typename LinearTraits::Val Val; GCLock lock; std::vector coeffs; std::vector vars; Val constval = 0; collect_linexps(env, c0, e0, coeffs, vars, constval); collect_linexps(env, c1, e1, coeffs, vars, constval); simplify_lin(coeffs, vars, constval); KeepAlive ka; if (coeffs.empty()) { ka = LinearTraits::newLit(constval); } else if (coeffs.size() == 1 && coeffs[0] == 1 && constval == 0) { ka = vars[0]; } else { std::vector coeffs_e(coeffs.size()); for (auto i = static_cast(coeffs.size()); i--;) { if (!LinearTraits::finite(coeffs[i])) { throw FlatteningError( env, e0->loc(), "unbounded coefficient in linear expression." " Make sure variables involved in non-linear/logical expressions have finite bounds" " in their definition or via constraints"); } coeffs_e[i] = LinearTraits::newLit(coeffs[i]); } std::vector vars_e(vars.size()); for (auto i = static_cast(vars.size()); i--;) { vars_e[i] = vars[i](); } std::vector args(3); args[0] = new ArrayLit(e0->loc(), coeffs_e); Type t = coeffs_e[0]->type(); t.dim(1); args[0]->type(t); args[1] = new ArrayLit(e0->loc(), vars_e); Type tt = vars_e[0]->type(); tt.dim(1); args[1]->type(tt); args[2] = LinearTraits::newLit(constval); Call* c = new Call(e0->loc().introduce(), constants().ids.lin_exp, args); add_path_annotation(env, c); tt = args[1]->type(); tt.dim(0); c->decl(env.model->matchFn(env, c, false)); if (c->decl() == nullptr) { throw FlatteningError(env, c->loc(), "cannot find matching declaration"); } c->type(c->decl()->rtype(env, args, false)); ka = c; } assert(ka()); return ka; } Call* aggregate_and_or_ops(EnvI& env, BinOp* bo, bool negateArgs, BinOpType bot) { assert(bot == BOT_AND || bot == BOT_OR); BinOpType negbot = (bot == BOT_AND ? BOT_OR : BOT_AND); typedef std::pair arg_literal; typedef std::list arg_literal_l; arg_literal_l bo_args({arg_literal(bo->lhs(), !negateArgs), arg_literal(bo->rhs(), !negateArgs)}); std::vector output_pos; std::vector output_neg; auto i = bo_args.begin(); while (i != bo_args.end()) { auto* bo_arg = i->first->dynamicCast(); UnOp* uo_arg = i->first->dynamicCast(); bool positive = i->second; if ((bo_arg != nullptr) && positive && bo_arg->op() == bot) { i->first = bo_arg->lhs(); i++; bo_args.insert(i, arg_literal(bo_arg->rhs(), true)); i--; i--; } else if ((bo_arg != nullptr) && !positive && bo_arg->op() == negbot) { i->first = bo_arg->lhs(); i++; bo_args.insert(i, arg_literal(bo_arg->rhs(), false)); i--; i--; } else if ((uo_arg != nullptr) && !positive && uo_arg->op() == UOT_NOT) { i->first = uo_arg->e(); i->second = true; } else if (bot == BOT_OR && (uo_arg != nullptr) && positive && uo_arg->op() == UOT_NOT) { output_neg.push_back(uo_arg->e()); i++; } else { if (positive) { output_pos.push_back(i->first); } else { output_neg.push_back(i->first); } i++; } } Call* c; std::vector c_args(1); if (bot == BOT_AND) { for (auto& i : output_neg) { UnOp* neg_arg = new UnOp(i->loc(), UOT_NOT, i); neg_arg->type(i->type()); output_pos.push_back(neg_arg); } auto* al = new ArrayLit(bo->loc().introduce(), output_pos); Type al_t = bo->type(); al_t.dim(1); al->type(al_t); env.annotateFromCallStack(al); c_args[0] = al; c = new Call(bo->loc().introduce(), bot == BOT_AND ? constants().ids.forall : constants().ids.exists, c_args); } else { auto* al_pos = new ArrayLit(bo->loc().introduce(), output_pos); Type al_t = bo->type(); al_t.dim(1); al_pos->type(al_t); env.annotateFromCallStack(al_pos); c_args[0] = al_pos; if (!output_neg.empty()) { auto* al_neg = new ArrayLit(bo->loc().introduce(), output_neg); al_neg->type(al_t); env.annotateFromCallStack(al_neg); c_args.push_back(al_neg); } c = new Call(bo->loc().introduce(), output_neg.empty() ? constants().ids.exists : constants().ids.clause, c_args); } c->decl(env.model->matchFn(env, c, false)); assert(c->decl()); Type t = c->decl()->rtype(env, c_args, false); t.cv(bo->type().cv()); c->type(t); return c; } /// Return a lin_exp or id if \a e is a lin_exp or id template Expression* get_linexp(Expression* e) { for (;;) { if (e && e->eid() == Expression::E_ID && e != constants().absent) { if (e->cast()->decl()->e()) { e = e->cast()->decl()->e(); } else { break; } } else { break; } } if (e && (e->isa() || e->isa() || (e->isa() && e->cast()->id() == constants().ids.lin_exp))) { return e; } return nullptr; } template void flatten_linexp_binop(EnvI& env, const Ctx& ctx, VarDecl* r, VarDecl* b, EE& ret, Expression* le0, Expression* le1, BinOpType& bot, bool doubleNeg, std::vector& ees, std::vector& args, ASTString& callid) { typedef typename LinearTraits::Val Val; std::vector coeffv; std::vector alv; Val d = 0; Expression* le[2] = {le0, le1}; // Assign linear expression directly if one side is an Id. Id* assignTo = nullptr; if (bot == BOT_EQ && ctx.b == C_ROOT) { if (le0->isa()) { assignTo = le0->cast(); } else if (le1->isa()) { assignTo = le1->cast(); } } for (unsigned int i = 0; i < 2; i++) { Val sign = (i == 0 ? 1 : -1); if (Lit* l = le[i]->dynamicCast()) { try { d += sign * l->v(); } catch (ArithmeticError& e) { throw EvalError(env, l->loc(), e.msg()); } } else if (le[i]->isa()) { coeffv.push_back(sign); alv.emplace_back(le[i]); } else if (Call* sc = le[i]->dynamicCast()) { GCLock lock; ArrayLit* sc_coeff = eval_array_lit(env, sc->arg(0)); ArrayLit* sc_al = eval_array_lit(env, sc->arg(1)); try { d += sign * LinearTraits::eval(env, sc->arg(2)); for (unsigned int j = 0; j < sc_coeff->size(); j++) { coeffv.push_back(sign * LinearTraits::eval(env, (*sc_coeff)[j])); alv.emplace_back((*sc_al)[j]); } } catch (ArithmeticError& e) { throw EvalError(env, sc->loc(), e.msg()); } } else { throw EvalError(env, le[i]->loc(), "Internal error, unexpected expression inside linear expression"); } } simplify_lin(coeffv, alv, d); if (coeffv.empty()) { bool result; switch (bot) { case BOT_LE: result = (0 < -d); break; case BOT_LQ: result = (0 <= -d); break; case BOT_GR: result = (0 > -d); break; case BOT_GQ: result = (0 >= -d); break; case BOT_EQ: result = (0 == -d); break; case BOT_NQ: result = (0 != -d); break; default: assert(false); break; } if (doubleNeg) { result = !result; } ees[2].b = constants().boollit(result); ret.r = conj(env, r, ctx, ees); return; } if (coeffv.size() == 1 && std::abs(coeffv[0]) == 1) { if (coeffv[0] == -1) { switch (bot) { case BOT_LE: bot = BOT_GR; break; case BOT_LQ: bot = BOT_GQ; break; case BOT_GR: bot = BOT_LE; break; case BOT_GQ: bot = BOT_LQ; break; default: break; } } else { d = -d; } typename LinearTraits::Bounds ib = LinearTraits::computeBounds(env, alv[0]()); if (ib.valid) { bool failed = false; bool subsumed = false; switch (bot) { case BOT_LE: subsumed = ib.u < d; failed = ib.l >= d; break; case BOT_LQ: subsumed = ib.u <= d; failed = ib.l > d; break; case BOT_GR: subsumed = ib.l > d; failed = ib.u <= d; break; case BOT_GQ: subsumed = ib.l >= d; failed = ib.u < d; break; case BOT_EQ: subsumed = ib.l == d && ib.u == d; failed = ib.u < d || ib.l > d; break; case BOT_NQ: subsumed = ib.u < d || ib.l > d; failed = ib.l == d && ib.u == d; break; default: break; } if (doubleNeg) { std::swap(subsumed, failed); } if (subsumed) { ees[2].b = constants().literalTrue; ret.r = conj(env, r, ctx, ees); return; } if (failed) { ees[2].b = constants().literalFalse; ret.r = conj(env, r, ctx, ees); return; } } if (ctx.b == C_ROOT && alv[0]()->isa() && bot == BOT_EQ) { GCLock lock; VarDecl* vd = alv[0]()->cast()->decl(); if (vd->ti()->domain()) { typename LinearTraits::Domain domain = LinearTraits::evalDomain(env, vd->ti()->domain()); if (LinearTraits::domainContains(domain, d)) { if (!LinearTraits::domainEquals(domain, d)) { set_computed_domain(env, vd, LinearTraits::newDomain(d), false); } ret.r = bind(env, ctx, r, constants().literalTrue); } else { ret.r = bind(env, ctx, r, constants().literalFalse); } } else { set_computed_domain(env, vd, LinearTraits::newDomain(d), false); ret.r = bind(env, ctx, r, constants().literalTrue); } } else { GCLock lock; Expression* e0; Expression* e1; BinOpType old_bot = bot; Val old_d = d; switch (bot) { case BOT_LE: e0 = alv[0](); if (e0->type().isint()) { d--; bot = BOT_LQ; } e1 = LinearTraits::newLit(d); break; case BOT_GR: e1 = alv[0](); if (e1->type().isint()) { d++; bot = BOT_LQ; } else { bot = BOT_LE; } e0 = LinearTraits::newLit(d); break; case BOT_GQ: e0 = LinearTraits::newLit(d); e1 = alv[0](); bot = BOT_LQ; break; default: e0 = alv[0](); e1 = LinearTraits::newLit(d); } if (ctx.b == C_ROOT && alv[0]()->isa() && !env.hasReverseMapper(alv[0]()->cast()) && alv[0]()->cast()->decl()->ti()->domain()) { VarDecl* vd = alv[0]()->cast()->decl(); typename LinearTraits::Domain domain = LinearTraits::evalDomain(env, vd->ti()->domain()); typename LinearTraits::Domain ndomain = LinearTraits::limitDomain(old_bot, domain, old_d); if (domain && ndomain) { if (LinearTraits::domainEmpty(ndomain)) { ret.r = bind(env, ctx, r, constants().literalFalse); return; } if (!LinearTraits::domainEquals(domain, ndomain)) { ret.r = bind(env, ctx, r, constants().literalTrue); set_computed_domain(env, vd, LinearTraits::newDomain(ndomain), false); if (r == constants().varTrue) { auto* bo = new BinOp(Location().introduce(), e0, bot, e1); bo->type(Type::varbool()); std::vector boargs(2); boargs[0] = e0; boargs[1] = e1; Call* c = new Call(Location(), op_to_builtin(e0, e1, bot), boargs); c->type(Type::varbool()); c->decl(env.model->matchFn(env, c, false)); auto it = env.cseMapFind(c); if (it != env.cseMapEnd()) { if (Id* ident = it->second.r()->template dynamicCast()) { bind(env, Ctx(), ident->decl(), constants().literalTrue); it->second.r = constants().literalTrue; } if (Id* ident = it->second.b()->template dynamicCast()) { bind(env, Ctx(), ident->decl(), constants().literalTrue); it->second.b = constants().literalTrue; } } } } return; } } args.emplace_back(e0); args.emplace_back(e1); } } else if (bot == BOT_EQ && coeffv.size() == 2 && coeffv[0] == -coeffv[1] && d == 0) { Id* id0 = alv[0]()->cast(); Id* id1 = alv[1]()->cast(); if (ctx.b == C_ROOT && r == constants().varTrue && (id0->decl()->e() == nullptr || id1->decl()->e() == nullptr)) { if (id0->decl()->e()) { (void)bind(env, ctx, id1->decl(), id0); } else { (void)bind(env, ctx, id0->decl(), id1); } } else { callid = LinearTraits::id_eq(); args.emplace_back(alv[0]()); args.emplace_back(alv[1]()); } } else { GCLock lock; if (assignTo != nullptr) { Val resultCoeff = 0; typename LinearTraits::Bounds bounds(d, d, true); for (auto i = static_cast(coeffv.size()); i--;) { if (alv[i]() == assignTo) { resultCoeff = coeffv[i]; continue; } typename LinearTraits::Bounds bound = LinearTraits::computeBounds(env, alv[i]()); if (bound.valid && LinearTraits::finite(bound)) { if (coeffv[i] > 0) { bounds.l += coeffv[i] * bound.l; bounds.u += coeffv[i] * bound.u; } else { bounds.l += coeffv[i] * bound.u; bounds.u += coeffv[i] * bound.l; } } else { bounds.valid = false; break; } } if (bounds.valid && resultCoeff != 0) { if (resultCoeff < 0) { bounds.l = LinearTraits::floorDiv(bounds.l, -resultCoeff); bounds.u = LinearTraits::ceilDiv(bounds.u, -resultCoeff); } else { Val bl = bounds.l; bounds.l = LinearTraits::ceilDiv(bounds.u, -resultCoeff); bounds.u = LinearTraits::floorDiv(bl, -resultCoeff); } VarDecl* vd = assignTo->decl(); if (vd->ti()->domain()) { typename LinearTraits::Domain domain = LinearTraits::evalDomain(env, vd->ti()->domain()); if (LinearTraits::domainIntersects(domain, bounds.l, bounds.u)) { typename LinearTraits::Domain new_domain = LinearTraits::intersectDomain(domain, bounds.l, bounds.u); if (!LinearTraits::domainEquals(domain, new_domain)) { set_computed_domain(env, vd, LinearTraits::newDomain(new_domain), false); } } else { ret.r = bind(env, ctx, r, constants().literalFalse); } } else { set_computed_domain(env, vd, LinearTraits::newDomain(bounds.l, bounds.u), true); } } } int coeff_sign; LinearTraits::constructLinBuiltin(bot, callid, coeff_sign, d); std::vector coeff_ev(coeffv.size()); for (auto i = static_cast(coeff_ev.size()); i--;) { coeff_ev[i] = LinearTraits::newLit(coeff_sign * coeffv[i]); } auto* ncoeff = new ArrayLit(Location().introduce(), coeff_ev); Type t = coeff_ev[0]->type(); t.dim(1); ncoeff->type(t); args.emplace_back(ncoeff); std::vector alv_e(alv.size()); Type tt = alv[0]()->type(); tt.dim(1); for (auto i = static_cast(alv.size()); i--;) { if (alv[i]()->type().isvar()) { tt.ti(Type::TI_VAR); } alv_e[i] = alv[i](); } auto* nal = new ArrayLit(Location().introduce(), alv_e); nal->type(tt); args.emplace_back(nal); Lit* il = LinearTraits::newLit(-d); args.push_back(il); } } EE flatten_binop(EnvI& env, const Ctx& input_ctx, Expression* e, VarDecl* r, VarDecl* b) { Ctx ctx = input_ctx; CallStackItem _csi(env, e); EE ret; auto* bo = e->cast(); if (is_reverse_map(bo)) { CallArgItem cai(env); Id* id = bo->lhs()->dynamicCast(); if (id == nullptr) { throw EvalError(env, bo->lhs()->loc(), "Reverse mappers are only defined for identifiers"); } if (bo->op() != BOT_EQ && bo->op() != BOT_EQUIV) { throw EvalError(env, bo->loc(), "Reverse mappers have to use `=` as the operator"); } Call* c = bo->rhs()->dynamicCast(); if (c == nullptr) { throw EvalError(env, bo->rhs()->loc(), "Reverse mappers require call on right hand side"); } std::vector args(c->argCount()); for (unsigned int i = 0; i < c->argCount(); i++) { Id* idi = c->arg(i)->dynamicCast(); if (idi == nullptr) { throw EvalError(env, c->arg(i)->loc(), "Reverse mapper calls require identifiers as arguments"); } EE ee = flat_exp(env, Ctx(), idi, nullptr, constants().varTrue); args[i] = ee.r(); } EE ee = flat_exp(env, Ctx(), id, nullptr, constants().varTrue); GCLock lock; Call* revMap = new Call(Location().introduce(), c->id(), args); args.push_back(ee.r()); Call* keepAlive = new Call(Location().introduce(), constants().varRedef->id(), args); keepAlive->type(Type::varbool()); keepAlive->decl(constants().varRedef); ret = flat_exp(env, Ctx(), keepAlive, constants().varTrue, constants().varTrue); if (ee.r()->isa()) { env.reverseMappers.insert(ee.r()->cast(), revMap); } return ret; } if ((bo->op() == BOT_EQ || bo->op() == BOT_EQUIV) && (bo->lhs() == constants().absent || bo->rhs() == constants().absent)) { GCLock lock; std::vector args(1); args[0] = bo->lhs() == constants().absent ? bo->rhs() : bo->lhs(); if (args[0] != constants().absent) { Call* cr = new Call(bo->loc().introduce(), "absent", args); cr->decl(env.model->matchFn(env, cr, false)); cr->type(cr->decl()->rtype(env, args, false)); ret = flat_exp(env, ctx, cr, r, b); } else { ret.b = bind(env, Ctx(), b, constants().literalTrue); ret.r = bind(env, ctx, r, constants().literalTrue); } return ret; } Ctx ctx0 = ctx; ctx0.neg = false; Ctx ctx1 = ctx; ctx1.neg = false; BinOpType bot = bo->op(); if (bo->lhs()->type().isbool()) { switch (bot) { case BOT_EQ: bot = BOT_EQUIV; break; case BOT_NQ: bot = BOT_XOR; break; case BOT_LQ: bot = BOT_IMPL; break; case BOT_GQ: bot = BOT_RIMPL; break; default: break; } } bool negArgs = false; bool doubleNeg = false; if (ctx.neg) { switch (bot) { case BOT_AND: ctx.b = -ctx.b; ctx.neg = false; negArgs = true; bot = BOT_OR; break; case BOT_OR: ctx.b = -ctx.b; ctx.neg = false; negArgs = true; bot = BOT_AND; break; default: break; } } Expression* boe0 = bo->lhs(); Expression* boe1 = bo->rhs(); bool isBuiltin = bo->decl() == nullptr || bo->decl()->e() == nullptr; switch (bot) { case BOT_AND: if (isBuiltin) { if (r == constants().varTrue) { Ctx nctx; nctx.neg = negArgs; nctx.b = negArgs ? C_NEG : C_ROOT; std::vector todo; todo.push_back(boe1); todo.push_back(boe0); while (!todo.empty()) { Expression* e_todo = todo.back(); todo.pop_back(); auto* e_bo = e_todo->dynamicCast(); if ((e_bo != nullptr) && e_bo->op() == (negArgs ? BOT_OR : BOT_AND)) { todo.push_back(e_bo->rhs()); todo.push_back(e_bo->lhs()); } else { (void)flat_exp(env, nctx, e_todo, constants().varTrue, constants().varTrue); } } ret.r = bind(env, ctx, r, constants().literalTrue); break; } GC::lock(); Call* c = aggregate_and_or_ops(env, bo, negArgs, bot); KeepAlive ka(c); GC::unlock(); ret = flat_exp(env, ctx, c, r, b); if (Id* id = ret.r()->dynamicCast()) { add_ctx_ann(id->decl(), ctx.b); } break; } case BOT_OR: if (isBuiltin) { GC::lock(); Call* c = aggregate_and_or_ops(env, bo, negArgs, bot); KeepAlive ka(c); GC::unlock(); ret = flat_exp(env, ctx, c, r, b); if (Id* id = ret.r()->dynamicCast()) { add_ctx_ann(id->decl(), ctx.b); } break; } case BOT_PLUS: if (isBuiltin) { KeepAlive ka; if (boe0->type().isint()) { ka = mklinexp(env, 1, 1, boe0, boe1); } else { ka = mklinexp(env, 1.0, 1.0, boe0, boe1); } ret = flat_exp(env, ctx, ka(), r, b); break; } case BOT_MINUS: if (isBuiltin) { KeepAlive ka; if (boe0->type().isint()) { ka = mklinexp(env, 1, -1, boe0, boe1); } else { ka = mklinexp(env, 1.0, -1.0, boe0, boe1); } ret = flat_exp(env, ctx, ka(), r, b); break; } case BOT_MULT: case BOT_IDIV: case BOT_MOD: case BOT_POW: case BOT_DIV: case BOT_UNION: case BOT_DIFF: case BOT_SYMDIFF: case BOT_INTERSECT: case BOT_DOTDOT: { assert(!ctx0.neg); assert(!ctx1.neg); EE e0 = flat_exp(env, ctx0, boe0, nullptr, b); EE e1 = flat_exp(env, ctx1, boe1, nullptr, b); if (e0.r()->type().isPar() && e1.r()->type().isPar()) { GCLock lock; auto* parbo = new BinOp(bo->loc(), e0.r(), bo->op(), e1.r()); std::vector args(2); args[0] = e0.r(); args[1] = e1.r(); FunctionI* fi = env.model->matchFn(env, bo->opToString(), args, false); parbo->decl(fi); Type tt = fi->rtype(env, {e0.r()->type(), e1.r()->type()}, false); assert(tt.isPar()); parbo->type(tt); try { Expression* res = eval_par(env, parbo); assert(!res->type().isunknown()); ret.r = bind(env, ctx, r, res); std::vector ees(2); ees[0].b = e0.b; ees[1].b = e1.b; ret.b = conj(env, b, Ctx(), ees); } catch (ResultUndefinedError&) { ret.r = create_dummy_value(env, e->type()); ret.b = bind(env, Ctx(), b, constants().literalFalse); } break; } if (isBuiltin && bot == BOT_MULT) { Expression* e0r = e0.r(); Expression* e1r = e1.r(); if (e0r->type().isPar()) { std::swap(e0r, e1r); } if (e1r->type().isPar() && e1r->type().isint()) { IntVal coeff = eval_int(env, e1r); KeepAlive ka = mklinexp(env, coeff, 0, e0r, nullptr); ret = flat_exp(env, ctx, ka(), r, b); break; } if (e1r->type().isPar() && e1r->type().isfloat()) { FloatVal coeff = eval_float(env, e1r); KeepAlive ka = mklinexp(env, coeff, 0.0, e0r, nullptr); ret = flat_exp(env, ctx, ka(), r, b); break; } } else if (isBuiltin && (bot == BOT_DIV || bot == BOT_IDIV)) { Expression* e0r = e0.r(); Expression* e1r = e1.r(); if (e1r->type().isPar() && e1r->type().isint()) { IntVal coeff = eval_int(env, e1r); if (coeff == 1) { ret = flat_exp(env, ctx, e0r, r, b); break; } } else if (e1r->type().isPar() && e1r->type().isfloat()) { FloatVal coeff = eval_float(env, e1r); if (coeff == 1.0) { ret = flat_exp(env, ctx, e0r, r, b); break; } KeepAlive ka = mklinexp(env, 1.0 / coeff, 0.0, e0r, nullptr); ret = flat_exp(env, ctx, ka(), r, b); break; } } GC::lock(); std::vector args(2); args[0] = e0.r(); args[1] = e1.r(); Call* cc; if (!isBuiltin) { cc = new Call(bo->loc().introduce(), bo->opToString(), args); } else { cc = new Call(bo->loc().introduce(), op_to_builtin(args[0], args[1], bot), args); } cc->type(bo->type()); EnvI::CSEMap::iterator cit; if ((cit = env.cseMapFind(cc)) != env.cseMapEnd()) { ret.b = bind(env, Ctx(), b, env.ignorePartial ? constants().literalTrue : cit->second.b()); ret.r = bind(env, ctx, r, cit->second.r()); } else { if (FunctionI* fi = env.model->matchFn(env, cc->id(), args, false)) { assert(cc->type() == fi->rtype(env, args, false)); cc->decl(fi); cc->type(cc->decl()->rtype(env, args, false)); KeepAlive ka(cc); GC::unlock(); EE ee = flat_exp(env, ctx, cc, r, nullptr); GC::lock(); ret.r = ee.r; std::vector ees(3); ees[0].b = e0.b; ees[1].b = e1.b; ees[2].b = ee.b; ret.b = conj(env, b, Ctx(), ees); } else { add_path_annotation(env, cc); ret.r = bind(env, ctx, r, cc); std::vector ees(2); ees[0].b = e0.b; ees[1].b = e1.b; ret.b = conj(env, b, Ctx(), ees); if (!ctx.neg) { env.cseMapInsert(cc, ret); } } } } GC::unlock(); break; case BOT_RIMPL: { std::swap(boe0, boe1); } // fall through case BOT_IMPL: { if (ctx.b == C_ROOT && r == constants().varTrue && boe0->type().isPar()) { bool bval; { GCLock lock; bval = eval_bool(env, boe0); } if (bval) { Ctx nctx = ctx; nctx.neg = negArgs; nctx.b = negArgs ? C_NEG : C_ROOT; ret = flat_exp(env, nctx, boe1, constants().varTrue, constants().varTrue); } else { Ctx nctx = ctx; nctx.neg = negArgs; nctx.b = negArgs ? C_NEG : C_ROOT; ret = flat_exp(env, nctx, constants().literalTrue, constants().varTrue, constants().varTrue); } break; } if (ctx.b == C_ROOT && r == constants().varTrue && boe1->type().isPar()) { bool bval; { GCLock lock; bval = eval_bool(env, boe1); } if (bval) { Ctx nctx = ctx; nctx.neg = negArgs; nctx.b = negArgs ? C_NEG : C_ROOT; ret = flat_exp(env, nctx, constants().literalTrue, constants().varTrue, constants().varTrue); break; } Ctx nctx = ctx; nctx.neg = !negArgs; nctx.b = !negArgs ? C_NEG : C_ROOT; ret = flat_exp(env, nctx, boe0, constants().varTrue, constants().varTrue); break; } GC::lock(); std::vector args; ASTString id; if (ctx.neg) { std::vector bo_args(2); bo_args[0] = boe0; bo_args[1] = new UnOp(bo->loc(), UOT_NOT, boe1); bo_args[1]->type(boe1->type()); id = constants().ids.forall; args.push_back(new ArrayLit(bo->loc(), bo_args)); args[0]->type(Type::varbool(1)); } else { std::vector clause_pos(1); clause_pos[0] = boe1; std::vector clause_neg(1); clause_neg[0] = boe0; args.push_back(new ArrayLit(boe1->loc().introduce(), clause_pos)); Type t0 = boe1->type(); t0.dim(1); args[0]->type(t0); args.push_back(new ArrayLit(boe0->loc().introduce(), clause_neg)); Type t1 = boe0->type(); t1.dim(1); args[1]->type(t1); id = constants().ids.clause; } ctx.neg = false; Call* c = new Call(bo->loc().introduce(), id, args); c->decl(env.model->matchFn(env, c, false)); if (c->decl() == nullptr) { throw FlatteningError(env, c->loc(), "cannot find matching declaration"); } c->type(c->decl()->rtype(env, args, false)); KeepAlive ka(c); GC::unlock(); ret = flat_exp(env, ctx, c, r, b); if (Id* id = ret.r()->dynamicCast()) { add_ctx_ann(id->decl(), ctx.b); } } break; case BOT_EQUIV: if (ctx.neg) { ctx.neg = false; ctx.b = -ctx.b; bot = BOT_XOR; ctx0.b = ctx1.b = C_MIX; goto flatten_bool_op; } if (!boe1->type().isOpt() && istrue(env, boe0)) { return flat_exp(env, ctx, boe1, r, b); } if (!boe0->type().isOpt() && istrue(env, boe1)) { return flat_exp(env, ctx, boe0, r, b); } if (!boe0->type().isOpt() && !boe1->type().isOpt() && (r != nullptr) && r == constants().varTrue) { if (boe1->type().isPar() || boe1->isa()) { std::swap(boe0, boe1); } if (istrue(env, boe0)) { return flat_exp(env, ctx1, boe1, r, b); } if (isfalse(env, boe0)) { ctx1.neg = true; ctx1.b = -ctx1.b; return flat_exp(env, ctx1, boe1, r, b); } ctx0.b = C_MIX; EE e0 = flat_exp(env, ctx0, boe0, nullptr, nullptr); if (istrue(env, e0.r())) { return flat_exp(env, ctx1, boe1, r, b); } if (isfalse(env, e0.r())) { ctx1.neg = true; ctx1.b = -ctx1.b; return flat_exp(env, ctx1, boe1, r, b); } Id* id = e0.r()->cast(); ctx1.b = C_MIX; (void)flat_exp(env, ctx1, boe1, id->decl(), constants().varTrue); add_ctx_ann(id->decl(), ctx1.b); ret.b = bind(env, Ctx(), b, constants().literalTrue); ret.r = bind(env, Ctx(), r, constants().literalTrue); break; } ctx0.b = ctx1.b = C_MIX; goto flatten_bool_op; case BOT_XOR: if (ctx.neg) { ctx.neg = false; ctx.b = -ctx.b; bot = BOT_EQUIV; } ctx0.b = ctx1.b = C_MIX; goto flatten_bool_op; case BOT_LE: if (ctx.neg) { doubleNeg = true; bot = BOT_GQ; if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = +ctx0.b; ctx1.b = -ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = +ctx0.b; ctx1.i = -ctx1.b; } } else { if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = -ctx0.b; ctx1.b = +ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = -ctx0.b; ctx1.i = +ctx1.b; } } goto flatten_bool_op; case BOT_LQ: if (ctx.neg) { doubleNeg = true; bot = BOT_GR; if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = +ctx0.b; ctx1.b = -ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = +ctx0.b; ctx1.i = -ctx1.b; } } else { if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = -ctx0.b; ctx1.b = +ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = -ctx0.b; ctx1.i = +ctx1.b; } } goto flatten_bool_op; case BOT_GR: if (ctx.neg) { doubleNeg = true; bot = BOT_LQ; if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = -ctx0.b; ctx1.b = +ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = -ctx0.b; ctx1.i = +ctx1.b; } } else { if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = +ctx0.b; ctx1.b = -ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = +ctx0.b; ctx1.i = -ctx1.b; } } goto flatten_bool_op; case BOT_GQ: if (ctx.neg) { doubleNeg = true; bot = BOT_LE; if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = -ctx0.b; ctx1.b = +ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = -ctx0.b; ctx1.i = +ctx1.b; } } else { if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = +ctx0.b; ctx1.b = -ctx1.b; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = +ctx0.b; ctx1.i = -ctx1.b; } } goto flatten_bool_op; case BOT_EQ: if (ctx.neg) { doubleNeg = true; bot = BOT_NQ; } if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = ctx1.b = C_MIX; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = ctx1.i = C_MIX; } goto flatten_bool_op; case BOT_NQ: if (ctx.neg) { doubleNeg = true; bot = BOT_EQ; } if (boe0->type().bt() == Type::BT_BOOL) { ctx0.b = ctx1.b = C_MIX; } else if (boe0->type().bt() == Type::BT_INT) { ctx0.i = ctx1.i = C_MIX; } goto flatten_bool_op; case BOT_IN: case BOT_SUBSET: case BOT_SUPERSET: ctx0.i = ctx1.i = C_MIX; flatten_bool_op : { bool inRootCtx = (ctx0.b == ctx1.b && ctx0.b == C_ROOT && b == constants().varTrue); EE e0 = flat_exp(env, ctx0, boe0, nullptr, inRootCtx ? b : nullptr); EE e1 = flat_exp(env, ctx1, boe1, nullptr, inRootCtx ? b : nullptr); ret.b = bind(env, Ctx(), b, constants().literalTrue); std::vector ees(3); ees[0].b = e0.b; ees[1].b = e1.b; if (isfalse(env, e0.b()) || isfalse(env, e1.b())) { ees.resize(2); ret.r = conj(env, r, ctx, ees); break; } if (e0.r()->type().isPar() && e1.r()->type().isPar()) { GCLock lock; auto* bo_par = new BinOp(e->loc(), e0.r(), bot, e1.r()); std::vector args({e0.r(), e1.r()}); bo_par->decl(env.model->matchFn(env, bo_par->opToString(), args, false)); if (bo_par->decl() == nullptr) { throw FlatteningError(env, bo_par->loc(), "cannot find matching declaration"); } bo_par->type(Type::parbool()); bool bo_val = eval_bool(env, bo_par); if (doubleNeg) { bo_val = !bo_val; } ees[2].b = constants().boollit(bo_val); ret.r = conj(env, r, ctx, ees); break; } if (e0.r()->type().bt() == Type::BT_INT && e1.r()->type().isPar() && e0.r()->isa() && (bot == BOT_IN || bot == BOT_SUBSET)) { VarDecl* vd = e0.r()->cast()->decl(); Id* ident = vd->id(); if (ctx.b == C_ROOT && r == constants().varTrue) { if (vd->ti()->domain() == nullptr) { vd->ti()->domain(e1.r()); } else { GCLock lock; IntSetVal* newdom = eval_intset(env, e1.r()); while (ident != nullptr) { bool changeDom = false; if (ident->decl()->ti()->domain() != nullptr) { IntSetVal* domain = eval_intset(env, ident->decl()->ti()->domain()); IntSetRanges dr(domain); IntSetRanges ibr(newdom); Ranges::Inter i(dr, ibr); IntSetVal* newibv = IntSetVal::ai(i); if (domain->card() != newibv->card()) { newdom = newibv; changeDom = true; } } else { changeDom = true; } if (ident->type().st() == Type::ST_PLAIN && newdom->size() == 0) { env.fail(); } else if (changeDom) { set_computed_domain(env, ident->decl(), new SetLit(Location().introduce(), newdom), false); if (ident->decl()->e() == nullptr && newdom->min() == newdom->max() && !vd->type().isSet()) { ident->decl()->e(IntLit::a(newdom->min())); } } ident = ident->decl()->e() != nullptr ? ident->decl()->e()->dynamicCast() : nullptr; } } ret.r = bind(env, ctx, r, constants().literalTrue); break; } if (vd->ti()->domain() != nullptr) { // check if current domain is already subsumed or falsified by this constraint GCLock lock; IntSetVal* check_dom = eval_intset(env, e1.r()); IntSetVal* domain = eval_intset(env, ident->decl()->ti()->domain()); { IntSetRanges cdr(check_dom); IntSetRanges dr(domain); if (Ranges::subset(dr, cdr)) { // the constraint is subsumed ret.r = bind(env, ctx, r, constants().literalTrue); break; } } if (vd->type().st() == Type::ST_PLAIN) { // only for var int (for var set of int, subset can never fail because of the domain) IntSetRanges cdr(check_dom); IntSetRanges dr(domain); if (Ranges::disjoint(cdr, dr)) { // the constraint is false ret.r = bind(env, ctx, r, constants().literalFalse); break; } } } } std::vector args; ASTString callid; Expression* le0 = nullptr; Expression* le1 = nullptr; if (boe0->type().isint() && !boe0->type().isOpt() && bot != BOT_IN) { le0 = get_linexp(e0.r()); } else if (boe0->type().isfloat() && !boe0->type().isOpt() && bot != BOT_IN) { le0 = get_linexp(e0.r()); } if (le0 != nullptr) { if (boe0->type().isint() && boe1->type().isint() && !boe1->type().isOpt()) { le1 = get_linexp(e1.r()); } else if (boe0->type().isfloat() && boe1->type().isfloat() && !boe1->type().isOpt()) { le1 = get_linexp(e1.r()); } } if (le1 != nullptr) { if (boe0->type().isint()) { flatten_linexp_binop(env, ctx, r, b, ret, le0, le1, bot, doubleNeg, ees, args, callid); } else { flatten_linexp_binop(env, ctx, r, b, ret, le0, le1, bot, doubleNeg, ees, args, callid); } } else { switch (bot) { case BOT_GR: std::swap(e0, e1); bot = BOT_LE; break; case BOT_GQ: std::swap(e0, e1); bot = BOT_LQ; break; default: break; } args.push_back(e0.r); args.push_back(e1.r); } if (!args.empty()) { GC::lock(); bool idIsOp = false; if (callid == "") { assert(args.size() == 2); if (!isBuiltin) { callid = op_to_id(bot); idIsOp = true; } else { callid = op_to_builtin(args[0](), args[1](), bot); } } std::vector args_e(args.size()); for (auto i = static_cast(args.size()); (i--) != 0U;) { args_e[i] = args[i](); } Call* cc = new Call(e->loc().introduce(), callid, args_e); cc->decl(env.model->matchFn(env, cc->id(), args_e, false)); if (cc->decl() == nullptr) { throw FlatteningError(env, cc->loc(), "cannot find matching declaration"); } if (idIsOp && cc->decl()->e() == nullptr) { // This is in fact a built-in operator, but we only found out after // constructing the call cc = new Call(e->loc().introduce(), op_to_builtin(args[0](), args[1](), bot), args_e); cc->decl(env.model->matchFn(env, cc->id(), args_e, false)); if (cc->decl() == nullptr) { throw FlatteningError(env, cc->loc(), "cannot find matching declaration"); } } cc->type(cc->decl()->rtype(env, args_e, false)); // add defines_var annotation if applicable Id* assignTo = nullptr; if (bot == BOT_EQ && ctx.b == C_ROOT) { if ((le0 != nullptr) && le0->isa()) { assignTo = le0->cast(); } else if ((le1 != nullptr) && le1->isa()) { assignTo = le1->cast(); } if (assignTo != nullptr) { make_defined_var(assignTo->decl()->flat(), cc); } } auto cit = env.cseMapFind(cc); if (cit != env.cseMapEnd()) { ees[2].b = cit->second.r(); if (doubleNeg) { Type t = ees[2].b()->type(); ees[2].b = new UnOp(Location().introduce(), UOT_NOT, ees[2].b()); ees[2].b()->type(t); } if (Id* id = ees[2].b()->dynamicCast()) { add_ctx_ann(id->decl(), ctx.b); } ret.r = conj(env, r, ctx, ees); GC::unlock(); } else { bool singleExp = true; for (auto& ee : ees) { if (!istrue(env, ee.b())) { singleExp = false; break; } } KeepAlive ka(cc); GC::unlock(); if (singleExp) { if (doubleNeg) { ctx.b = -ctx.b; ctx.neg = !ctx.neg; } ret.r = flat_exp(env, ctx, cc, r, nullptr).r; } else { ees[2].b = flat_exp(env, Ctx(), cc, nullptr, nullptr).r; if (doubleNeg) { GCLock lock; Type t = ees[2].b()->type(); ees[2].b = new UnOp(Location().introduce(), UOT_NOT, ees[2].b()); ees[2].b()->type(t); } if (Id* id = ees[2].b()->dynamicCast()) { add_ctx_ann(id->decl(), ctx.b); } ret.r = conj(env, r, ctx, ees); } } } else { ret.r = conj(env, r, ctx, ees); } } break; case BOT_PLUSPLUS: { std::vector ee(2); EE eev = flat_exp(env, ctx, boe0, nullptr, nullptr); ee[0] = eev; ArrayLit* al; if (eev.r()->isa()) { al = eev.r()->cast(); } else { Id* id = eev.r()->cast(); if (id->decl() == nullptr) { throw InternalError("undefined identifier"); } if (id->decl()->e() == nullptr) { throw InternalError("array without initialiser not supported"); } al = follow_id(id)->cast(); } ArrayLit* al0 = al; eev = flat_exp(env, ctx, boe1, nullptr, nullptr); ee[1] = eev; if (eev.r()->isa()) { al = eev.r()->cast(); } else { Id* id = eev.r()->cast(); if (id->decl() == nullptr) { throw InternalError("undefined identifier"); } if (id->decl()->e() == nullptr) { throw InternalError("array without initialiser not supported"); } al = follow_id(id)->cast(); } ArrayLit* al1 = al; std::vector v(al0->size() + al1->size()); for (unsigned int i = al0->size(); (i--) != 0U;) { v[i] = (*al0)[i]; } for (unsigned int i = al1->size(); (i--) != 0U;) { v[al0->size() + i] = (*al1)[i]; } GCLock lock; auto* alret = new ArrayLit(e->loc(), v); alret->type(e->type()); ret.b = conj(env, b, Ctx(), ee); ret.r = bind(env, ctx, r, alret); } break; } return ret; } } // namespace MiniZinc