Preprocessor include guards (with preprocessorV2)

0 votes
asked May 1 in Wanted features by StefanDensow (280 points)

Hi,

the default behavior of !include changed with preprocessorV2 to that of #include as in the C/C++ preprocessor. There, it is common practice to use include guards to prevent multiple inclusion of the same file. Multiple inclusions can also be harmful in PlantUML because they can easily lead to (infinite) cyclic inclusion.

Therefore I'm trying to replicate include guards with PlantUML preprocessorV2, see the example (render _view_seq.puml): https://github.com/zimtkeks/zimtkeks.github.io/tree/plantuml-include-guards/static/2019-03-08-plantuml-diagrams-without-redundancy (as ZIP)

The details are in _common_seq.iuml:10. I'm trying to check whether a variable has been already defined, whose name is composed of function arguments. If not, I define it. PlantUML gives me no useful error message. I'm not sure if it is at all possible to define a variable like this. But since the C/C++ preprocessor can do that, preprocessorV2 should be able to that as well (IMHO :-)

So, how can we get that to work?

Thanks!

1 Answer

+1 vote
answered May 1 by plantuml (198,780 points)

Two points :

  • We are probably going to enhance preprocessorV2 to have some !include_once primitive that will automatically implements include guards. Then we will also implements !include_many primitive that will allow multiple inclusion (as in the current preprocessor). The big question is now what should be the default behaviour of !include ? Should it act like !include_once or like !include_many ? We are probably going to turn it to !include_once behaviour, like in the current preprocessor. Any though about it ?
  • Right now, you cannot replicate include guards with PlantUML preprocessorV2. But the need is here. There is a function %variable_exists documented in http://wiki.plantuml.net/site/preprocessing-v2 but it's not enough because you cannot dynamically create variables. So we are going to add %set_variable_value and %get_variable_value to extend this. Even if you won't need it since include guard will also be implemented
Next beta (around may the 8th) will have those features.
Thanks for your tests, and all suggestions are still welcome !
commented May 2 by StefanDensow (280 points)
Thanks for the reply!

To 1: I like the !include_once/!include_many partitioning, because it makes things explicit. Cyclic inclusions always happen accidentaly because things are _not_ explicit to user. These explicit directives motivate the user to make a conscious decision on the inclusion mode. However, deciding on a default for !include is hard, because it's wrong about half of time, no matter what :-)
Did you consider to remove !include? If that's not an option, I would default to include_many because that's the expected behavior by users familiar with the C/C++ preprocessor. But that depends heavily on your user base, which you know better than me.

To 2: I realized that I was asking for more than may be justified. I was trying to generate a variable dynamically, which the C/C++ preprocessor can't do. There, each header file contains a manual #define __INCLUDED_MYHEADERFILE_H, which I want to avoid in my code, if possible. However, if that's possible with %set/get_variable_value, that would be awesome. I'll try that once the beta is here.
If that works, I would prefer these self-built include guards because I can control the else-option, i.e. I can display a custom note in the diagram, which simply states that the requested block is _not_ included because was included before in the diagram, and that the user should look for the block further up in the diagram. I can't do that with !include_once. There, it looks no different in the diagram whether the block is not included (because it was included before) or if the block doesn't even exist.
commented May 6 by plantuml (198,780 points)
With last beta http://beta.plantuml.net/plantuml.jar you can have :

@startuml
%set_variable_value("$foo"+"dummy", "42")

!ifndef $foodummy
Alice -> Bob : you should not see that
!endif
Alice -> Bob : expected 42 $foodummy
Alice -> Bob : expected 42 %get_variable_value("$"+"foo"+"dummy")

@enduml

We have also implemented !include / !include_once / !include_many
commented May 9 by StefanDensow (280 points)
Hi, I updated the code (see first post) to this:

!unquoted function $CALL_METHOD($FROM_CLASS, $TO_CLASS, $FUNC_NAME, $ARG = "", $RETURN_LABEL = "")
    $FROM_CLASS -> $TO_CLASS: $FUNC_NAME($ARG)
    activate $TO_CLASS

            !$GUARD_NAME = "$INCLUDED_" + $TO_CLASS + "_" + $FUNC_NAME
            !if %variable_exists($GUARD_NAME)
                note over $TO_CLASS: ""$GUARD_NAME" exists, don't include ""$FUNC_NAME"
            !else
                %set_variable_value($GUARD_NAME, 42)
                note over $TO_CLASS: ""$GUARD_NAME"" doesn't exist, include ""$FUNC_NAME""
                !include $TO_CLASS.iuml!$FUNC_NAME
                note over $TO_CLASS: end of ""$FUNC_NAME""
            !endif

    $FROM_CLASS <-- $TO_CLASS: $RETURN_LABEL
    deactivate $TO_CLASS
!endfunction

However, that doesn't work as intended. With 1.2019.06beta16, %variable_exists() always returns false, so the cyclic inclusion is only stopped by the internal include guard of the !include directive.
How can I fix that?

A second thing: The github code (https://github.com/zimtkeks/zimtkeks.github.io/blob/plantuml-include-guards/static/2019-03-08-plantuml-diagrams-without-redundancy/_common_seq.iuml#L11) also contains an attempt to use %file_exists() in the function above. However, when I enable the corresponding lines, PlantUML complains about "Unknown variable name $GUARD_NAME, which I don't understand. What am I missing?

Thanks for your help!
commented May 9 by plantuml (198,780 points)
Point 1 is an interesting issue.
A variable can be either local (to a function) or global. When a variable is local, it is distroyed when you exit the function. That was the issue : %set_variable_value() was creating a local variable.
This is fixed in last beta http://beta.plantuml.net/plantuml.jar (beta 18)
%set_variable_value() now creates a global variable. This should work better.
Point 2 remains to be investigated
commented May 9 by plantuml (198,780 points)
About point2, the last example is working with beta 18

@startuml

!define NOTE_TODO(PARTICIPANT) note over PARTICIPANT #hotpink: TODO

!unquoted function $CALL_METHOD($FROM_CLASS, $TO_CLASS, $FUNC_NAME, $ARG = "", $RETURN_LABEL = "")
    $FROM_CLASS -> $TO_CLASS: $FUNC_NAME($ARG)
    activate $TO_CLASS


            !$GUARD_NAME = "$INCLUDED_" + $TO_CLASS + "_" + $FUNC_NAME
            !if %variable_exists($GUARD_NAME)
                note over $TO_CLASS: ""$GUARD_NAME" exists, don't include ""$FUNC_NAME"
            !else
                %set_variable_value($GUARD_NAME, 42)
                note over $TO_CLASS: ""$GUARD_NAME"" doesn't exist, include ""$FUNC_NAME""
                note over $TO_CLASS: end of ""$FUNC_NAME""
            !endif

    $FROM_CLASS <-- $TO_CLASS: $RETURN_LABEL
    deactivate $TO_CLASS
!endfunction

$CALL_METHOD(foo1, foo2, method)
@enduml

But I am not sure wether the issue is solved or not with beta18. Could you provide a short snipset showing the issue if it's still there ? Thanks
commented May 10 by StefanDensow (280 points)
With beta18, %variable_exists() now works as expected, thanks!

I was not aware that the preprocessor has local/global variables, I didn't find anything on local/global variables in http://plantuml.com/preprocessing-v2 or http://wiki.plantuml.net/site/preprocessing-v2 .
If %set_variable_value() declares a global variable, how is a local variable created? Via !$var = 42?
That would imply that a variable names, that is constructed at runtime can only be declared a global variable (since this dynamic declaration requires %set_variable_value()), while a local variable must have 'static' name.
But then again, maybe this composing-variable-names-at-runtime stuff is too niche to justify the effort of making that a first-class feature, that is consistent everywhere. I don't know :-)

So the present state works for me. But I would prefer more evident scoping, e.g. also with a naming convention to differentiate local/global variables (like G_GLOBAL, l_local or $GLOBAL, $local).

------------------

I also tried %file_exists() again but I still get "Unknown variable name $GUARD_NAME". As a test I added the most simple %file_exists() statement to _view_seq.puml - and it fails :(
I would expect that to work, could you have a look at https://github.com/zimtkeks/zimtkeks.github.io/tree/plantuml-include-guards/static/2019-03-08-plantuml-diagrams-without-redundancy ?
Thanks!
commented May 10 by plantuml (198,780 points)
> I was not aware that the preprocessor has local/global variables,
>
It's our fault...
We've just put some info on http://plantuml.com/preprocessing-v2

There is also a new beta (beta20) http://beta.plantuml.net/plantuml.jar
It fixes some bug relateds to variable management.
And it slightly changes the behaviour of %set_variable_value() which now always create a global variable.

We've patched some of your code in http://beta.plantuml.net/static_V1.zip to make _view_seq.puml working.

When including a block like :
@startuml(id=COMMON)
!include _common_seq.iuml
%set_variable_value("$TaxiManager_DELEGATE", "SpeedyCab")
' other file-local stuff goes here
@enduml

You do have to use %set_variable_value() because if you include this from a function, it will otherwise create a local variable, which is not what you want to do.
If you turn -verbose on command line, you'll see some info about variable creation.

We have to turn $CALL_METHOD() as a quoted function, because it's the only way to have something like:
$CALL_METHOD("TaxiManager", $TaxiManager_DELEGATE, $TaxiManager_DELEGATE)

The writting is more verbose, but now at least it's clear when you use a hard coded string or a variable.

There are probably some other bugs or some other features that might be added in the beta, so we are fully interested in your tests and feedback.
commented May 14 by StefanDensow (280 points)
Oh cool, thanks for patching my code! I didn't migrate this code yet to preprocessorV2 and I wasn't aware that the DELEGATE would conflict with with !unquoted..

Also thanks for mentioning -verbose, wasn't aware of that either, looks really helpful.

Did you have a chance to look at the %file_exists() issue? It still happens in beta22.
...