From 1804d3fed55a4310c98326e947a6c44f7daabd2b Mon Sep 17 00:00:00 2001
From: Mark Olesen <Mark.Olesen@esi-group.com>
Date: Tue, 23 Nov 2021 21:03:21 +0100
Subject: [PATCH] ENH: additional #word and #message dictionary directives
 (#2276)

- use `#word` to concatenate, expand content with the resulting string
  being treated as a word token. Can be used in dictionary or
  primitive context.

  In dictionary context, it fills the gap for constructing dictionary
  names on-the-fly. For example,

  ```
  #word "some_prefix_solverInfo_${application}"
  {
      type    solverInfo;
      libs    (utilityFunctionObjects);
      ...
  }
  ```

  The '#word' directive will automatically squeeze out non-word
  characters. In the block content form, it will also strip out
  comments. This means that this type of content should also work:

  ```
  #word {
     some_prefix_solverInfo
     /* Appended with application name (if defined) */
     ${application:+_}  // Use '_' separator
     ${application}     // The application
  }
  {
      type    solverInfo;
      libs    (utilityFunctionObjects);
      ...
  }
  ```
  This is admittedly quite ugly, but illustrates its capabilities.

- use `#message` to report expanded string content to stderr.
  For example,

  ```
  T
  {
     solver          PBiCG;
     preconditioner  DILU;
     tolerance       1e-10;
     relTol          0;
     #message "using solver: $solver"
  }
  ```
  Only reports on the master node.
---
 applications/test/dictionary/testDict         |  57 ++++++-
 src/OpenFOAM/Make/files                       |   6 +-
 src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C | 136 ++++++++++++++-
 src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H |  15 +-
 .../db/dictionary/functionEntries/README      |  17 +-
 .../functionEntries/calcEntry/calcEntry.H     |   4 +-
 .../functionEntries/codeStream/codeStream.H   |   4 +-
 .../functionEntries/evalEntry/evalEntry.C     | 102 +-----------
 .../functionEntries/evalEntry/evalEntry.H     |   5 +-
 .../functionEntry/functionEntry.C             |  14 ++
 .../functionEntry/functionEntry.H             |   9 +
 .../functionEntries/ifEntry/ifEntry.C         |   6 +-
 .../functionEntries/ifEntry/ifEntry.H         |   4 +-
 .../functionEntries/ifeqEntry/ifeqEntry.H     |   4 +-
 .../includeEntry/includeEntry.H               |   4 +-
 .../includeEtcEntry/includeEtcEntry.H         |   4 +-
 .../includeFuncEntry/includeFuncEntry.H       |   4 +-
 .../functionEntries/inputMode/inputMode.H     |   4 +-
 .../message/messageDirective.C                | 145 ++++++++++++++++
 .../message/messageDirective.H                | 101 +++++++++++
 .../functionEntries/removeEntry/removeEntry.H |   4 +-
 .../functionEntries/word/wordDirective.C      | 157 ++++++++++++++++++
 .../functionEntries/word/wordDirective.H      | 100 +++++++++++
 tutorials/IO/dictionary/good-word-expand.dict |  61 +++++++
 .../laminar/movingCone/system/blockMeshDict   |   4 +-
 25 files changed, 837 insertions(+), 134 deletions(-)
 create mode 100644 src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.C
 create mode 100644 src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.H
 create mode 100644 src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.C
 create mode 100644 src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.H
 create mode 100644 tutorials/IO/dictionary/good-word-expand.dict

diff --git a/applications/test/dictionary/testDict b/applications/test/dictionary/testDict
index ab8b85d3f2b..eee439527fd 100644
--- a/applications/test/dictionary/testDict
+++ b/applications/test/dictionary/testDict
@@ -18,6 +18,8 @@ FoamFile
 #sinclude   "$FOAM_CASE/someUnknownFile"
 #includeIfPresent "$FOAM_CASE/someUnknownFile-$FOAM_CASENAME"
 
+zeroVelocity    uniform (0 0 0);
+
 internalField   uniform 1;
 
 // supply defaults
@@ -48,7 +50,7 @@ x 5;
 varName x;
 
 
-//Indirection for keys
+// Indirection for keys
 key inlet_9;
 
 
@@ -67,13 +69,17 @@ boundaryField
     inlet_5  { $inactive }
     inlet_6a { $...inactive }   // Relative scoping - fairly horrible to use
     inlet_6b { $^inactive }     // Absolute scoping
+    inlet_6c { key ${/key}; }   // Absolute scoping
+
     inlet_7  { ${inactive}}     // Test variable expansion
     inlet_8  { $inactive }
+
+    // Variable expansion for a keyword
     ${key}   { $inactive }
 
     #include "testDictInc"
 
-    outlet
+    outletBase
     {
         type            inletOutlet;
         inletValue      $internalField;
@@ -83,16 +89,36 @@ boundaryField
         y               6;
     }
 
-    // this should have no effect
+    outlet
+    {
+        $outletBase
+    }
+
+    Default_Boundary_Region
+    {
+        valueOut        $zeroVelocity;
+    }
+
+    // this should have no effect (not in scope)
     #remove inactive
 
-    inlet_7  { ${${varType}}}   // Test indirection/recursive expansion
-    inlet_8  { $active }
+    // But this should work to remove things in different scopes
+    #remove "/zeroVelocity"
+
+    inlet_7  { ${inactive} }    // Test variable expansion
+    inlet_8  { $inactive }
+
+    inlet_7a { ${${varType}} }  // Test indirection/recursive expansion
+    inlet_7b { ${${varType}} }  // Test indirection/recursive expansion
 
     #overwrite inlet_8  { type none; }
 }
 
 
+// No patterns with scoped removal
+// #remove "/boundaryField/outletB.*"
+#remove "/boundaryField/outletBase"
+
 #include "testDict2"
 
 verbatim #{
@@ -123,10 +149,25 @@ baz
     $active
 }
 
-// this should work
-#remove active
+// This should work
+#remove x
 
-// this should work too
+// This should work too
 #remove ( bar baz )
 
+// Remove a sub-dictionary entry
+#remove "/anynumber.*/value"
+
+// Removal does not auto-vivify
+#remove "/nonExistentDict/value"
+
+// Add into existing dictionary
+"/anynumber.*/someValue"  100;
+
+// Auto-vivify
+// - but currently cannot auto-vivify entries with dictionary patterns
+"/abd/someValue"    100;
+"/def/'someValue.*" 100;
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files
index 7e4850fc44c..79cd748121b 100644
--- a/src/OpenFOAM/Make/files
+++ b/src/OpenFOAM/Make/files
@@ -303,13 +303,15 @@ $(functionEntries)/calcEntry/calcEntry.C
 $(functionEntries)/codeStream/codeStream.C
 $(functionEntries)/evalEntry/evalEntry.C
 $(functionEntries)/functionEntry/functionEntry.C
+$(functionEntries)/ifEntry/ifEntry.C
+$(functionEntries)/ifeqEntry/ifeqEntry.C
 $(functionEntries)/includeEntry/includeEntry.C
 $(functionEntries)/includeEtcEntry/includeEtcEntry.C
 $(functionEntries)/includeFuncEntry/includeFuncEntry.C
 $(functionEntries)/inputMode/inputMode.C
+$(functionEntries)/message/messageDirective.C
 $(functionEntries)/removeEntry/removeEntry.C
-$(functionEntries)/ifeqEntry/ifeqEntry.C
-$(functionEntries)/ifEntry/ifEntry.C
+$(functionEntries)/word/wordDirective.C
 
 IOdictionary = db/IOobjects/IOdictionary
 $(IOdictionary)/baseIOdictionary.C
diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C
index bdb22307ae1..ad422dc7c8a 100644
--- a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C
+++ b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C
@@ -37,6 +37,7 @@ License
 // Truncate error message for readability
 static constexpr const unsigned errLen = 80;
 
+
 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
 
 namespace
@@ -55,6 +56,21 @@ inline bool validVariableChar(char c)
     return (Foam::word::valid(c) || c == '/');
 }
 
+
+inline void inplaceTrimRight(std::string& s)
+{
+    auto end = s.length();
+    if (end)
+    {
+        while (end && Foam::isspace(s[end-1]))
+        {
+            --end;
+        }
+
+        s.erase(end);
+    }
+}
+
 } // End anonymous namespace
 
 
@@ -123,7 +139,7 @@ char Foam::ISstream::nextValid()
                 // C-style comment: discard through to "*/" ending
                 if (!seekCommentEnd_Cstyle())
                 {
-                    return 0;
+                    return 0;  // Premature end of stream
                 }
             }
             else
@@ -390,17 +406,133 @@ static token::tokenType readVariable
     return token::tokenType::ERROR;
 }
 
+
+// Raw, low-level get into a string.
+// Continues reading after an initial opening delimiter (eg, '{')
+// until it finds the matching closing delimiter (eg, '}')
+static bool readUntilBalancedDelimiter
+(
+    ISstream& is,
+    std::string& str,
+    const bool stripComments,
+    const char delimOpen,
+    const char delimClose
+)
+{
+    constexpr const unsigned bufLen = 1024;
+    static char buf[bufLen];
+
+    unsigned nChar = 0;
+    unsigned depth = 1;  // Initial '{' already seen by caller
+    char c = 0;
+
+    str.clear();
+    while (is.get(c))
+    {
+        if ((str.empty() && !nChar) && isspace(c))
+        {
+            continue;  // Ignore leading whitespace
+        }
+
+        buf[nChar++] = c;
+
+        // Note: no '\' escape handling needed at the moment
+
+        if (c == delimOpen)
+        {
+            ++depth;
+        }
+        else if (c == delimClose)
+        {
+            --depth;
+            if (!depth)
+            {
+                // Closing character - do not include in output
+                --nChar;
+                str.append(buf, nChar);
+                inplaceTrimRight(str);  // Remove trailing whitespace
+                return true;
+            }
+        }
+        else if (stripComments && c == '/')
+        {
+            // Strip C/C++ comments from expressions
+            // Note: could also peek instead of get/putback
+
+            if (!is.get(c))
+            {
+                break;  // Premature end of stream
+            }
+            else if (c == '/')
+            {
+                --nChar;  // Remove initial '/' from buffer
+
+                // C++ comment: discard through newline
+                (void) is.getLine(nullptr, '\n');
+            }
+            else if (c == '*')
+            {
+                --nChar;  // Remove initial '/' from buffer
+
+                // C-style comment: discard through to "*/" ending
+                if (!is.seekCommentEnd_Cstyle())
+                {
+                    break;  // Premature end of stream
+                }
+            }
+            else
+            {
+                // Reanalyze the char
+                is.putback(c);
+            }
+        }
+
+        if (nChar == bufLen)
+        {
+            str.append(buf, nChar);  // Flush full buffer
+            nChar = 0;
+        }
+    }
+
+
+    // Abnormal exit of the loop
+
+    str.append(buf, nChar);  // Finalize pending content
+    inplaceTrimRight(str);   // Remove trailing whitespace
+
+    // Exhausted stream without finding closing sequence
+    return false;
+}
+
 } // End namespace Foam
 
 
 // * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * * //
 
+bool Foam::ISstream::continueReadUntilRightBrace
+(
+    std::string& str,
+    const bool stripComments
+)
+{
+    return
+        readUntilBalancedDelimiter
+        (
+            *this,
+            str,
+            stripComments,
+            token::BEGIN_BLOCK,
+            token::END_BLOCK
+        );
+}
+
+
 Foam::Istream& Foam::ISstream::read(token& t)
 {
     constexpr const unsigned bufLen = 128; // Max length for labels/scalars
     static char buf[bufLen];
 
-    // Return the put back token if it exists
+    // Return the putback token if it exists
     if (Istream::getBack(t))
     {
         return *this;
diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H
index db8bba40fa4..67894c86454 100644
--- a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H
+++ b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H
@@ -66,7 +66,8 @@ class ISstream
 
     // Private Member Functions
 
-        //- Get the next valid character
+        //- Get the next valid (non-whitespace) character,
+        //- after skipping any C/C++ comments.
         char nextValid();
 
         //- No copy assignment
@@ -131,6 +132,18 @@ public:
         //  \return False if stream exhausted before finding the comment end
         bool seekCommentEnd_Cstyle();
 
+        //- Raw, low-level get into a string.
+        //- Continues reading \b after an initial left-brace until it finds
+        //- the matching closing right-brace.
+        //  Tracks balanced pairs, trims out leading/trailing whitespace.
+        //
+        //  \return False if stream exhausted before finding closing brace
+        bool continueReadUntilRightBrace
+        (
+            std::string& str,
+            const bool stripComments = true
+        );
+
 
         // Read Functions
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/README b/src/OpenFOAM/db/dictionary/functionEntries/README
index b114ac121d0..98969e2dec0 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/README
+++ b/src/OpenFOAM/db/dictionary/functionEntries/README
@@ -6,23 +6,32 @@
   #overwrite        | dict              | entry introducer
   #warn             | dict              | entry introducer
   #error            | dict              | entry introducer
-                    |                   |
   #remove           | dict              | keyType or List<keyType>
                     |                   |
   #include          | dict/primitive    | string
   #sinclude         | dict/primitive    | string
-  #includeIfPresent | dict/primitive    | string
   #includeEtc       | dict/primitive    | string
   #sincludeEtc      | dict/primitive    | string
   #includeFunc      | dict              | word
                     |                   |
+  #message          | dict/primitive    | string or braced content
+  #word             | dict/primitive    | string or braced content
+  #eval             | primitive         | string or braced content
+                    |                   |
   #calc             | dict/primitive    | string
   #codeStream       | dict/primitive    | dictionary
   #if               | dict              | string
   #ifeq             | dict              | entry entry
 
 
-Pending future extensions
+Deprecated
+
+| directive         | comments                              |
+|-------------------|---------------------------------------|
+  #includeIfPresent | same as #sinclude                     |
+
+
+Pending future extensions (considered reserved)
 
 | directive         | context           | content           | line oriented?
 |-------------------|-------------------|-------------------|-----------------
@@ -31,4 +40,4 @@ Pending future extensions
   #undef            | dict              | keyType or List<keyType>
 
 
-2019-08-21
+2021-11-24
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/calcEntry/calcEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/calcEntry/calcEntry.H
index 34d0ab4c6c5..0f2792855b7 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/calcEntry/calcEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/calcEntry/calcEntry.H
@@ -51,8 +51,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef calcEntry_H
-#define calcEntry_H
+#ifndef functionEntries_calcEntry_H
+#define functionEntries_calcEntry_H
 
 #include "codeStream.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.H b/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.H
index 691be4ceecb..036c260faf6 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/codeStream/codeStream.H
@@ -92,8 +92,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef codeStream_H
-#define codeStream_H
+#ifndef functionEntries_codeStream_H
+#define functionEntries_codeStream_H
 
 #include "functionEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C
index 89d153a31ae..1efaa7790c0 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C
+++ b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C
@@ -56,96 +56,6 @@ namespace functionEntries
 } // End namespace Foam
 
 
-// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
-
-namespace Foam
-{
-
-// Slurp a string until a closing '}' is found.
-// Track balanced bracket/brace pairs, with max stack depth of 60.
-static bool slurpUntilBalancedBrace(ISstream& is, std::string& str)
-{
-    constexpr const unsigned bufLen = 1024;
-    static char buf[bufLen];
-
-    is.fatalCheck(FUNCTION_NAME);
-
-    unsigned nChar = 0;
-    unsigned depth = 1; // Initial '{' already seen by caller
-    char c;
-
-    str.clear();
-    while (is.get(c))
-    {
-        buf[nChar++] = c;
-
-        if (c == token::BEGIN_BLOCK)
-        {
-            ++depth;
-        }
-        else if (c == token::END_BLOCK)
-        {
-            --depth;
-            if (!depth)
-            {
-                // Closing '}' character - do not include in output
-                --nChar;
-                str.append(buf, nChar);
-                return true;
-            }
-        }
-        else if (c == '/')
-        {
-            // Strip C/C++ comments from expressions
-            // Note: could also peek instead of get/putback
-
-            if (!is.get(c))
-            {
-                break;  // Premature end of stream
-            }
-            else if (c == '/')
-            {
-                --nChar;  // Remove initial '/' from buffer
-
-                // C++ comment: discard through newline
-                (void) is.getLine(nullptr, '\n');
-            }
-            else if (c == '*')
-            {
-                --nChar;  // Remove initial '/' from buffer
-
-                // C-style comment: discard through to "*/" ending
-                if (!is.seekCommentEnd_Cstyle())
-                {
-                    break;  // Premature end of stream
-                }
-            }
-            else
-            {
-                // Reanalyze the char
-                is.putback(c);
-            }
-        }
-
-        if (nChar == bufLen)
-        {
-            str.append(buf, nChar);  // Flush full buffer
-            nChar = 0;
-        }
-    }
-
-
-    // Abnormal exit of the loop
-
-    str.append(buf, nChar);  // Finalize pending content
-
-    is.fatalCheck(FUNCTION_NAME);
-    return false;
-}
-
-} // End namespace Foam
-
-
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
 
 Foam::tokenList Foam::functionEntries::evalEntry::evaluate
@@ -292,8 +202,11 @@ Foam::tokenList Foam::functionEntries::evalEntry::evaluate
         is >> tok;
     }
 
-    string str;  // The string to evaluate
-    if (tok.isString())
+
+    // The string to evaluate
+    string str;
+
+    if (tok.isStringType())  // Also accepts a single bare word
     {
         // - #eval "expr"
         // - #eval #{ expr #}
@@ -303,12 +216,13 @@ Foam::tokenList Foam::functionEntries::evalEntry::evaluate
     else if (tok.isPunctuation(token::BEGIN_BLOCK))
     {
         // - #eval { expr }
-        if (!slurpUntilBalancedBrace(dynamic_cast<ISstream&>(is), str))
+        // strip comments
+        if (!continueReadUntilRightBrace(is, str, true))
         {
             reportReadWarning
             (
                 is,
-                "Premature end while reading expression - missing '}'?"
+                "Premature end while reading #eval - missing '}'?"
             );
         }
     }
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H
index 58d450dd9f5..858696a5584 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H
@@ -65,8 +65,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef evalEntry_H
-#define evalEntry_H
+#ifndef functionEntries_evalEntry_H
+#define functionEntries_evalEntry_H
 
 #include "functionEntry.H"
 
@@ -98,6 +98,7 @@ class evalEntry
     //- Evaluate and return a token list
     static tokenList evaluate(const dictionary& parentDict, Istream& is);
 
+
 public:
 
     //- Execute in a primitiveEntry context, extracts token or line
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C
index c363b63f875..d23aaeb1392 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C
+++ b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C
@@ -47,6 +47,7 @@ namespace Foam
         execute,
         primitiveEntryIstream
     );
+
 } // End namespace Foam
 
 
@@ -61,6 +62,19 @@ Foam::token Foam::functionEntry::readLine(const word& key, Istream& is)
 }
 
 
+bool Foam::functionEntry::continueReadUntilRightBrace
+(
+    Istream& is,
+    std::string& str,
+    const bool stripComments
+)
+{
+    return
+        dynamic_cast<ISstream&>(is)
+            .continueReadUntilRightBrace(str, stripComments);
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::functionEntry::functionEntry
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.H
index 9b796f3f60c..99c3efbf762 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.H
@@ -80,6 +80,15 @@ protected:
         template<class StringType>
         static List<StringType> readStringList(Istream& is);
 
+        //- Slurp a string until a closing '}' is found.
+        //  Track balanced bracket/brace pairs, with max stack depth of 60.
+        static bool continueReadUntilRightBrace
+        (
+            Istream& is,
+            std::string& str,
+            const bool stripComments = true
+        );
+
         //- No copy construct
         functionEntry(const functionEntry&) = delete;
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.C
index ca6ca8f8c2c..f33e531dcd5 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.C
+++ b/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.C
@@ -50,13 +50,13 @@ namespace functionEntries
 } // End namespace Foam
 
 
-// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
 
 bool Foam::functionEntries::ifEntry::isTrue(ITstream& its)
 {
     Switch logic;
 
-    if (its.size() && its.first().isScalar())
+    if (its.peekFirst().isScalar())
     {
         // Use default rounding tolerance
         logic = Switch(its.first().scalarToken());
@@ -70,6 +70,8 @@ bool Foam::functionEntries::ifEntry::isTrue(ITstream& its)
 }
 
 
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
 bool Foam::functionEntries::ifEntry::execute
 (
     DynamicList<filePos>& stack,
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.H
index 364247dad9b..6f1679751cd 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/ifEntry/ifEntry.H
@@ -54,8 +54,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef ifEntry_H
-#define ifEntry_H
+#ifndef functionEntries_ifEntry_H
+#define functionEntries_ifEntry_H
 
 #include "ifeqEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H
index a925964fcda..0cf864fa5db 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H
@@ -69,8 +69,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef ifeqEntry_H
-#define ifeqEntry_H
+#ifndef functionEntries_ifeqEntry_H
+#define functionEntries_ifeqEntry_H
 
 #include "functionEntry.H"
 #include "DynamicList.H"
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/includeEntry/includeEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/includeEntry/includeEntry.H
index d6747fe5d1c..00d28c6bb25 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/includeEntry/includeEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/includeEntry/includeEntry.H
@@ -53,8 +53,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef includeEntry_H
-#define includeEntry_H
+#ifndef functionEntries_includeEntry_H
+#define functionEntries_includeEntry_H
 
 #include "functionEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/includeEtcEntry/includeEtcEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/includeEtcEntry/includeEtcEntry.H
index 4b7507a3c73..8c545643695 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/includeEtcEntry/includeEtcEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/includeEtcEntry/includeEtcEntry.H
@@ -50,8 +50,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef includeEtcEntry_H
-#define includeEtcEntry_H
+#ifndef functionEntries_includeEtcEntry_H
+#define functionEntries_includeEtcEntry_H
 
 #include "functionEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/includeFuncEntry/includeFuncEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/includeFuncEntry/includeFuncEntry.H
index ddb205839f0..7a430ee7f3f 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/includeFuncEntry/includeFuncEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/includeFuncEntry/includeFuncEntry.H
@@ -64,8 +64,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef includeFuncEntry_H
-#define includeFuncEntry_H
+#ifndef functionEntries_includeFuncEntry_H
+#define functionEntries_includeFuncEntry_H
 
 #include "functionEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/inputMode/inputMode.H b/src/OpenFOAM/db/dictionary/functionEntries/inputMode/inputMode.H
index 3c6ad960407..9259c63db3a 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/inputMode/inputMode.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/inputMode/inputMode.H
@@ -55,8 +55,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef inputMode_H
-#define inputMode_H
+#ifndef functionEntries_inputMode_H
+#define functionEntries_inputMode_H
 
 #include "entry.H"
 #include "functionEntry.H"
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.C b/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.C
new file mode 100644
index 00000000000..a150da5ee3f
--- /dev/null
+++ b/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.C
@@ -0,0 +1,145 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "messageDirective.H"
+#include "dictionary.H"
+#include "stringOps.H"
+#include "addToMemberFunctionSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionEntries
+{
+    addNamedToMemberFunctionSelectionTable
+    (
+        functionEntry,
+        messageDirective,
+        execute,
+        dictionaryIstream,
+        message
+    );
+
+    addNamedToMemberFunctionSelectionTable
+    (
+        functionEntry,
+        messageDirective,
+        execute,
+        primitiveEntryIstream,
+        message
+    );
+
+} // End namespace functionEntry
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
+
+bool Foam::functionEntries::messageDirective::evaluate
+(
+    const dictionary& parentDict,
+    Istream& is
+)
+{
+    token tok(is);
+
+    // The string to evaluate
+    string str;
+
+    if (tok.isStringType())  // Also accepts a single bare word
+    {
+        // - #message expr
+        // - #message "expr"
+        // - #message #{ expr #}
+        str = tok.stringToken();
+    }
+    else if (tok.isPunctuation(token::BEGIN_BLOCK))
+    {
+        // - #message { expr }
+        // strip comments
+        if (!continueReadUntilRightBrace(is, str, true))
+        {
+            reportReadWarning
+            (
+                is,
+                "Premature end while reading #message - missing '}'?"
+            );
+        }
+    }
+    else
+    {
+        FatalIOErrorInFunction(is)
+            << "Invalid input for #message."
+               " Expecting a string or block to expand, but found" << nl
+            << tok.info() << endl
+            << exit(FatalIOError);
+    }
+
+    stringOps::inplaceExpand(str, parentDict);
+    stringOps::inplaceTrim(str);
+
+    if (!str.empty() && error::master())
+    {
+        // Use stderr directly, in case message should be part of startup
+        std::cerr
+            << str << " (file: \""
+            << parentDict.relativeName() << "\" line: "
+            << tok.lineNumber() << ")\n" << std::flush;
+    }
+
+    return true;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionEntries::messageDirective::execute
+(
+    dictionary& parentDict,
+    Istream& is
+)
+{
+    evaluate(parentDict, is);
+
+    return true;
+}
+
+
+bool Foam::functionEntries::messageDirective::execute
+(
+    const dictionary& parentDict,
+    primitiveEntry& entry,
+    Istream& is
+)
+{
+    evaluate(parentDict, is);
+
+    return true;
+}
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.H b/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.H
new file mode 100644
index 00000000000..d396957d6fd
--- /dev/null
+++ b/src/OpenFOAM/db/dictionary/functionEntries/message/messageDirective.H
@@ -0,0 +1,101 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::functionEntries::messageDirective
+
+Description
+    Expands string content and reports as a message on stderr.
+
+    For example,
+    \verbatim
+    T
+    {
+        solver          PBiCG;
+        preconditioner  DILU;
+        tolerance       1e-10;
+        relTol          0;
+        #message "using solver: $solver"
+    }
+    \endverbatim
+
+SourceFiles
+    messageDirective.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionEntries_messageDirective_H
+#define functionEntries_messageDirective_H
+
+#include "functionEntry.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionEntries
+{
+
+/*---------------------------------------------------------------------------*\
+                      Class messageDirective Declaration
+\*---------------------------------------------------------------------------*/
+
+class messageDirective
+:
+    public functionEntry
+{
+    // Private Member Functions
+
+    //- Evaluate
+    static bool evaluate(const dictionary& parentDict, Istream& is);
+
+
+public:
+
+    //- Execute in a primitiveEntry context.
+    //  Reports message string (after expansion) - does not alter entry.
+    static bool execute
+    (
+        const dictionary& parentDict,
+        primitiveEntry& entry,
+        Istream& is
+    );
+
+    //- Execute in a sub-dict context.
+    //  Reports message string (after expansion) - does not alter dictionary.
+    static bool execute(dictionary& parentDict, Istream& is);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionEntries
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/removeEntry/removeEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/removeEntry/removeEntry.H
index 918084313d8..1db53b72eba 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/removeEntry/removeEntry.H
+++ b/src/OpenFOAM/db/dictionary/functionEntries/removeEntry/removeEntry.H
@@ -52,8 +52,8 @@ SourceFiles
 
 \*---------------------------------------------------------------------------*/
 
-#ifndef removeEntry_H
-#define removeEntry_H
+#ifndef functionEntries_removeEntry_H
+#define functionEntries_removeEntry_H
 
 #include "functionEntry.H"
 
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.C b/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.C
new file mode 100644
index 00000000000..7342250e626
--- /dev/null
+++ b/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.C
@@ -0,0 +1,157 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+\*---------------------------------------------------------------------------*/
+
+#include "wordDirective.H"
+#include "dictionary.H"
+#include "stringOps.H"
+#include "addToMemberFunctionSelectionTable.H"
+
+// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionEntries
+{
+    addNamedToMemberFunctionSelectionTable
+    (
+        functionEntry,
+        wordDirective,
+        execute,
+        dictionaryIstream,
+        word
+    );
+
+    addNamedToMemberFunctionSelectionTable
+    (
+        functionEntry,
+        wordDirective,
+        execute,
+        primitiveEntryIstream,
+        word
+    );
+
+} // End namespace functionEntries
+} // End namespace Foam
+
+
+// * * * * * * * * * * * * * Private Member Functions  * * * * * * * * * * * //
+
+Foam::token Foam::functionEntries::wordDirective::evaluate
+(
+    const dictionary& parentDict,
+    Istream& is
+)
+{
+    token tok(is);
+
+    // The string to evaluate
+    string str;
+
+    if (tok.isStringType())  // Also accepts a single bare word
+    {
+        // - #word expr
+        // - #word "expr"
+        // - #word #{ expr #}
+        str = tok.stringToken();
+    }
+    else if (tok.isPunctuation(token::BEGIN_BLOCK))
+    {
+        // - #word { expr }
+        // strip comments
+        if (!continueReadUntilRightBrace(is, str, true))
+        {
+            reportReadWarning
+            (
+                is,
+                "Premature end while reading #word - missing '}'?"
+            );
+        }
+    }
+    else
+    {
+        FatalIOErrorInFunction(is)
+            << "Invalid input for #word."
+               " Expecting a string or block to expand, but found" << nl
+            << tok.info() << endl
+            << exit(FatalIOError);
+    }
+
+    stringOps::inplaceExpand(str, parentDict);
+
+    word result(word::validate(str));  // Includes trimming etc.
+
+    if (!result.empty())
+    {
+        tok = std::move(result);
+        return tok;
+    }
+
+    // Expanded to nothing - treat as a no-op
+    return token::undefinedToken;
+}
+
+
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
+
+bool Foam::functionEntries::wordDirective::execute
+(
+    const dictionary& parentDict,
+    primitiveEntry& entry,
+    Istream& is
+)
+{
+    token tok(evaluate(parentDict, is));
+
+    if (tok.good())
+    {
+        // Can add evaluated value directly into primitiveEntry
+        entry.append(std::move(tok), true);  // Lazy resizing
+    }
+
+    return true;
+}
+
+
+bool Foam::functionEntries::wordDirective::execute
+(
+    dictionary& parentDict,
+    Istream& is
+)
+{
+    token tok(evaluate(parentDict, is));
+
+    if (tok.good())
+    {
+        // Using putBack to insert evaluated value into stream
+        is.putBack(tok);
+    }
+
+    return true;
+}
+
+
+// ************************************************************************* //
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.H b/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.H
new file mode 100644
index 00000000000..69e81ac8497
--- /dev/null
+++ b/src/OpenFOAM/db/dictionary/functionEntries/word/wordDirective.H
@@ -0,0 +1,100 @@
+/*---------------------------------------------------------------------------*\
+  =========                 |
+  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
+   \\    /   O peration     |
+    \\  /    A nd           | www.openfoam.com
+     \\/     M anipulation  |
+-------------------------------------------------------------------------------
+    Copyright (C) 2021 OpenCFD Ltd.
+-------------------------------------------------------------------------------
+License
+    This file is part of OpenFOAM.
+
+    OpenFOAM is free software: you can redistribute it and/or modify it
+    under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+    for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.
+
+Class
+    Foam::functionEntries::wordDirective
+
+Description
+    Converts/expands string content to a word.
+    This can be useful for composition of names.
+
+    For example,
+    \verbatim
+    #word "some_prefix_solverInfo_${application}_${{10 * 5}}"
+    {
+        type    solverInfo;
+        libs    (utilityFunctionObjects);
+        fields  (".*");
+
+        returnName  #word solver$type;
+    }
+    \endverbatim
+
+SourceFiles
+    wordDirective.C
+
+\*---------------------------------------------------------------------------*/
+
+#ifndef functionEntries_wordDirective_H
+#define functionEntries_wordDirective_H
+
+#include "functionEntry.H"
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+namespace Foam
+{
+namespace functionEntries
+{
+
+/*---------------------------------------------------------------------------*\
+                        Class wordDirective Declaration
+\*---------------------------------------------------------------------------*/
+
+class wordDirective
+:
+    public functionEntry
+{
+    // Private Member Functions
+
+    //- Evaluate
+    static token evaluate(const dictionary& parentDict, Istream& is);
+
+
+public:
+
+    //- Execute in a primitiveEntry context
+    static bool execute
+    (
+        const dictionary& parentDict,
+        primitiveEntry& entry,
+        Istream& is
+    );
+
+    //- Execute in a sub-dict context.
+    static bool execute(dictionary& parentDict, Istream& is);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+} // End namespace functionEntries
+} // End namespace Foam
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+#endif
+
+// ************************************************************************* //
diff --git a/tutorials/IO/dictionary/good-word-expand.dict b/tutorials/IO/dictionary/good-word-expand.dict
new file mode 100644
index 00000000000..4c398afe5d1
--- /dev/null
+++ b/tutorials/IO/dictionary/good-word-expand.dict
@@ -0,0 +1,61 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2112                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      dictionary;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// message from a word
+#message message-word
+
+// message from a string
+#message "message string [] from ${FOAM_API:-unset}"
+
+// message from a braced-block string
+#message { message block string from ${FOAM_API:-unset} }
+
+
+// word in primitive entry
+
+foamApi using #word "_ ${FOAM_API:-unset}" version;
+
+
+// word as dictionary entry (string syntax)
+
+#word "dict1entry_ ${FOAM_API:-unset}"
+{
+    value1 10;
+}
+
+
+// word as dictionary entry (braced-block string)
+
+#word { dict2entry_ ${FOAM_API:-unset} }
+{
+    value1 20;
+}
+
+#word
+{
+    dict3entry
+    ${FOAM_API:+_}  // Use '_' separator
+    ${FOAM_API}     // The value (if any)
+}
+{
+    // This is a funny corner-case for #eval.
+    // It also accepts a single word ... not that many make sense though
+
+    value1 #eval pi();
+}
+
+
+// ************************************************************************* //
diff --git a/tutorials/incompressible/pimpleFoam/laminar/movingCone/system/blockMeshDict b/tutorials/incompressible/pimpleFoam/laminar/movingCone/system/blockMeshDict
index a467b741933..433dac2dce0 100644
--- a/tutorials/incompressible/pimpleFoam/laminar/movingCone/system/blockMeshDict
+++ b/tutorials/incompressible/pimpleFoam/laminar/movingCone/system/blockMeshDict
@@ -1,7 +1,7 @@
 /*--------------------------------*- C++ -*----------------------------------*\
 | =========                 |                                                 |
 | \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
-|  \\    /   O peration     | Version:  v2106                                 |
+|  \\    /   O peration     | Version:  v2112                                 |
 |   \\  /    A nd           | Website:  www.openfoam.com                      |
 |    \\/     M anipulation  |                                                 |
 \*---------------------------------------------------------------------------*/
@@ -16,6 +16,8 @@ FoamFile
 
 mergeType points;   // Wedge geometry - Merge points instead of topology
 
+#message "Using mergeType: ${mergeType:-default}"
+
 scale   0.001;
 
 vertices
-- 
GitLab