QM  4.0.1
Conditional Compilation

Often, the models created with QM are intended for entire families of related products (product lines), as opposed to a single end-product only. The designers of such product lines often find that big pieces of the model, such as state machines, can be similar in all individual products, except for some parts that vary.

One popular way to manage the variations at the C/C++ code level is through conditional compilation, meaning that blocks of code are surrounded by the preprocessor statements #if<expr> / #endif and are actually compiled only when the preprocessor expression <expr> evaluates to true at compile time.

QM inherently supports inserting any user code into file-templates, which means that you can very easily surround blocks of code of your choice (including the Code-Generation Directives ) with conditional compilation statements.

However, to achieve a fine-granularity conditional compilation inside classes you need to use the special support for conditional compilation built-into QM. This section describes how to add conditional compilation statements for individual attributes, operations, states, and transitions, including regular transitions, internal transitions, transitions with guards, and CATCH_ALL transitions.

Note
The conditional compilation is supported for all QP framework types (qpc, qpcpp, and qpn) as well as all state machine types (subclasses of QMsm and QHsm base classes in QP5).

Applying Conditional Compilation

To mark a state or a transition as conditionally compiled, you need to append a question mark (?) followed by the preprocessor expression to the name of the attribute, state, or the transition in the Property Editor. The following two screen shots provide examples for a conditional state and conditional transition, respectively. The highlighted text after the state name and transition trigger, respectively, corresponds to the pre-processor expression.

ce_cond_state_prop.png
Setting the Name Property of a Conditionally Compiled State


ce_cond_tran_prop.png
Setting the Name Property of a Conditionally Compiled Transition

Pre-processor Expression Syntax

The conditional expression that you add to the name property of a state or transition can be any valid expression accepted by the C/C++ pre-processor (excluding the ?, obviously).

For example, a conditional compilation expression ?def FOO_BAR will lead to the generation of the following pre-processor statements around a declaration or definition of a state:

#ifdef FOO_BAR
//. . .
#endif /* def FOO_BAR */
Note
Please note that the QM code generator specifically does not append any spaces after the #if, so that statements of the form #ifdef FOO_BAR and #ifundef FOO_BAR can be easily generated from the conditions ?def FOO_BAR and ?undef FOO_BAR, respectively.

However, your pre-processor expressions might be also more complex and might include logical operators &&, ||, etc. For example, the condition ? (defined FOO_BAR) && (VERSION >= 531) will will lead to the generation of the following pre-processor statements:

#if (defined FOO_BAR) && (VERSION >= 531)
//. . .
#endif /* (defined FOO_BAR) && (VERSION >= 531) */

Rendering Conditional States/Transitions

As shown in the screen shots below, the QM™ tool will give you a visual indication by displaying the question mark (?) in front of the state name or the transition trigger in the State Diagram as well as in the Model Explorer :

ce_cond_state.png
Rendering of a Conditionally-Compiled State


ce_cond_tran.png
Rendering of a Conditionally-Compiled Transition

Conditional Compilation and Model Correctness

The QM™ code generator applies the conditional compilation consistently and in a way that most developers should find intuitive.

For example, for a conditionally-compiled state, both the state declaration and state definition will be surrounded by the corresponding #if<expr> / #endif statements. Similarly, all substates of a conditionally-compiled state will be surrounded by the same #if<expr>/#endif statements as the superstate. The QM code generator will even correctly nest the #if/#endif statements for nested states, as one would expect. (NOTE: The following sub-sections describe how conditional compilation is applied in various special cases).

Attention
However, the QM™ code generator is limited in that it does not "know" the settings of the pre-processor macros used for the conditional expressions. Consequently, QM can not check the completeness of a whole state-machine with some states or transitions conditionally-compiled. For example, the QM code generator will not report errors for transitions targeting conditionally-compiled states, even though such states might be "compiled out" in the actual code. In most of such cases, the compilation of the code will fail (so you will know that your model is inconsistent), but avoiding such inconsistencies in the model is considered the responsibility of the developer (you), not the QM™ tool.

Conditional States

This section describes the special cases of conditionally-compiled states.

Nesting Conditional States

If both a superstate and some of its substates are conditionally compiled (each with their own conditional expressions), the QM code generator will correctly nest the #if/#endif statements around the substates.

ce_cond_nest.png
Nested Conditional States

For example, if a superstate state1 with a condition ?def FOO_BAR has a substate state2 with a condition ?(VERSION > 531), QM will generate the following conditional compilation statements for state1 and state2.

typedef struct {
/* protected: */
QMsm super;
/* private state histories */
#ifdef FOO_BAR
QMState const *his_state1;
#endif /* def FOO_BAR */
} Test_QMsm;
#ifdef FOO_BAR
#if(VERSION > 531)
// declaration or definition of state2
#endif /* (VERSION > 531) */
#endif /* def FOO_BAR */

Conditional States with History

If a conditionally compiled state has a history, the QM code generator will correctly surround the declaration and the initialization of the state history variable. This will prevent allocating space and thus wasting memory (RAM) for the history attribute, in case the corresponding state is "compiled out".

ce_cond_hist.png
Conditional State with History

For example, if state1 with a condition ?def FOO_BAR has a history connector, QM will generate the following conditional compilation statements around the declaration and initialization of the his_state1 history attribute.

/*${AOs::Test_QMsm} .......................................................*/
typedef struct {
/* protected: */
QMsm super;
/* private state histories */
#ifdef FOO_BAR
QMState const *his_state1;
#endif /* def FOO_BAR */
} Test_QMsm;
/*${AOs::Test_QMsm::SM} ...................................................*/
static QState Test_QMsm_initial(Test_QMsm * const me, QEvt const * const e) {
// . . .
#ifdef FOO_BAR
me->his_state1 = &Test_QMsm_state3_s;
#endif /* def FOO_BAR */
return QM_TRAN_INIT(&tatbl_);
}

Conditional Transitions

A conditional compilation condition appended to the trigger name works for all transition types supported by QM, including regular transitions, internal transitions, transitions with guards, multi-trigger transitions, and CATCH_ALL transitions. The following sub-sections describe the special cases for conditionally-compiled transitions.

Due to the way transitions are implemented in QP, there is no declaration of transition, so the conditional compilation applies only to the definition of a transition inside a state-handler function. The following snippet of code shows the conditional transition TRIG2 with the condition ? (defined FOO_BAR) && (VERSION >= 531) at line 6:

/* ${AOs::Test_QMsm::SM::state1} */
static QState Test_QMsm_state1(Test_QMsm * const me, QEvt const * const e) {
QState status_;
switch (e->sig) {
#if (defined FOO_BAR) && (VERSION >= 531)
/* ${AOs::Test_QMsm::SM::state1::TRIG2} */
case TRIG2_SIG: {
action2();
// action associated with TRAN2
break;
}
#endif /* (defined FOO_BAR) && (VERSION >= 531) */
default: {
status_ = QM_SUPER();
break;
}
}
(void)me; /* avoid compiler warning in case 'me' is not used */
return status_;
}
Note
Conditionally-compiled transitions can be attached to conditionally-compiled states. In that case the #if/#endif preprocessor statements will naturally nest, which will produce the expected result. (A transition will only be "compiled in" when both the conditional expression for the sate as well as the actual conditional expression for the transition evaluate to true).

Multi-Trigger Conditional Transitions

A multi-trigger transition is a transition labeled with a (comma-separated) list of triggers, any one of which can trigger the transition. The conditional compilation works for this type of transition as well. In that case, you simply append the list of triggers with the question-mark (?) and the pre-processor expression. The following screen shot illustrates the situation:

ce_cond_multi.png
Multi-Trigger Conditional Transition

CATCH_ALL Conditional Transitions

The special CATCH_ALL transition is triggered by any not-explicitly handled trigger by a given state (so it "catches" all triggers). The conditional compilation works for this type of transition as well. In that case, you simply append the special CATCH_ALL pre-defined trigger with the question-mark (?) and the pre-processor expression. The following screen shot illustrates the situation:

ce_cond_catch.png
CATCH_ALL Conditional Transition

Due to the CATCH_ALL transitions are implemented in QP, the preprocessor #if statement has also the #else branch in that case, so that there is no "catch all" behavior when such transition is "compiled out". The following code snippet illustrates the conditional transition in a QMsm sub-class (please note the #else conditional branch at line 23):

/* ${AOs::Test_QMsm::SM::state1} */
static QState Test_QMsm_state1(Test_QMsm * const me, QEvt const * const e) {
QState status_;
switch (e->sig) {
// triggers ...
default: {
#ifdef FOO
/* ${AOs::Test_QMsm::SM::state0::CATCH_ALL} */
static struct {
QMState const *target;
QActionHandler act[3];
} const tatbl_ = { /* transition-action table */
&Test_QMsm_state1_s, /* target state */
{
Q_ACTION_CAST(&Test_QMsm_state0_x), /* exit */
Q_ACTION_CAST(&Test_QMsm_state1_e), /* entry */
Q_ACTION_CAST(0) /* zero terminator */
}
};
action1();
status_ = QM_TRAN(&tatbl_);
#else
status_ = QM_SUPER();
#endif /* def FOO */
break;
}
}
return status_;
}


The following code snippet illustrates the conditional transition in a QHsm sub-class (please note the #else conditional branch at line 31):

/*${AOs::Test_QHsm::SM::state1} ............................................*/
static QState Test_QHsm_state1(Test_QHsm * const me, QEvt const * const e) {
QState status_;
switch (e->sig) {
/* ${AOs::Test_QHsm::SM::state1} */
case Q_ENTRY_SIG: {
entry1();
status_ = Q_HANDLED();
break;
}
/* ${AOs::Test_QHsm::SM::state1} */
case Q_EXIT_SIG: {
exit1();
status_ = Q_HANDLED();
break;
}
// triggers ...
default: {
#ifdef FOO
/* ${AOs::Test_QHsm::SM::state1::CATCH_ALL} */
if (e->sig < Q_USER_SIG) {
status_ = Q_SUPER(&QHsm_top);
}
else {
action1();
status_ = Q_TRAN(&Test_QHsm_state2);
}
#else
status_ = Q_SUPER(&QHsm_top);
#endif /* def FOO */
break;
}
}
return status_;
}



Next: QM Compiler (qmc)