Skip to content

Conditional Code Generation vs Generation of Conditional Code

vranken edited this page Nov 29, 2024 · 2 revisions

In most use cases and particularly when working with attributes the code generation process will need to behave conditionally on the given data. Two techniques can be distinguished:

  • The code generator behaves conditionally and generates either the one or the other code pattern. The generated code unconditionally has a specific behavior
  • The code generator unconditionally generates a code pattern, which behaves conditionally. This technique is comparable to the former one if the condition is a C preprocessor condition; all behaviors are apparent in the source file but at run time the generated code unconditionally has a specific behavior, too

The former technique is the better one as it means more meaningful, better readable code but it can not always be applied. The template engine has the restriction that only Boolean expressions can be used for conditional code generation. It is e.g. impossible to test the value of a numeric data element. It is easily possible to generate different code for received frames opposed to sent frames (in the data model the object Frame has the Boolean fields isReceived and isSent) but it is not directly possible to generate another specific behavior for a frame of 10 ms send period as for a frame of 100 ms period time.

Conditional code generation

The first technique doesn't need to be explained in detail, please look into the manual of the StringTemplate V4 engine; it's all based on the construct <if(booleanExpression)>. Please note, that

  • the Java null is a Boolean false to StringTemplate V4
  • the presence of a data element is a Boolean expression for StringTemplate V4.

Both patterns are heavily used by the data model. Many fields are null by default and only reference meaningful Java objects if they apply. A list of objects, which is logically empty, will rather be a null value than an empty Java collection. An unset comment will rather be a null value than the empty string, etc. Here's an example how the code generator can conditionally handle frames with and without multiplexed signals:

<if(Pdu.muxSignalAry)>
switch(<Pdu.muxSelector.name>)
{
    <Pdu.muxSignalAry:{muxS|case <muxS.muxValue>: return <muxS.s.name>;}>
    default: assert(false); /* Invalid multiplexer switch value */
}
<else>
    /* This PDU doesn't have multiplexed signals. No action needed here. */
<endif>

The presence of a data element is used with Java Map objects. A map may be queried for any key; the template engine returns a Boolean false if the map doesn't contain an entry for the key. In particular, this is used for attributes, please find a detailed discussion at [Attributes in the network database]. Furthermore, Special Signals are built on maps: In many environments all or most PDUs contain signals with a common name and meaning, e.g. a signal CRC, which contains a check sum or SQC, which contains a PDU sequence counter. The user can specify a list of those signals, which are identified by name during the parsing process. Each PDU, which has a signal of specified name (or name pattern) will have a map containing the Signal object. (The key is a user-specified identifier.) In the template it can easily be tested if the PDU has a specific special signal. Related handling code can be generated; all fields of the Signal object can directly be accessed to do so.

Generation of conditional code

Where direct (Boolean) decisions are impossible the same code pattern needs to be generated unconditionally. However, if appropriate C preprocessor constructs are generated the effect can still be the desired: Seeing data dependent specific behavior of the code without run-time decisions, where those are not required. Let's take the send period of regular frames as an example.

The network database may have an attribute sendPeriod for (regular) frames. It be an integer value specifying the time in Milli seconds. The environment offers three tasks for sending frames at specific rates and the generated code should assign the frames to the best fitting task. Be the following code snippet part of the environment:

void sendFrameAt1ms(uint32 ID, uint8 dataAry[], uint8 DLC);
void sendFrameAt10ms(uint32 ID, uint8 dataAry[], uint8 DLC);
void sendFrameAt100ms(uint32 ID, uint8 dataAry[], uint8 DLC);

Conditional generation of frame dependent code is impossible as the attribute is neither a Boolean nor an enumeration. For generating the frame send code we can use the following snippet of a template:

extern uint8 tmpDataAry[8];
pack_<frame.name>(tmpDataAry);
#if 5 \> <frame.attribMap.sendPeriod>
sendFrameAt1ms(<frame.id>, tmpDataAry, <frame.size>);
#elif 50 \> <frame.attribMap.sendPeriod>
sendFrameAt10ms(<frame.id>, tmpDataAry, <frame.size>);
#elif 150 \> <frame.attribMap.sendPeriod>
sendFrameAt100ms(<frame.id>, tmpDataAry, <frame.size>);
#else
# error Unsupported frame period time <frame.attribMap.sendPeriod> <\\>
encountered. Code extension required
#endif

This will generate C code, which looks stupid but does what we expect it to do. Here's a hypothetic example for two frames, EngineTorque with ID=127 and t=10 ms and BatteryVoltage with ID=678 and t=80 ms:

extern uint8 tmpDataAry[8];
pack_EngineTorque(tmpDataAry);
#if 5 > 10
sendFrameAt1ms(127, tmpDataAry, 8);
#elif 50 > 10
sendFrameAt10ms(127, tmpDataAry, 8);
#elif 150 > 10
sendFrameAt100ms(127, tmpDataAry, 8);
#else
# error Unsupported frame period time 10 encountered. Code extension required
#endif

extern uint8 tmpDataAry[8];
pack_BatteryVoltage(tmpDataAry);
#if 5 > 80
sendFrameAt1ms(678, tmpDataAry, 6);
#elif 50 > 80
sendFrameAt10ms(678, tmpDataAry, 6);
#elif 150 > 80
sendFrameAt100ms(678, tmpDataAry, 6);
#else
# error Unsupported frame period time 80 encountered. Code extension required
#endif

The preprocessor condition is always predetermined true for exactly one case. Encountering such code, a human programmer would frowningly remove all other cases but for auto-generated code it's still acceptable.