def evalTimeConstraint(self, time_constraint: Term,
                        ctx: EvalContext) -> bool:
     assert time_constraint is not None
     # return True
     rv = chcast(bool, self.evalTerm(time_constraint, ctx))
     # print('evalTimeConstraint: ', rv)
     return rv
    def mk_l4contract(self, l:List[SExpr]) -> L4Contract:
        x : SExpr
        for x in l:
            #assert len(x) >= 2, "Problem top-level: " + str(x)
            rem = x.tillEnd(1)
            def head(constant:str) -> bool:
                nonlocal x
                return streqci(x[0], constant)

            if   head( STR_ARG_MACRO_DEC_LABEL):
                macroname = chcaststr(x[1])
                macroparams : List[str]
                if isinstance(x[2],str):
                    macroparams = [x[2]]
                else:
                    macroparams = cast(List[str],castse(x[2]).lst)
                macrobody = chcast(SExpr, x[3])
                self.top.str_arg_macros[ macroname ] = L4Macro(macroparams, macrobody)

            elif   head(GLOBAL_VARS_AREA_LABEL):
                self.top.global_var_decs = self._mk_global_vars(rem)

            elif head(CONTRACT_PARAMETERS_AREA_LABEL):
                # assert all(isinstance(expr[0],str) for expr in rem)
                self.top.contract_params = {castid(ContractParamId,expr[0]) : self._mk_contract_param(expr) for expr in rem}

            elif head(TOPLEVEL_CLAIMS_AREA_LABEL):
                self.top.claims = self._mk_claims(rem)

            elif head(ROLES_DEC_LABEL):
                self.top.roles.extend(self._mk_actors(rem))

            elif head(PROSE_CONTRACT_AREA_LABEL):
                self.top.prose_contract = self._mk_prose_contract(cast(List[List[str]], rem))

            elif head(FORMAL_CONTRACT_AREA_LABEL):
                self._mk_main_program_area(rem)

            elif head(TIMEUNIT_DEC_LABEL):
                self.top.timeunit = chcaststr(x[1]).lower()
                self.assertOrSyntaxError( self.top.timeunit in SUPPORTED_TIMEUNITS, x, f"{TIMEUNIT_DEC_LABEL} must be one of {str(SUPPORTED_TIMEUNITS)}")

            elif head( DEFINITIONS_AREA ):
                self.top.definitions = self._mk_definitions(rem)

            elif head( DOT_FILE_NAME_LABEL ):
                self.top.dot_file_name = chcaststr(x[1][1]) # the extra [1] is because its parse is of the form ['STRLIT', 'filename']
            elif head( IMG_FILE_NAME_LABEL ):
                self.top.img_file_name = chcaststr(x[1][1]) # the extra [1] is because its parse is of the form ['STRLIT', 'filename']

            else:
                raise Exception("Unsupported: ", x[0])

        for f in self.after_model_build_requirements:
            if not f[0]():
                raise Exception(f[1])

        return self.top
    def current_event_compatible_with_enabled_NextActionRule(
            self, action_rule: NextActionRule) -> bool:
        assert self.cur_event is not None
        role_action_match = self.cur_event.role_id == action_rule.role_id and self.cur_event.action_id == action_rule.action_id
        if not role_action_match: return False
        if not action_rule.where_clause: return True

        # ctx2 = EvalContext(self.gvarvals, self.cur_event.params, ctx.abapvals)
        return chcast(bool, self.evalTerm(action_rule.where_clause, None))
    def apply_action(self,
                     action: Action,
                     to_breach_section_id: Optional[SectionId] = None
                     ) -> ApplyActionResult:
        assert self.cur_event is not None

        if to_breach_section_id is not None:
            self.last_or_current_section_id = to_breach_section_id
            self.last_section_entrance_delta = self.cur_event_delta()
            return ApplyActionResult(None)

        self.last_appliedaction_params = self.cur_event.params
        rv: ApplyActionResult

        if action.global_state_transform:
            self.evalCodeBlock(action.global_state_transform)

        future: PartlyInstantiatedPartyFutureActionRule
        floatingrules_added: List[PartlyInstantiatedPartyFutureActionRule] = []
        for far in action.futures:
            is_disabled = far.entrance_enabled_guard and \
                          not chcast(bool,self.evalTerm(far.entrance_enabled_guard,None)) # TODO: unsure about this None
            if is_disabled:
                continue

            if far.where_clause:
                new_where_clause = PartialEvalTerm(
                    far.where_clause,
                    EvalContext(
                        self.gvarvals.copy(),
                        self.last_appliedaction_params.copy()
                        if self.last_appliedaction_params else None, True))
                future = PartlyInstantiatedPartyFutureActionRule(
                    far, new_where_clause)
                # far = copy.copy(far)
                # far.where_clause = new_where_clause
            else:
                future = PartlyInstantiatedPartyFutureActionRule(far, None)

            if far.deontic_keyword == 'must-later':
                self.future_obligations.append(future)
            else:
                assert far.deontic_keyword == 'may-later'
                self.future_permissions.append(future)
            floatingrules_added.append(future)
            # print("new future:", ef)

        if action.dest_section_id != LOOP_KEYWORD:
            self.last_or_current_section_id = action.dest_section_id
        self.last_section_entrance_delta = self.cur_event_delta()

        return ApplyActionResult(
            floatingrules_added if len(floatingrules_added) > 0 else None)
    def current_event_compatible_with_floatingrule(
            self,
            pif_action_rule: PartlyInstantiatedPartyFutureActionRule) -> bool:
        assert self.cur_event is not None
        role_action_match = self.cur_event.role_id == pif_action_rule.rule.role_id and self.cur_event.action_id == pif_action_rule.rule.action_id
        if not role_action_match: return False
        if not pif_action_rule.pe_where_clause: return True

        # will sub in self.cur_event.params for *rule*-bound action params in pif_action_rule.pe_where_clause
        # all other variables value in pif_action_rule.pe_where_clause are supplied by pif_action_rule.ctx
        # note that whether the current event is compatible with a future is not allowed to depend on
        # anything from the current execution context except self.cur_event.params. that is the reason for the None
        # argument.

        # TODO UNCERTAIN

        ctx = EvalContext(
            self.gvarvals,
            None,
            use_current_event_params_for_rulebound_action_params=True)
        return chcast(bool, self.evalTerm(pif_action_rule.pe_where_clause,
                                          ctx))
Example #6
0
 def subst(self, paramvals: List[SExprOrStr]) -> SExpr:
     return chcast(
         SExpr,
         sexpr_subst_mult_string(self.macrobody, self.macroparams,
                                 paramvals))
    def evalStatement(self, stmt: GlobalStateTransformStatement):
        # An Action's GlobalStateTransform block is *always* evaluated in the most recent global variable and
        # action parameter substitution context that's visible to it, as given by self.gvarvals and self.cur_event,
        # even when applying an action from a PartlyInstantiatedPartyFutureActionRule.
        # Therefore, this function does not take an EvalContext argument.

        # gvardec : GlobalVarDec

        if isinstance(stmt, (GlobalVarAssignStatement, IncrementStatement,
                             DecrementStatement, TimesEqualsStatement)):
            gvardec = self.top.gvarDecObj(stmt.varname)
            assert gvardec is not None, f"Global variable declaration for {stmt.varname} not found."

            if gvardec.isWriteOnceMore(
            ) and self.gvar_write_cnt[stmt.varname] >= 2:
                raise Exception(
                    f"Attempt to write twice more (after init) to writeOnceMore variable `{stmt.varname}`"
                )
            dictSetOrInc(self.gvar_write_cnt, stmt.varname, init=1)
            todo_once("use vardec for runtime type checking")
            rhs_value = self.evalTerm(stmt.value_expr, None)

        if isinstance(stmt, GlobalVarAssignStatement):
            self.gvarvals[stmt.varname] = rhs_value
            print(f"\t{stmt.varname} := {rhs_value}")

        elif isinstance(
                stmt,
            (IncrementStatement, DecrementStatement, TimesEqualsStatement)):
            current_var_val: Data = self.gvarvals[stmt.varname]
            assert current_var_val is not None

            if isinstance(stmt, IncrementStatement):
                self.gvarvals[stmt.varname] = current_var_val + rhs_value
                print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}")
            elif isinstance(stmt, DecrementStatement):
                self.gvarvals[stmt.varname] = current_var_val - rhs_value
                print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}")
            elif isinstance(stmt, TimesEqualsStatement):
                self.gvarvals[stmt.varname] = current_var_val * rhs_value
                print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}")
            else:
                raise Exception('fixme')

        elif isinstance(stmt, InCodeConjectureStatement):
            # print("conjecture " + str(stmt))
            assert self.evalTerm(
                stmt.value_expr, None
            ), f"""Conjecture {stmt.value_expr} is false! Variable values and action params:
            {str(self.gvarvals)}
            {str(self.last_appliedaction_params)}
            """

        elif isinstance(stmt, IfElse):
            test = chcast(bool, self.evalTerm(stmt.test, None))
            self.evalCodeBlock(
                GlobalStateTransform(
                    stmt.true_branch if test else stmt.false_branch))

        elif isinstance(stmt, StateTransformLocalVarDec):
            rhs_value = self.evalTerm(stmt.value_expr, None)
            self.localvar_vals[stmt.varname] = rhs_value

        else:
            raise NotImplementedError(
                "Unhandled GlobalStateTransformStatement: " + str(stmt))
    def assess_event_legal_wrt_nextrules(
            self, event: Event) -> EventLegalityAssessment:
        # for far in chain(self.future_obligations, self.future_permissions):
        #     if self.current_event_compatible_with_enabled_NextActionRule(event, far):
        #         print("COMPAT!")

        entrance_enabled_strong_obligs: List[PartyNextActionRule] = list()
        entrance_enabled_weak_obligs: List[PartyNextActionRule] = list()
        entrance_enabled_permissions: List[PartyNextActionRule] = list()
        entrance_enabled_env_action_rules: List[EnvNextActionRule] = list()

        # entrance_enabled_future_strong_obligs : List[PartyNextActionRule] = list()
        # entrance_enabled_future_permissions : List[PartyNextActionRule] = list()

        nar: NextActionRule
        ctx = EvalContext(
            self.gvarvals,
            self.last_appliedaction_params
            if self.last_or_current_section.is_anon() else None,
            use_current_event_params_for_rulebound_action_params=False)

        for nar in self.last_or_current_section.action_rules():
            entrance_enabled = True
            if nar.entrance_enabled_guard:
                # next_timestamp param to evalTerm is None because enabled-guards
                # are evaluated only at section entrance time.
                if not chcast(bool,
                              self.evalTerm(nar.entrance_enabled_guard, ctx)):
                    entrance_enabled = False

            if entrance_enabled:
                if isinstance(nar, EnvNextActionRule):
                    entrance_enabled_env_action_rules.append(nar)
                else:
                    assert isinstance(nar, PartyNextActionRule)
                    if nar.deontic_keyword == 'may' or nar.deontic_keyword == 'should':
                        entrance_enabled_permissions.append(nar)
                    elif nar.deontic_keyword == 'must':
                        entrance_enabled_strong_obligs.append(nar)
                    elif nar.deontic_keyword == 'obligation-options-include':
                        entrance_enabled_weak_obligs.append(nar)
                    else:
                        assert False

        # CASE 0a: Exactly one must-later:
        # if len(entrance_enabled_future_strong_obligs) > 0:
        #     assert (len(entrance_enabled_future_permissions) == len(entrance_enabled_weak_obligs) ==
        #             len(entrance_enabled_strong_obligs) == len(entrance_enabled_permissions) ==
        #             len(entrance_enabled_env_action_rules) == 0), "usage of must-later currently very restricted"
        #     fo = entrance_enabled_future_strong_obligs[0]
        #     self.future_obligations.append(fo)
        #     return EventOk(True)
        #
        # if len(entrance_enabled_future_permissions) > 0:
        #     assert (len(entrance_enabled_future_strong_obligs) == len(entrance_enabled_weak_obligs) ==
        #             len(entrance_enabled_strong_obligs) == len(entrance_enabled_permissions) ==
        #             len(entrance_enabled_env_action_rules) == 0), "usage of may-later currently very restricted"
        #     fp = entrance_enabled_future_permissions[0]
        #     self.future_permissions.append(fp)
        #     return EventOk(True)

        # CASE 1: 1 or more entrance-enabled strong obligations
        if len(entrance_enabled_strong_obligs) > 1:
            contract_bug(
                "There are multiple active strong obligations. This is an error."
            )
            return ContractFlawedError()
        if len(entrance_enabled_strong_obligs) == 1:
            if len(entrance_enabled_weak_obligs) > 0 or len(
                    entrance_enabled_permissions) > 0 or len(
                        entrance_enabled_env_action_rules) > 0:
                contract_bug(
                    "There is exactly one active strong obligation, but there is also at least one "
                    "active permission, environment-action, or weak obligation. This is an error."
                )
                return ContractFlawedError()
            else:
                assert isinstance(entrance_enabled_strong_obligs[0],
                                  PartyNextActionRule)
                if not self.current_event_compatible_with_enabled_NextActionRule(
                        entrance_enabled_strong_obligs[0]):
                    contract_bug(
                        f"There is exactly one active strong obligation\n"
                        f"\t{entrance_enabled_strong_obligs[0]}\n"
                        f"but it is not compatible with the current event\n"
                        f"\t{event}\n"
                        f"Environment looks like:\n"
                        f"{self.environ_tostr()}")
                else:
                    time_constraint_ok = self.evalTimeConstraint(
                        entrance_enabled_strong_obligs[0].time_constraint, ctx)
                    # print("time_constraint_ok", time_constraint_ok)
                    if time_constraint_ok:
                        return EventOk()  #(None,None)
                    else:
                        return BreachResult({
                            event.role_id
                        }, f"Strong obligation of {event.role_id} expired or not yet active. See rule {entrance_enabled_strong_obligs[0]}\n with time constraint {entrance_enabled_strong_obligs[0].time_constraint}"
                                            )

        # CASE 2: 0 entrance-enabled strong obligations (SO action-rules)
        present_enabled_weak_obligs: List[NextActionRule] = list(
            filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx),
                   entrance_enabled_weak_obligs))
        present_enabled_permissions: List[NextActionRule] = list(
            filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx),
                   entrance_enabled_permissions))
        present_enabled_env_action_rules: List[NextActionRule] = list(
            filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx),
                   entrance_enabled_env_action_rules))

        # present_enabled_nonSO_action_rules : Iterator[NextActionRule] = chain(
        #     present_enabled_permissions.__iter__(),
        #     present_enabled_weak_obligs.__iter__(),
        #     present_enabled_env_action_rules.__iter__())
        present_enabled_nonSO_action_rules: List[
            NextActionRule] = present_enabled_permissions + present_enabled_weak_obligs + present_enabled_env_action_rules
        # print("present_enabled_nonSO_action_rules", present_enabled_nonSO_action_rules)

        # CASE 2a: `event` compatible with exactly one present-enabled non-SO action_rule
        # for x in present_enabled_nonSO_action_rules:
        #     print("maybe...", self.current_event_compatible_with_enabled_NextActionRule(x) )

        compatible_present_enabled_nonSO_action_rules = list(
            filter(
                lambda x: self.
                current_event_compatible_with_enabled_NextActionRule(x),
                present_enabled_nonSO_action_rules))
        if len(compatible_present_enabled_nonSO_action_rules) == 1:
            return EventOk()  #(None,None)

        if len(compatible_present_enabled_nonSO_action_rules) == 0 and len(
                present_enabled_weak_obligs) == 0:
            return BreachResult([
                event.role_id
            ], f"{event.role_id} tried to do an action {event.action_id} that no rule in the current section {self.last_or_current_section_id} permits them to do."
                                )
            # assert len(present_enabled_permissions) > 0 or len(present_enabled_env_action_rules) > 0
            # self.evalError("TODO: correctly assign breach in this case! And check that there's not a problem with role ids")

        # print("compatible_present_enabled_nonSO_action_rules", compatible_present_enabled_nonSO_action_rules)

        # CASE 2b: `event` compatible with more than one present-enabled non-SO action-rules
        # ...This is actually ok as long as timeConstraintsPartitionFuture isn't used.
        if len(compatible_present_enabled_nonSO_action_rules) > 1:
            return EventOk()  #(None,None)

        # CASE 2c: `event` compatible with 0 present-enabled non-SO action-rules
        # This is a breach. We need to determine what subset of the roles is at fault
        # You're at fault if you had an entrance-enabled weak obligation (which now expired,
        # from previous cases).
        breach_roles = filter(
            lambda r: len(
                list(
                    filter(lambda c: c.role_id == r,
                           entrance_enabled_weak_obligs))) > 0, self.top.roles)

        # print("event:", event)
        # print("vars:", self.gvarvals)
        # print("entrance_enabled_permissions", mapjoin(str,entrance_enabled_permissions,', '))
        # print("present_enabled_permissions", list(present_enabled_permissions))
        # print("present_enabled_weak_obligs", list(present_enabled_weak_obligs))
        # print("present_enabled_env_action_rules", list(present_enabled_env_action_rules))
        # print()

        return BreachResult(
            list(breach_roles),
            f"{list(breach_roles)} had weak obligations that they didn't fulfill in time."
        )