Plcgen expression generation
I have been writing an expression generator for plcgen for the past weeks by roughly porting the cif2plc transExpr
.
I have completed that function with some support code around it last week but not tried it yet.
Looking at it again from a larger distance I think it needs another iteration with more precision in what it should do and what is allowed. For this I'd like your input.
shows the global structure of the expression generator that I have in mind (although it's much larger than just "expressions").
You create an expression generator for all expressions in some context. A POU could be such a context, or an event / input / output / update / etc function or code block.
- The target is needed to steer the generator away from unsupported constructs, eg no
LOG10
call when the target doesn't have it, conversion to reals around a call if the target doesn't have an integer version of a function, etc. - The var-context defines how variables work. In automata we have 2 sets of state variables (current and new), while in internal functions we have only 1 set of variables.
- At the left, a
CIF expression
is given to the generator. It also needs to know if the expression is evaluated at the left or the right side of the assignment. Finally, ashape
seems useful so you can state what answer you want from the generator (see also below). - At the right, the result of the generator. The PLC expression is the main result, but depending on the CIF expression it may need to generate code as well that needs to be executed before evaluating the expression. Finally there is the option of generating a PLC function to hide the generated code. cif2plc does this all the time. I am not sure this is a good idea. Most functions are small I think and it complicates flow analysis to optimize the generated code if we ever reach that point (which I sincerely hope).
- The generator also needs to know how to read and write CIF state and convert CIF data like enumeration literals etc. At the bottom-left this is handled by a separate class that contains this information. The advantage of making it separate is that you can insert proxies in the traffic there, and eg track which variables were queried by the generator. You could also make a proxy that creates a new variable for each requested value. That would immediately give you a set of parameters for a POU as well as their values needed at the point of calling that generated function.
- Finally at the bottom-right, if the generator needs to branch out to generating code, it often needs temporary storage to communicate between different statements and between statements and the final answer.
Shape
The principal output of the generator is a PLC expression. It must be evaluated at PLC runtime to get the value of the CIF expression at that point in time.
However, CIF is more expressive in its expressions than a PLC and we want to allow those CIF expressions as much as possible. Thus, the generator may also generate PLC statements that need to be executed before the expression evaluation. The other escape route here is generating a function that executes those statements. This is what cif2plc does. I am currently avoiding those but they may be needed.
The shape provided as input to the generator can have 3 values currently:
-
value is the common shape. It says you want a PLC expression, and it may also return code with it. Such code may use variables (discussed elsewhere).
-
stored means the result should be stored in a (provided) variable. The benefit of providing this information is that the generator can take that desire into account when it needs to generate code and store temporary results into variables. It can avoid cases like
x := if a then 3 else 5
getting translated toIF a' THEN tmp := 3 ELSE tmp := 5 END_IF; x' := tmp;
-
literal is for variable initialization in a symbol table, and this area is very unclear to me, as in I don't know what the generator may or may not generate here. Some assistance would be appreciated.
- May it generate code? If yes, may it use temporary variables?
- May it use state variables?
- May it perform a function call?
In other words what limitations exist here?
Can we avoid having this? (It seems to have many restrictions and thus it's unlikely to allow some common but complicated CIF expressions.)
Questions
-
What do you think about this idea?
-
In variable initialization, some PLCs store the initial value in a symbol table. I know that only that spot supports multi-value literals (ie arrays and structs). This would be a 3rd value for the
var-context
I think.- Can you call a function POU at that point?
- Do you have variables at that point?
Assuming both answers are "no", is that universal or should I assume this holds for all PLCs? It does mean we get limitations in CIF what you can express as initial value.
The other option is to initialize at start-up of course. Then you never generate multi-level literals. Is that a feasible option for all PLCs? (mostly as in, how does that work out form siemens and abb?)
-
Is the "shape" sufficient? Does it need more differentiation at this time?
With sufficient limitations eg in the "literal" shape, I think we need to analyse expressions for being feasible to translate during pre-checking. You cannot start with aiming for a literal, and 3 levels down in the expression find you need to branch out to generating code because there is an "if" expression there.
-
The current idea wrt continuous variables is to limit them to being timers and implement them as timer function blocks. That is more in line with a normal PLC implementation. I have however no idea how that works out in expression context. How do you access a timer value from eg a POU
-
There are no input/output variables here, which means they are currently hidden in the CIF state. That is, they are queried and modified as normal CIF variables. My guess is that is currently a good solution.
-
Other thoughts?
I am sure I missed a few questions, but these will pop up later.
Addresses #397 (closed) #418 (closed)