|
Model synthesis is a technique that shares some of the properties of both preprocessing and metaprogramming. It is a process that allows the engineer to inject programmable code fragments into the operation body.
The need for such a process comes from the fact that operation bodies are introduced and kept in the so-called held state, which means that they are not evaluated until late in the grammar processing stage. The following example demonstrates that this is indeed the case:
The grammar tree displayed above contains two literals as separate entities and the plusT operation that refers to them. It might seem like an obvious opportunity to simplify it and perform the addition immediately (and just once), so that the actual statement in the body is just:
but keeping the whole operation body unevaluated is intentional, as it allows to construct models without any prior assumptions with regard to how compilers work. This approach also allows users to retain complete control on such aspects by means of preprocessors, which can selectively perform any needed simplifications.
Still, this unevaluated format is not always convenient, for example here the intention was to benefit from Mathematica’s computational abilities and compute the constant value in-line:
There is a grammar error in the above code and the grammar reduction process gets stuck with the following state:
This tree shows that FMT does not know what to do with Factorial (it is not a supported operation) and consequently how to proceed with the assignment.
The model synthesis is a method of selective evaluation of chosen model parts - the part of the model that needs to be forcibly evaluated has to be wrapped in the Synthesize symbol, for example:
The grammar was accepted and the complete tree shows that 5! was indeed evaluated and the result (120) was used in the model in its place:
The Synthesize symbol is not a standard Wolfram function (it is defined in “FMTSymbols`”), but for consistency with other model building blocks like control structures its name starts with capital, which intentionally breaks the recommended naming convention.
The first example from this chapter can be rewritten with this expression:
to force evaluation the evaluation and the use of 3 as a single literal.
The following example is not just a funny exercise, but reveals some important properties of model synthesis:
If the intent was to pick some constant value at random, the goal was achieved, but with a trap:
The same grammar does not necessarily look the same when displayed again:
Similarly, the generated code (here in C++) can be different each time:
#include "my_package.h" #include <cstdint> using namespace my_package; // Operation definitions. void my_package::op() { std::int32_t temp; temp = 51; }
another try:
#include "my_package.h" #include <cstdint> using namespace my_package; // Operation definitions. void my_package::op() { std::int32_t temp; temp = 69; }
The above evaluations demonstrate that model synthesis is performed not just once, when the operation is defined, but every time the grammar is needed. This means that careless models can lead to inconsistent results, as correctness checks and code generators can see essentially different models.
In order to solve this particular problem, the possible approach is to capture the random value and inject it into the model from the external variable:
Now, as long as the myRandomValue symbol does not change, all processing will rely on the same value.
Model synthesis is not only useful for computing constants - complete expressions or even control structures can be computed and injected into the model with this technique, which can complement metaprogramming capabilities described in the previous chapter. For example, 100 data objects generated there with the use of metaprogramming (example repeated):
can be initialized in an operation declared and defined this way:
The generated code (in C++) for this operation (shortened!), looks like:
void my_package::init_signals() { signal1 = 0; signal2 = 0; signal3 = 0; // ... signal99 = 0; signal100 = 0; }
Again, in a real project signal names would be obtained from a configuration file or perhaps from an external database.
Model synthesis can require some knowledge of how different operations are internally represented to avoid the trap of unintended evaluation of computed expressions - for example, above the assignment was directly synthesized as assignmentT, as the use of standard Set would cause unintended effects with all involved symbols. Control statements can hide their own, similar traps. Fortunately, it is not necessary to follow the internal representation in all its details, as they will be filled as necessary in normal grammar processing steps. Above, it was necessary to use assignmentT instead of Set, but it was not necessary to refer to objectLhsT or literalT - these details are filled in automatically.
Another example shows how model synthesis can be used to generate code that fills a short array with integer values corresponding to a sine waveform for 8-bit DAC. The list of samples can be obtained with the following expression:
These samples can be validated graphically:
The appropriate model definitions are as follows, starting with the data type for the array object:
The sample array data object can be defined as:
And, finally, declaration and definition of the sample initialization operation:
The final generated code (again, in C++) for the initialization function is:
void my_package::init_samples() { samples[0] = 128; samples[1] = 167; samples[2] = 202; samples[3] = 230; samples[4] = 248; samples[5] = 255; samples[6] = 248; samples[7] = 230; samples[8] = 202; samples[9] = 167; samples[10] = 128; samples[11] = 88; samples[12] = 53; samples[13] = 25; samples[14] = 7; samples[15] = 1; samples[16] = 7; samples[17] = 25; samples[18] = 53; samples[19] = 88; }
Of course, the combination of metaprogamming with model synthesis allows to parameterize the sample array length as well.
As a final note, it is important to state that model synthesis is resolved before calling grammar preprocessors, so that synthesized models can be further processed according to user-defined rules.
Previous: Metaprogramming, next: Grammar checks
See also Table of Contents.