Beispiel #1
0
    def get_proof(self):
        invC = Const("inv", TFun(gcl.stateT, BoolType))
        transC = Const("trans", TFun(gcl.stateT, gcl.stateT, BoolType))
        s1 = Var("s1", gcl.stateT)
        s2 = Var("s2", gcl.stateT)
        prop = Thm([], Implies(invC(s1), transC(s1, s2), invC(s2)))
        # print(printer.print_thm(prop))

        trans_pt = ProofTerm.assume(transC(s1, s2))
        # print(printer.print_thm(trans_pt.th))
        P = Implies(invC(s1), invC(s2))
        ind_pt = apply_theorem("trans_cases", inst=Inst(a1=s1, a2=s2, P=P))
        # print(printer.print_thm(ind_pt.th))

        ind_As, ind_C = ind_pt.prop.strip_implies()
        for ind_A in ind_As[1:-1]:
            # print("ind_A: ", ind_A)
            vars, As, C = logic.strip_all_implies(ind_A, ["s", "k"])
            # for A in As:
            #     print("A: ", A)
            # print("C: ", C)
            eq1 = ProofTerm.assume(As[0])
            eq2 = ProofTerm.assume(As[1])
            guard = ProofTerm.assume(As[2])
            inv_pre = ProofTerm.assume(As[3]).on_arg(rewr_conv(eq1)).on_prop(
                rewr_conv("inv_def"))
            C_goal = ProofTerm.assume(C).on_arg(rewr_conv(eq2)).on_prop(
                rewr_conv("inv_def"))
Beispiel #2
0
    def testInductList(self):
        Ta = TVar("a")
        Tlista = Type("list", Ta)
        list_ext = induct.add_induct_type(
            "list", ["a"], [("nil", Tlista, []), ("cons", TFun(Ta, Tlista, Tlista), ["x", "xs"])])

        nil = Const("nil", Tlista)
        cons = Const("cons", TFun(Ta, Tlista, Tlista))
        x = Var("x", Ta)
        xs = Var("xs", Tlista)
        x2 = Var("x'", Ta)
        xs2 = Var("xs'", Tlista)
        P = Var("P", TFun(Tlista, boolT))
        xlist = Var("x", Tlista)

        res = [
            AxType("list", 1),
            AxConstant("nil", Tlista),
            AxConstant("cons", TFun(Ta, Tlista, Tlista)),
            Theorem("list_nil_cons_neq", Thm([], logic.neg(eq(nil, cons(x, xs))))),
            Theorem("list_cons_inject", Thm([], imp(eq(cons(x, xs), cons(x2, xs2)), conj(eq(x, x2), eq(xs, xs2))))),
            Theorem("list_induct", Thm([], imp(P(nil), all(x, all(xs, imp(P(xs), P(cons(x, xs))))), P(xlist)))),
            Attribute("list_induct", "var_induct")
        ]
        self.assertEqual(list_ext.data, res)
Beispiel #3
0
 def number(self, n):
     if int(n) == 0:
         return Const("zero", None)
     elif int(n) == 1:
         return Const("one", None)
     else:
         return Const("of_nat", None)(Binary(int(n)))
Beispiel #4
0
    def get_proof(self):
        invC = Const("inv", TFun(gcl.stateT, boolT))
        transC = Const("trans", TFun(gcl.stateT, gcl.stateT, boolT))
        s1 = Var("s1", gcl.stateT)
        s2 = Var("s2", gcl.stateT)
        prop = Thm.mk_implies(invC(s1), transC(s1, s2), invC(s2))
        # print(printer.print_thm(self.thy, prop))

        trans_pt = ProofTerm.assume(transC(s1, s2))
        # print(printer.print_thm(self.thy, trans_pt.th))
        P = Term.mk_implies(invC(s1), invC(s2))
        ind_pt = apply_theorem(self.thy,
                               "trans_cases",
                               inst={
                                   "a1": s1,
                                   "a2": s2,
                                   "P": P
                               })
        # print(printer.print_thm(self.thy, ind_pt.th))

        ind_As, ind_C = ind_pt.prop.strip_implies()
        for ind_A in ind_As[1:-1]:
            # print("ind_A: ", printer.print_term(self.thy, ind_A))
            vars, As, C = logic.strip_all_implies(ind_A, ["s", "k"])
            # for A in As:
            #     print("A: ", printer.print_term(self.thy, A))
            # print("C: ", printer.print_term(self.thy, C))
            eq1 = ProofTerm.assume(As[0])
            eq2 = ProofTerm.assume(As[1])
            guard = ProofTerm.assume(As[2])
            inv_pre = ProofTerm.assume(As[3]).on_arg(self.thy, rewr_conv(eq1)) \
                                             .on_prop(self.thy, rewr_conv("inv_def"))
            C_goal = ProofTerm.assume(C).on_arg(self.thy, rewr_conv(eq2)) \
                                        .on_prop(self.thy, rewr_conv("inv_def"))
Beispiel #5
0
    def testInferPrintedType(self):
        t = Const("nil", listT(Ta))
        infer_printed_type(thy, t)
        self.assertTrue(hasattr(t, "print_type"))

        t = list.cons(Ta)(Var("a", Ta))
        infer_printed_type(thy, t)
        self.assertFalse(hasattr(t.fun, "print_type"))

        t = Term.mk_equals(Const("nil", listT(Ta)), Const("nil", listT(Ta)))
        infer_printed_type(thy, t)
        self.assertFalse(hasattr(t.fun.fun, "print_type"))
        self.assertTrue(hasattr(t.arg1, "print_type"))
        self.assertFalse(hasattr(t.arg, "print_type"))

        t = Term.mk_equals(list.mk_append(list.nil(Ta), list.nil(Ta)),
                           list.nil(Ta))
        infer_printed_type(thy, t)
        self.assertTrue(hasattr(t.arg1.arg1, "print_type"))
        self.assertFalse(hasattr(t.arg1.arg, "print_type"))
        self.assertFalse(hasattr(t.arg, "print_type"))

        t = Term.mk_abs(Var("x", Ta), Term.mk_equals(Var("x", Ta),
                                                     Var("x", Ta)))
        infer_printed_type(thy, t)
Beispiel #6
0
    def testInferTypeFail(self):
        test_data = [(Const("implies", None)(Var("A1", nat.natT),
                                             Var("A2", None))),
                     (Const("equals", None)(Var("A", None), Var("a", None)))]

        for t in test_data:
            self.assertRaisesRegex(TypeInferenceException, "Unable to unify",
                                   type_infer, thy, ctxt, t)
Beispiel #7
0
    def testInferTypeFail2(self):
        test_data = [
            Abs("x", None, Abs("y", None, Const("equals", None)(Var("x", None), Var("y", None)))),
            Const("nil", None),
        ]

        for t in test_data:
            self.assertRaisesRegex(TypeInferenceException, "Unspecified type", type_infer, t)
Beispiel #8
0
    def testInferType(self):
        test_data = [
            # A1 --> A2
            (Const("implies", None)(Var("A1", None), Var("A2", None)),
             Term.mk_implies(Var("A1", boolT), Var("A2", boolT))),
            # A1 = A2
            (Const("equals", None)(Var("A1", boolT), Var("A2", None)),
             Term.mk_equals(Var("A1", boolT), Var("A2", boolT))),
            # a = b
            (Const("equals", None)(Var("a", None), Var("b", None)),
             Const("equals", TFun(Ta, Ta, boolT))(Var("a", Ta), Var("b", Ta))),
            # %x. P x
            (Abs("x", None,
                 Var("P", None)(Bound(0))),
             Abs("x", Ta,
                 Var("P", TFun(Ta, boolT))(Bound(0)))),
            # %x y. x = y
            (Abs("x", Ta,
                 Abs("y", None,
                     Const("equals", None)(Bound(1), Bound(0)))),
             Abs(
                 "x", Ta,
                 Abs("y", Ta,
                     Const("equals", TFun(Ta, Ta, boolT))(Bound(1),
                                                          Bound(0))))),
            # [a]
            (Const("cons", None)(Var("a", None), Const("nil", None)),
             list.cons(Ta)(Var("a", Ta), Const("nil", listT(Ta)))),
        ]

        for t, res in test_data:
            self.assertEqual(type_infer(thy, ctxt, t), res)
Beispiel #9
0
    def testToInternalVars(self):
        test_data = [
            (Var("x", TVar("a")), Var("_x", TVar("_a"))),
            (Var("x", natT), Var("_x", natT)),
            (Const("x", TVar("a")), Const("x", TVar("_a"))),
            (Const("x", natT), Const("x", natT)),
            (Abs("x", TVar("a"),
                 Var("y",
                     TVar("b"))), Abs("x", TVar("_a"), Var("_y", TVar("_b")))),
        ]

        for t, res in test_data:
            self.assertEqual(matcher.to_internal_vars(t), res)
Beispiel #10
0
 def testRewrConvWithAssum(self):
     x = Const("x", natT)
     y = Const("y", natT)
     x_eq_y = Term.mk_equals(x, y)
     th = Thm([], Term.mk_implies(x_eq_y, x_eq_y))
     cv = arg_conv(rewr_conv(ProofTerm.atom(0, th)))
     f = Const("f", TFun(natT, natT))
     res = Thm([x_eq_y], Term.mk_equals(f(x), f(y)))
     self.assertEqual(cv.eval(thy, f(x)), res)
     prf = Proof()
     prf.add_item(0, "sorry", th=th)
     cv.get_proof_term(thy, f(x)).export(prf=prf)
     self.assertEqual(thy.check_proof(prf), res)
Beispiel #11
0
    def testSubstType(self):
        test_data = [
            (Var('a', STa), Var("a", Tb)),
            (Const("c", STa), Const("c", Tb)),
            (Var("f", TFun(STa, Tb))(Var("a",
                                         STa)), Var("f",
                                                    TFun(Tb, Tb))(Var("a",
                                                                      Tb))),
            (Abs("x", STa, B0), Abs("x", Tb, B0)),
            (Abs("x", STa, Var('a', STa)), Abs("x", Tb, Var("a", Tb))),
        ]

        for t, res in test_data:
            self.assertEqual(t.subst_type(TyInst(a=Tb)), res)
Beispiel #12
0
    def testAbsConv(self):
        nat0 = Const("zero", natT)
        nat1 = Const("one", natT)
        f = Const("f", TFun(natT, natT))
        g = Const("g", TFun(natT, natT))
        x = Var("x", natT)

        thy.add_theorem("f_eq_g", eq(f(x), g(x)))
        t = Term.mk_abs(x, f(x))
        cv = abs_conv(rewr_conv("f_eq_g"))
        res_th = eq(t, Term.mk_abs(x, g(x)))
        self.assertEqual(cv.eval(thy, t), res_th)
        prf = cv.get_proof_term(thy, t).export()
        self.assertEqual(thy.check_proof(prf), res_th)
Beispiel #13
0
    def testFirstOrderMatchFun(self):
        """First-order matching of variables in function position."""
        P = Var("P", TFun(Ta, boolT))
        Q = Var("Q", TFun(Ta, boolT))
        C = Const("C", TFun(boolT, boolT, boolT))

        test_data = [
            (abs(x, P(x)), abs(x, C(P(x), Q(x))), {
                "P": abs(x, C(P(x), Q(x)))
            }),
            (abs(x, C(P(x), Q(x))), abs(x, C(Q(x), P(x))), {
                "P": Q,
                "Q": P
            }),
            (abs(x, C(P(x), P(x))), abs(x, C(C(P(x), Q(x)), C(P(x), Q(x)))), {
                "P": abs(x, C(P(x), Q(x)))
            }),
            (exists(x, P(x)), exists(x, conj(P(x), Q(x))), {
                "P": abs(x, conj(P(x), Q(x)))
            }),
        ]

        for pat, t, inst in test_data:
            if inst is not None:
                self.assertEqual(matcher.first_order_match(pat, t)[1], inst)
            else:
                self.assertRaises(matcher.MatchException,
                                  matcher.first_order_match, pat, t)
Beispiel #14
0
 def get_proof_term(self, t):
     assert t.is_equals() or t.is_less_eq() or t.is_less()\
         or t.is_greater_eq() or t.is_greater(), "%s is not an equality term" % t
     pt1 = refl(t)  # a = b <==> a = b
     if t.is_equals():
         pt2 = pt1.on_rhs(rewr_conv('int_sub_move_0_r',
                                    sym=True))  # a = b <==> a - b = 0
         eq_refl = ProofTerm.reflexive(equals(IntType))
     elif t.is_less_eq():
         pt2 = pt1.on_rhs(rewr_conv('int_leq'))
         eq_refl = ProofTerm.reflexive(less_eq(IntType))
     elif t.is_less():
         pt2 = pt1.on_rhs(rewr_conv('int_less'))
         eq_refl = ProofTerm.reflexive(less(IntType))
     elif t.is_greater_eq():
         pt2 = pt1.on_rhs(rewr_conv('int_geq'))
         eq_refl = ProofTerm.reflexive(greater_eq(IntType))
     elif t.is_greater():
         pt2 = pt1.on_rhs(rewr_conv('int_gt'))
         eq_refl = ProofTerm.reflexive(greater(IntType))
     pt3 = simp_full().get_proof_term(
         pt2.prop.arg.arg1)  # a - b = a + (-1) * b
     pt4 = ProofTerm.combination(eq_refl, pt3)
     pt5 = ProofTerm.combination(pt4, refl(Const(
         'zero', IntType)))  # a - b = 0 <==> a + (-1)*b = 0
     return pt2.transitive(pt5)  # a = b <==> a + (-1) * b = 0
Beispiel #15
0
    def get_extension(self):
        assert self.error is None, "get_extension"
        res = []
        res.append(
            extension.Constant(self.name, self.type, ref_name=self.cname))

        for rule in self.rules:
            res.append(extension.Theorem(rule['name'], Thm([], rule['prop'])))
            res.append(extension.Attribute(rule['name'], 'hint_backward'))

        # Case rule
        Targs, _ = self.type.strip_type()
        vars = []
        for i, Targ in enumerate(Targs):
            vars.append(Var("_a" + str(i + 1), Targ))

        P = Var("P", BoolType)
        pred = Const(self.name, self.type)
        assum0 = pred(*vars)
        assums = []
        for rule in self.rules:
            prop = rule['prop']
            As, C = prop.strip_implies()
            eq_assums = [Eq(var, arg) for var, arg in zip(vars, C.args)]
            assum = Implies(*(eq_assums + As), P)
            for var in reversed(prop.get_vars()):
                assum = Forall(var, assum)
            assums.append(assum)

        prop = Implies(*([assum0] + assums + [P]))
        res.append(extension.Theorem(self.cname + "_cases", Thm([], prop)))

        return res
Beispiel #16
0
    def parse(self, data):
        self.name = data['name']

        try:
            self.type = parser.parse_type(data['type'])
            self.cname = theory.thy.get_overload_const_name(
                self.name, self.type)

            for rule in data['rules']:
                with context.fresh_context(defs={self.name: self.type}):
                    prop = parser.parse_term(rule['prop'])

                # Test conclusion of the prop
                _, concl = prop.strip_implies()
                f, _ = concl.strip_comb()
                if f != Const(self.name, self.type):
                    raise ItemException(
                        "Inductive %s: wrong head of conclusion" % self.name)

                self.rules.append({'name': rule['name'], 'prop': prop})

        except Exception as error:
            self.type = data['type']
            self.rules = data['rules']
            self.error = error
            self.trace = traceback2.format_exc()
Beispiel #17
0
    def parse(self, data):
        self.name = data['name']

        try:
            self.type = parser.parse_type(data['type'])
            self.cname = theory.thy.get_overload_const_name(
                self.name, self.type)

            for rule in data['rules']:
                with context.fresh_context(defs={self.name: self.type}):
                    prop = parser.parse_term(rule['prop'])

                # prop should be an equality
                if not prop.is_equals():
                    raise ItemException("Fun %s: rule is not an equality" %
                                        self.name)

                f, args = prop.lhs.strip_comb()
                if f != Const(self.name, self.type):
                    raise ItemException("Fun %s: wrong head of lhs" %
                                        self.name)
                lhs_vars = set(v.name for v in prop.lhs.get_vars())
                rhs_vars = set(v.name for v in prop.rhs.get_vars())
                if not rhs_vars.issubset(lhs_vars):
                    raise ItemException(
                        "Fun %s: extra variables in rhs: %s" %
                        (self.name, ", ".join(v for v in rhs_vars - lhs_vars)))

                self.rules.append({'prop': prop})

        except Exception as error:
            self.type = data['type']
            self.rules = data['rules']
            self.error = error
            self.trace = traceback2.format_exc()
Beispiel #18
0
def mk_exists(x, body):
    """Given a variable x and a term t possibly depending on x, return
    the term ?x. t.

    """
    exists_t = Const("exists", TFun(TFun(x.T, boolT), boolT))
    return exists_t(Term.mk_abs(x, body))
Beispiel #19
0
class NatComparison(Macro):

    oprmap = {
        '<=': Ind('Coq.Init.Peano.le', 0),
        '<': Const('Coq.Init.Peano.lt'),
        '=': Apply(Ind('Coq.Init.Logic.eq', 0), NatType())
    }

    @classmethod
    def name(cls):
        return "net_comparison"

    def __init__(self, opr, l, r):
        assert opr in ('<=', '<',
                       '='), "unsupported comparison operator %s" % opr
        self.opr = opr
        self.l = l
        self.r = r

    def check(self, environment=None):
        _, sideff_l = self.l.check(environment)
        _, sideff_r = self.r.check(environment)
        return Sort.mkProp(), set.union(sideff_l, sideff_r)

    def render(self, environment=None, debug=False):
        return "(%s %s %s)" % (
            self.l.render(environment, debug),
            self.opr,
            self.r.render(environment, debug),
        )

    def unfold(self):
        return Apply(self.oprmap[self.opr], self.l, self.r)
Beispiel #20
0
def load_system(filename):
    dn = os.path.dirname(os.path.realpath(__file__))
    with open(os.path.join(dn, 'examples/' + filename + '.json'),
              encoding='utf-8') as a:
        data = json.load(a)

    basic.load_theory('gcl')

    name = data['name']
    vars = []
    for nm, str_T in data['vars'].items():
        T = parser.parse_type(str_T)
        vars.append(Var(nm, T))

    for i, nm in enumerate(data['states']):
        theory.thy.add_term_sig(nm, NatType)
        theory.thy.add_theorem(nm + "_def",
                               Thm([], Eq(Const(nm, NatType), Nat(i))))

    states = [Const(nm, NatType) for nm in data['states']]

    rules = []
    for rule in data['rules']:
        if isinstance(rule['var'], str):
            rule_var = Var(rule['var'], NatType)
            cur_vars = {v.name: v.T for v in vars + [rule_var]}
        else:
            assert isinstance(rule['var'], list)
            rule_var = [Var(nm, NatType) for nm in rule['var']]
            cur_vars = {v.name: v.T for v in vars + rule_var}

        with context.fresh_context(vars=cur_vars):
            guard = parser.parse_term(rule['guard'])
            assign = dict()
            for k, v in rule['assign'].items():
                assign[parser.parse_term(k)] = parser.parse_term(v)
            rules.append((rule_var, guard, assign))

    invs = []
    for inv in data['invs']:
        inv_vars = [Var(nm, NatType) for nm in inv['vars']]
        with context.fresh_context(vars={v.name: v.T
                                         for v in vars + inv_vars}):
            prop = parser.parse_term(inv['prop'])
        invs.append((inv_vars, prop))

    return ParaSystem(name, vars, states, rules, invs)
Beispiel #21
0
def mk_exists1(x, body):
    """Given a variable x and a term P possibly depending on x, return
    the term ?!x. P.

    """
    assert x.is_var(), "mk_exists1"
    exists1_t = Const("exists1", TFun(TFun(x.T, BoolType), BoolType))
    return exists1_t(Lambda(x, body))
Beispiel #22
0
    def testCheckedExtend(self):
        """Checked extension: adding an axiom."""
        id_simps = Eq(Comb(Const("id", TFun(Ta,Ta)), x), x)
        exts = [extension.Theorem("id.simps", Thm([], id_simps))]

        ext_report = theory.thy.checked_extend(exts)
        self.assertEqual(theory.get_theorem("id.simps", svar=False), Thm([], id_simps))
        self.assertEqual(ext_report.get_axioms(), [("id.simps", Thm([], id_simps))])
Beispiel #23
0
 def helper(*args):
     if len(args) == 3:
         f, a, b = args
         return Const("fun_upd", None)(f, a, b)
     elif len(args) > 3:
         return helper(helper(*args[:3]), *args[3:])
     else:
         raise TypeError()
Beispiel #24
0
def mk_some(x, body):
    """Given a variable x and a term P possibly depending on x, return
    the term SOME x. P.

    """
    assert x.is_var(), "mk_some"
    some_t = Const("Some", TFun(TFun(x.T, BoolType), x.T))
    return some_t(Lambda(x, body))
Beispiel #25
0
 def vname(self, s):
     s = str(s)
     if theory.thy.has_term_sig(s) or s in context.ctxt.defs:
         # s is the name of a constant in the theory
         return Const(s, None)
     else:
         # s not found, either bound or free variable
         return Var(s, None)
Beispiel #26
0
def load_system(filename):
    dn = os.path.dirname(os.path.realpath(__file__))
    with open(os.path.join(dn, 'examples/' + filename + '.json'),
              encoding='utf-8') as a:
        data = json.load(a)

    thy = basic.load_theory('gcl')

    name = data['name']
    vars = []
    for nm, str_T in data['vars'].items():
        T = parser.parse_type(thy, str_T)
        vars.append(Var(nm, T))

    for i, nm in enumerate(data['states']):
        thy.add_term_sig(nm, natT)
        thy.add_theorem(nm + "_def",
                        Thm.mk_equals(Const(nm, natT), to_binary(i)))

    states = [Const(nm, natT) for nm in data['states']]

    rules = []
    for rule in data['rules']:
        if isinstance(rule['var'], str):
            rule_var = Var(rule['var'], natT)
            ctxt = dict((v.name, v.T) for v in vars + [rule_var])
        else:
            assert isinstance(rule['var'], list)
            rule_var = [Var(nm, natT) for nm in rule['var']]
            ctxt = dict((v.name, v.T) for v in vars + rule_var)
        guard = parser.parse_term(thy, ctxt, rule['guard'])
        assign = dict()
        for k, v in rule['assign'].items():
            assign[parser.parse_term(thy, ctxt,
                                     k)] = parser.parse_term(thy, ctxt, v)
        rules.append((rule_var, guard, assign))

    invs = []
    for inv in data['invs']:
        inv_vars = [Var(nm, natT) for nm in inv['vars']]
        ctxt = dict((v.name, v.T) for v in vars + inv_vars)
        prop = parser.parse_term(thy, ctxt, inv['prop'])
        invs.append((inv_vars, prop))

    return ParaSystem(thy, name, vars, states, rules, invs)
Beispiel #27
0
 def vname(self, s):
     thy = parser_setting['thy']
     s = str(s)
     if thy.has_term_sig(s):
         # s is the name of a constant in the theory
         return Const(s, None)
     else:
         # s not found, either bound or free variable
         return Var(s, None)
Beispiel #28
0
    def testIsPattern(self):
        test_data = [
            (Var('a', Ta), True),
            (Var('f', TFun(Ta, Tb))(Var('a', Ta)), False),
            (Const('f', TFun(Ta, Tb))(Var('a', Ta)), True),
        ]

        for t, res in test_data:
            self.assertEqual(matcher.is_pattern(t, []), res)
Beispiel #29
0
def within(net, S):
    """Form the term within net S.
    
    net -- a term of type 'a net.
    S -- a term of type 'a set.
    
    """
    Ta = net.get_type().args[0]
    return Const('within', TFun(netT(Ta), set.setT(Ta), netT(Ta)))(net, S)
Beispiel #30
0
class BinaryNumberExpr(Macro):

    oprmap = {
        '+': None,
        '-': None,
        '*': None,
        '/': None,
        '%': None,
        '&': None,
        '|': None,
        '>>': None,
        '<<': None,
        '>': None,
        '>=': None,
        '<=': Const('Coq.ZArith.BinInt.Z.le'),
        '<': Const('Coq.ZArith.BinInt.Z.lt'),
        '=': Apply(Ind('Coq.Init.Logic.eq', 0), BinaryNumberType()),
        '==': None,
        '!=': None,
    }

    def __init__(self, opr, l, r=None):
        assert opr in self.oprmap, "unsupported operator %s" % opr
        self.opr = opr
        self.l = l
        self.r = r

    def type(self, environment=None):
        return Sort.mkProp()

    def check(self, environment=None):
        _, sideff_l = self.l.check(environment)
        _, sideff_r = self.r.check(environment)
        return Sort.mkProp(), set.union(sideff_l, sideff_r)

    def render(self, environment=None, debug=False):
        return "(%s %s %s)" % (
            self.l.render(environment, debug),
            self.opr,
            self.r.render(environment, debug),
        )

    def unfold(self):
        return Apply(self.oprmap[self.opr], self.l, self.r)