diff --git a/applications/test/stringList/Test-stringList.C b/applications/test/stringList/Test-stringList.C
index f7628aa2543f274ea3421df909a73492a8d6f250..23e441a8699883c639856b9407b1b4c6a2ffa63c 100644
--- a/applications/test/stringList/Test-stringList.C
+++ b/applications/test/stringList/Test-stringList.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -41,7 +41,7 @@ using namespace Foam;
 
 int main(int argc, char *argv[])
 {
-    stringList strLst
+    stringList strings
     {
         "hello",
         "heello",
@@ -53,51 +53,57 @@ int main(int argc, char *argv[])
         "okkey",
         "okkkey",
     };
+    labelList matches;
 
-    wordRes reLst(IStringStream("( okey \"[hy]e+.*\" )")());
+    wordRes matcher1(IStringStream("( okey \"[hy]e+.*\" )")());
 
-    Info<< "stringList " << strLst << nl;
+    Info<< "stringList " << strings << nl;
 
-    labelList matches = findStrings(regExp(".*ee.*"), strLst);
+    {
+        keyType key(".*ee.*", keyType::REGEX);
+        matches = findMatchingStrings(regExp(key), strings);
 
-    Info<< "matches found for regexp .*ee.* :" << nl << matches << nl;
+        Info<< "matches found for regexp " << key << " :" << nl
+            << matches << nl;
 
-    forAll(matches, i)
-    {
-        Info<< " -> " << strLst[matches[i]] << nl;
+        for (const label idx : matches)
+        {
+            Info<< " -> " << strings[idx] << nl;
+        }
     }
 
     Info<< "Match found using ListOps = "
-        << ListOps::found(strLst, regExp(".*ee.*")) << nl;
+        << ListOps::found(strings, regExp(".*ee.*")) << nl;
 
     Info<< "First index = "
-        << ListOps::find(strLst, regExp(".*ee.*")) << nl;
+        << ListOps::find(strings, regExp(".*ee.*")) << nl;
 
     Info<< endl;
 
-    matches = findStrings(reLst, strLst);
+    matches = findMatchingStrings(matcher1, strings);
 
-    Info<< "matching " << flatOutput(reLst) << " => "
-        << reLst.matching(strLst) << nl;
-    Info<< "matches found for " << flatOutput(reLst) << " => "
+    Info<< "matching " << flatOutput(matcher1) << " => "
+        << matcher1.matching(strings) << nl;
+    Info<< "matches found for " << flatOutput(matcher1) << " => "
         << matches << nl;
-    forAll(matches, i)
+
+    for (const label idx : matches)
     {
-        Info<< " -> " << strLst[matches[i]] << nl;
+        Info<< " -> " << strings[idx] << nl;
     }
     Info<< endl;
 
-    stringList subLst = subsetStrings(regExp(".*ee.*"), strLst);
+    stringList subLst = subsetStrings(regExp(".*ee.*"), strings);
     Info<< "subset stringList: " << subLst << nl;
 
-    subLst = subsetStrings(reLst, strLst);
+    subLst = subsetStrings(matcher1, strings);
     Info<< "subset stringList: " << subLst << nl;
 
-    inplaceSubsetStrings(reLst, strLst);
-    Info<< "subsetted stringList: " << strLst << nl;
+    inplaceSubsetStrings(matcher1, strings);
+    Info<< "subsetted stringList: " << strings << nl;
 
-    inplaceSubsetStrings(regExp(".*l.*"), strLst);
-    Info<< "subsetted stringList: " << strLst << nl;
+    inplaceSubsetStrings(regExp(".*l.*"), strings);
+    Info<< "subsetted stringList: " << strings << nl;
 
     Info<< "\nEnd\n" << endl;
 
diff --git a/applications/test/stringSplit/Test-stringSplit.C b/applications/test/stringSplit/Test-stringSplit.C
index 2994d384f4dc530eb5a6fefac344bf97a7025fcf..2dc4e6ab98e65e9d11de540e1102887b7196cbb4 100644
--- a/applications/test/stringSplit/Test-stringSplit.C
+++ b/applications/test/stringSplit/Test-stringSplit.C
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -38,8 +38,12 @@ Description
 using namespace Foam;
 
 // Simple utility
-template<class String>
-void printSubStrings(const String& str, const SubStrings<String>& split)
+template<class StringType>
+void printSubStrings
+(
+    const StringType& str,
+    const SubStrings<StringType>& split
+)
 {
     Info<< "string {" << str.size() << " chars} = " << str << nl
         << split.size() << " elements {" << split.length() << " chars}"
diff --git a/applications/test/wordRe/Test-wordRe.C b/applications/test/wordRe/Test-wordRe.C
index 37c34a71dc844c19ceab5f715c41e5dbde351588..f91258511da77eb3c7408c2ccb240bdf4c990edc 100644
--- a/applications/test/wordRe/Test-wordRe.C
+++ b/applications/test/wordRe/Test-wordRe.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -32,6 +32,7 @@ Description
 #include "IOstreams.H"
 #include "IOobject.H"
 #include "IFstream.H"
+#include "ITstream.H"
 #include "List.H"
 #include "Tuple2.H"
 #include "keyType.H"
@@ -51,11 +52,84 @@ word typeOf(wordRe::compOption retval)
 }
 
 
+Ostream& printInfo(const wordRe& wre)
+{
+    if (wre.isPattern())
+    {
+        Info<< "wordRe(regex) ";
+    }
+    else
+    {
+        Info<< "wordRe(plain) ";
+    }
+    Info<< wre;
+    return Info;
+}
+
+
+// Could use something like this for reading wordRes
+void exptl_reading(Istream& is, wordRes& list)
+{
+    token tok(is);
+
+    bool ok = ((tok.isWord() || tok.isQuotedString()) && !tok.isCompound());
+    if (ok)
+    {
+        list.resize(1);
+        ok = list[0].assign(tok);
+    }
+    if (!ok)
+    {
+        if (tok.good())
+        {
+            is.putBack(tok);
+        }
+        list.readList(is);
+    }
+}
+
+
+bool testReadList_wordRes(const std::string& input)
+{
+    ITstream is("input", input);
+    wordRes list;
+
+    exptl_reading(is, list);
+
+    const label nTrailing = is.nRemainingTokens();
+
+    Info<< "input:<<<<" << nl << input.c_str() << nl
+        << ">>>> with " << nTrailing << " tokens remaining" << nl
+        << "list: " << flatOutput(list) << nl;
+
+    return !nTrailing;
+}
+
+
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 // Main program:
 
 int main(int argc, char *argv[])
 {
+    if (true)
+    {
+        Info<< "Test input for wordRes" << nl << nl;
+
+        testReadList_wordRes
+        (
+            "("
+            "this \"x.*\" \"file[a-b]\" xvalues \"xv.*\""
+            ")"
+        );
+
+        testReadList_wordRes
+        (
+            "\".*\""
+        );
+
+        Info<< nl << nl;
+    }
+
     wordRe wre;
     std::string s1("this .* file");
     Foam::string s2("this .* file");
@@ -64,13 +138,13 @@ int main(int argc, char *argv[])
     keyType keyre("x.*", keyType::REGEX);
 
     wordRes wordrelist
-    {
+    ({
         {"this", wordRe::LITERAL},
         {"x.*", wordRe::REGEX},
         {"file[a-b]", wordRe::REGEX},
         {"xvalues", wordRe::LITERAL},
         {"xv.*", wordRe::REGEX},
-    };
+    });
 
     if (false)
     {
@@ -169,45 +243,45 @@ int main(int argc, char *argv[])
     }
     Info<< nl;
 
-    wordRe(s1, wordRe::DETECT).info(Info) << nl;
-    wordRe(s2).info(Info) << nl;
-    wordRe(s2, wordRe::DETECT).info(Info) << nl;
-    wordRe(s3, wordRe::REGEX).info(Info) << nl;
+    printInfo(wordRe(s1, wordRe::DETECT)) << nl;
+    printInfo(wordRe(s2)) << nl;
+    printInfo(wordRe(s2, wordRe::DETECT)) << nl;
+    printInfo(wordRe(s3, wordRe::REGEX)) << nl;
 
     wre = "this .* file";
 
     Info<<"substring: " << wre.substr(4) << nl;
 
-    wre.info(Info) << nl;
+    printInfo(wre) << nl;
     wre = s1;
-    wre.info(Info) << nl;
+    printInfo(wre) << nl;
     wre.uncompile();
-    wre.info(Info) << nl;
+    printInfo(wre) << nl;
 
     wre = "something";
-    wre.info(Info) << " before" << nl;
+    printInfo(wre) << " before" << nl;
     wre.uncompile();
-    wre.info(Info) << " uncompiled" << nl;
+    printInfo(wre) << " uncompiled" << nl;
     wre.compile(wordRe::DETECT);
-    wre.info(Info) << " after DETECT" << nl;
+    printInfo(wre) << " after DETECT" << nl;
     wre.compile(wordRe::ICASE);
-    wre.info(Info) << " after ICASE" << nl;
+    printInfo(wre) << " after ICASE" << nl;
     wre.compile(wordRe::DETECT_ICASE);
-    wre.info(Info) << " after DETECT_ICASE" << nl;
+    printInfo(wre) << " after DETECT_ICASE" << nl;
 
     wre = "something .* value";
-    wre.info(Info) << " before" << nl;
+    printInfo(wre) << " before" << nl;
     wre.uncompile();
-    wre.info(Info) << " uncompiled" << nl;
+    printInfo(wre) << " uncompiled" << nl;
     wre.compile(wordRe::DETECT);
-    wre.info(Info) << " after DETECT" << nl;
+    printInfo(wre) << " after DETECT" << nl;
     wre.uncompile();
-    wre.info(Info) << " uncompiled" << nl;
+    printInfo(wre) << " uncompiled" << nl;
     wre.compile();
-    wre.info(Info) << " re-compiled" << nl;
+    printInfo(wre) << " re-compiled" << nl;
 
     wre.set("something .* value", wordRe::LITERAL);
-    wre.info(Info) << " set as LITERAL" << nl;
+    printInfo(wre) << " set as LITERAL" << nl;
 
     IOobject::writeDivider(Info);
 
@@ -220,7 +294,7 @@ int main(int argc, char *argv[])
         const wordRe& wre = rawList[elemI].first();
         const string& str = rawList[elemI].second();
 
-        wre.info(Info)
+        printInfo(wre)
             << " equals:" << (wre == str)
             << "(" << wre.match(str, true) << ")"
             << " match:" << wre.match(str)
@@ -230,11 +304,10 @@ int main(int argc, char *argv[])
         wordRe wre2;
         wre2.set(wre, wordRe::ICASE);
 
-        wre2.info(Info)
+        printInfo(wre2)
             << " match:" << wre2.match(str)
             << "  str=" << str
             << nl;
-
     }
 
     Info<< "\nEnd\n" << endl;
diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C
index b98355a03bd77186bf48ed3c474569dccbbef403..0115cf0427dadf73a771e3f3998350d24b417500 100644
--- a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C
+++ b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2018 OpenFOAM Foundation
-    Copyright (C) 2019-2020 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -205,6 +205,10 @@ bool Foam::functionEntries::ifeqEntry::equalToken
             {
                 return equal(t1.floatToken(), t2.floatToken());
             }
+            else if (t2.isLabel())
+            {
+                return t1.floatToken() == t2.labelToken();
+            }
             else if (t2.isScalar())
             {
                 return t1.scalarToken() == t2.scalarToken();
@@ -216,6 +220,10 @@ bool Foam::functionEntries::ifeqEntry::equalToken
             {
                 return equal(t1.doubleToken(), t2.doubleToken());
             }
+            else if (t2.isLabel())
+            {
+                return t1.doubleToken() == t2.labelToken();
+            }
             else if (t2.isScalar())
             {
                 return t1.scalarToken() == t2.scalarToken();
diff --git a/src/OpenFOAM/primitives/chars/char/char.H b/src/OpenFOAM/primitives/chars/char/char.H
index c8efbbc39e25202c6af8d6e2711fcb1518267c49..03cb15ddfb84f8a1b7c526b7b86db53cd8cc8092 100644
--- a/src/OpenFOAM/primitives/chars/char/char.H
+++ b/src/OpenFOAM/primitives/chars/char/char.H
@@ -61,15 +61,17 @@ Ostream& operator<<(Ostream& os, const char c);
 //- Write a nul-terminated C-string
 Ostream& operator<<(Ostream& os, const char* str);
 
-//- Test for whitespace
-inline bool isspace(char c)
+//- Test for whitespace (C-locale)
+inline bool isspace(char c) noexcept
 {
     return
     (
-        c == ' '
-     || c == '\n'
-     || c == '\r'
-     || c == '\t'
+        c == ' '    // (0x20)  space (SPC)
+     || c == '\t'   // (0x09)  horizontal tab (TAB)
+     || c == '\n'   // (0x0a)  newline (LF)
+     || c == '\v'   // (0x0b)  vertical tab (VT)
+     || c == '\f'   // (0x0c)  feed (FF)
+     || c == '\r'   // (0x0d)  carriage return (CR)
     );
 }
 
diff --git a/src/OpenFOAM/primitives/enums/Enum.H b/src/OpenFOAM/primitives/enums/Enum.H
index c3a4a012e29af992043a2e6f5d6b0f0e7d150340..8321f612c7a1f0850940d9c6e82ff1420ec90975 100644
--- a/src/OpenFOAM/primitives/enums/Enum.H
+++ b/src/OpenFOAM/primitives/enums/Enum.H
@@ -41,7 +41,6 @@ SourceFiles
 #define Enum_H
 
 #include "wordList.H"
-#include <initializer_list>
 #include <ostream>
 #include <utility>
 
diff --git a/src/OpenFOAM/primitives/hashes/Hash/Hash.H b/src/OpenFOAM/primitives/hashes/Hash/Hash.H
index 6dfc907cb9ed8918c9e0247d6210fa1a1d6ab98a..b2acbb5ff918456546604183bcc32ada67d453fe 100644
--- a/src/OpenFOAM/primitives/hashes/Hash/Hash.H
+++ b/src/OpenFOAM/primitives/hashes/Hash/Hash.H
@@ -41,6 +41,7 @@ Description
 #include "uLabel.H"
 #include "Hasher.H"
 #include "fileName.H"
+#include "keyType.H"
 #include "wordRe.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.C b/src/OpenFOAM/primitives/strings/fileName/fileName.C
index 64cdaa477f3d57ec51a54c2d431e45d85bbe89bd..2905d6fbc281b33a9ce7ad8d68e664a49392951b 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileName.C
+++ b/src/OpenFOAM/primitives/strings/fileName/fileName.C
@@ -464,12 +464,6 @@ Foam::fileName Foam::fileName::relative
 }
 
 
-bool Foam::fileName::hasExt(const wordRe& ending) const
-{
-    return string::hasExt(ending);
-}
-
-
 Foam::wordList Foam::fileName::components(const char delim) const
 {
     const auto parsed = stringOps::split<string>(*this, delim);
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileName.H b/src/OpenFOAM/primitives/strings/fileName/fileName.H
index a6a137a86942cac65e862ee7956fedd19f94ec46..78f851d2a7cb448625ea0b731b99e3f109d9bdd0 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileName.H
+++ b/src/OpenFOAM/primitives/strings/fileName/fileName.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -55,14 +55,13 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
+class fileName;
+class token;
 template<class T> class List;
 template<class T> class UList;
 typedef List<word> wordList;
 
-class wordRe;
-class fileName;
-
 /*---------------------------------------------------------------------------*\
                           Class fileName Declaration
 \*---------------------------------------------------------------------------*/
@@ -100,7 +99,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         fileName() = default;
 
         //- Copy construct
@@ -142,6 +141,13 @@ public:
 
     // Member Functions
 
+        //- Inherit all regular string assign() methods
+        using string::assign;
+
+        //- Assign from word or string token.
+        //  \return false if the token was the incorrect type
+        bool assign(const token& tok);
+
         //- Is this character valid for a fileName?
         inline static bool valid(char c);
 
@@ -322,17 +328,11 @@ public:
         //  or when the file name is empty or ended with a '/'.
         inline fileName& ext(const word& ending);
 
-        //- Return true if it has an extension or simply ends with a '.'
-        inline bool hasExt() const;
-
-        //- Return true if the extension is the same as the given ending.
-        inline bool hasExt(const word& ending) const;
-
-        //- Return true if the extension matches the given ending.
-        bool hasExt(const wordRe& ending) const;
+        //- Various checks for extensions
+        using string::hasExt;
 
         //- Remove extension, returning true if string changed.
-        inline bool removeExt();
+        using string::removeExt;
 
 
         //- Return path components as wordList
@@ -389,7 +389,7 @@ public:
         inline fileName& operator=(const char* str);
 
 
-    // Other operators
+    // Other Operators
 
         //- Append a path element with '/' separator.
         //  No '/' separator is added if this or the argument are empty.
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
index 961997331acad23f89841fe8d937209a9b046702..f7fffcbc7e290863a35376a7b891bba2de6cbad4 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
+++ b/src/OpenFOAM/primitives/strings/fileName/fileNameI.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -171,18 +171,6 @@ inline bool Foam::fileName::hasPath() const
 }
 
 
-inline bool Foam::fileName::hasExt() const
-{
-    return string::hasExt();
-}
-
-
-inline bool Foam::fileName::hasExt(const word& ending) const
-{
-    return string::hasExt(ending);
-}
-
-
 inline std::string Foam::fileName::path(const std::string& str)
 {
     const auto i = str.rfind('/');
@@ -256,12 +244,6 @@ inline bool Foam::fileName::removePath()
 }
 
 
-inline bool Foam::fileName::removeExt()
-{
-    return string::removeExt();
-}
-
-
 inline Foam::fileName& Foam::fileName::ext(const word& ending)
 {
     string::ext(ending);
diff --git a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
index 5dbff430f4676008c6c06dabc0a6d0a6baec5949..2fcca95eb509cd8b5857df9dd3f214e657683dad 100644
--- a/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
+++ b/src/OpenFOAM/primitives/strings/fileName/fileNameIO.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -29,46 +29,60 @@ License
 #include "fileName.H"
 #include "IOstreams.H"
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::fileName::fileName(Istream& is)
-:
-    string()
 {
     is >> *this;
 }
 
 
-Foam::Istream& Foam::operator>>(Istream& is, fileName& val)
-{
-    token t(is);
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-    if (!t.good())
+bool Foam::fileName::assign(const token& tok)
+{
+    if (tok.isWord())
     {
-        FatalIOErrorInFunction(is)
-            << "Bad token - could not get string"
-            << exit(FatalIOError);
-        is.setBad();
-        return is;
+        // Also accept a plain word as a fileName
+        assign(tok.wordToken());
+        return true;
     }
-
-    if (t.isStringType())
+    else if (tok.isQuotedString())
     {
-        // Also accept a plain word as a fileName
-        val = t.stringToken();
+        assign(tok.stringToken());
+        stripInvalid();  // More stringent for fileName than string
+        return true;
     }
-    else
+
+    return false;
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
+Foam::Istream& Foam::operator>>(Istream& is, fileName& val)
+{
+    token tok(is);
+
+    if (!val.assign(tok))
     {
-        FatalIOErrorInFunction(is)
-            << "Wrong token type - expected string, found "
-            << t.info()
-            << exit(FatalIOError);
+        FatalIOErrorInFunction(is);
+        if (tok.good())
+        {
+            FatalIOError
+                << "Wrong token type - expected string, found "
+                << tok.info();
+        }
+        else
+        {
+            FatalIOError
+                << "Bad token - could not get fileName";
+        }
+        FatalIOError << exit(FatalIOError);
         is.setBad();
         return is;
     }
 
-    val.stripInvalid();
-
     is.check(FUNCTION_NAME);
     return is;
 }
diff --git a/src/OpenFOAM/primitives/strings/keyType/keyType.C b/src/OpenFOAM/primitives/strings/keyType/keyType.C
index 1b9899f03638889d13ba451e77d9ac0e161cd166..6749afe42df6b93514846172f58895b3e63af9bd 100644
--- a/src/OpenFOAM/primitives/strings/keyType/keyType.C
+++ b/src/OpenFOAM/primitives/strings/keyType/keyType.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -28,7 +28,9 @@ License
 
 #include "keyType.H"
 #include "regExp.H"
+#include "token.H"
 #include "IOstreams.H"
+#include <algorithm>  // For swap
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -38,9 +40,6 @@ const Foam::keyType Foam::keyType::null;
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::keyType::keyType(Istream& is)
-:
-    word(),
-    type_(option::LITERAL)
 {
     is  >> *this;
 }
@@ -48,6 +47,18 @@ Foam::keyType::keyType(Istream& is)
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
+void Foam::keyType::swap(keyType& rhs)
+{
+    if (this == &rhs)
+    {
+        return;  // Self-swap is a no-op
+    }
+
+    word::swap(static_cast<word&>(rhs));
+    std::swap(type_, rhs.type_);
+}
+
+
 bool Foam::keyType::match(const std::string& text, bool literal) const
 {
     if (!literal && isPattern())
@@ -59,37 +70,40 @@ bool Foam::keyType::match(const std::string& text, bool literal) const
 }
 
 
-// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
-
-Foam::Istream& Foam::operator>>(Istream& is, keyType& val)
+bool Foam::keyType::assign(const token& tok)
 {
-    token t(is);
-
-    if (!t.good())
+    if (tok.isWord())
     {
-        FatalIOErrorInFunction(is)
-            << "Bad token - could not get a word/regex"
-            << exit(FatalIOError);
-        is.setBad();
-        return is;
+        // Assign from word - literal
+        assign(tok.wordToken());
+        uncompile();
+        return true;
     }
-
-    if (t.isWord())
+    else if (tok.isQuotedString())
     {
-        val = t.wordToken();
-        val.setType(keyType::LITERAL);
+        // Assign from quoted string - regular expression
+        assign(tok.stringToken());
+        compile();
+        return true;
     }
-    else if (t.isString())
-    {
-        // Assign from string, treat as regular expression
-        val = t.stringToken();
-        val.setType(keyType::REGEX);
 
-        // Flag empty strings as an error
+    return false;
+}
+
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
+Foam::Istream& Foam::operator>>(Istream& is, keyType& val)
+{
+    token tok(is);
+
+    if (val.assign(tok))
+    {
         if (val.empty())
         {
+            // Empty strings are an error
             FatalIOErrorInFunction(is)
-                << "Empty word/expression"
+                << "Zero-length regex"
                 << exit(FatalIOError);
             is.setBad();
             return is;
@@ -97,10 +111,19 @@ Foam::Istream& Foam::operator>>(Istream& is, keyType& val)
     }
     else
     {
-        FatalIOErrorInFunction(is)
-            << "Wrong token type - expected word or string, found "
-            << t.info()
-            << exit(FatalIOError);
+        FatalIOErrorInFunction(is);
+        if (tok.good())
+        {
+            FatalIOError
+                << "Wrong token type - expected word or string, found "
+                << tok.info();
+        }
+        else
+        {
+            FatalIOError
+                << "Bad token - could not get keyType";
+        }
+        FatalIOError << exit(FatalIOError);
         is.setBad();
         return is;
     }
diff --git a/src/OpenFOAM/primitives/strings/keyType/keyType.H b/src/OpenFOAM/primitives/strings/keyType/keyType.H
index 7f5b2c87f0640978a2ea5be46ffe7d1c0f10cf19..7ed8fe64b6e288d4d5b04be432d6a04e8002139b 100644
--- a/src/OpenFOAM/primitives/strings/keyType/keyType.H
+++ b/src/OpenFOAM/primitives/strings/keyType/keyType.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -36,6 +36,7 @@ Description
 
 SourceFiles
     keyType.C
+    keyTypeI.H
 
 \*---------------------------------------------------------------------------*/
 
@@ -43,6 +44,7 @@ SourceFiles
 #define keyType_H
 
 #include "word.H"
+#include "wordRe.H"
 #include "stdFoam.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -51,8 +53,10 @@ namespace Foam
 {
 
 // Forward Declarations
-class Istream;
-class Ostream;
+class keyType;
+class token;
+Istream& operator>>(Istream& is, keyType& val);
+Ostream& operator<<(Ostream& os, const keyType& val);
 
 /*---------------------------------------------------------------------------*\
                            Class keyType Declaration
@@ -90,12 +94,6 @@ private:
         option type_;
 
 
-    // Private Member Functions
-
-        //- No assignment where we cannot determine string/word type
-        void operator=(const std::string&) = delete;
-
-
 public:
 
     // Static Data Members
@@ -106,69 +104,82 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct, empty literal
         inline keyType();
 
-        //- Copy construct, retaining type (literal or regex)
-        inline keyType(const keyType& s);
-
-        //- Copy construct from word, treat as literal.
-        inline keyType(const word& s);
+        //- Copy construct
+        keyType(const keyType&) = default;
 
-        //- Copy construct from string, treat as regular expression.
-        inline keyType(const string& s);
+        //- Move construct
+        keyType(keyType&&) = default;
 
-        //- Construct as copy of character array, treat as literal.
-        inline keyType(const char* s);
+        //- Implicit copy construct from word, treat as LITERAL
+        inline keyType(const word& str);
 
-        //- Copy construct from std::string with specified treatment
-        inline keyType(const std::string& s, option opt);
+        //- Implicit move construct from word, treat as LITERAL
+        inline keyType(word&& str);
 
-        //- Move construct, retaining type (literal or regex)
-        inline keyType(keyType&& s);
+        //- Implicit copy construct from Foam::string, treat as REGEX
+        inline keyType(const string& str);
 
-        //- Move construct from word, treat as literal.
-        inline keyType(word&& s);
+        //- Implicit move construct from Foam::string, treat as REGEX
+        inline keyType(string&& str);
 
-        //- Move construct from string, treat as regular expression.
-        inline keyType(string&& s);
+        //- Copy construct from std::string with specified treatment
+        inline keyType(const std::string& str, option opt);
 
         //- Move construct from std::string with specified treatment
-        inline keyType(std::string&& s, option opt);
+        inline keyType(std::string&& str, option opt);
 
-        //- Construct from Istream
+        //- Implicit construct from character array,
+        //- with specified compile option (default is LITERAL)
+        inline keyType(const char* str, option opt = option::LITERAL);
+
+        //- Construct from Istream by reading a token
         //  Treat as regular expression if surrounded by quotation marks.
         explicit keyType(Istream& is);
 
 
     // Member Functions
 
-        //- Is this character valid for a keyType?
-        //  This is largely identical with what word accepts, but also
-        //  permit brace-brackets, which are valid for some regexs.
-        inline static bool valid(char c);
+        //- Test for valid keyType character?
+        //  Like Foam::word, but with brace-brackets,
+        //  which are valid for some regexs.
+        inline static bool valid(const char c);
 
 
     // Access
 
         //- The keyType is treated as literal, not as pattern.
-        inline bool isLiteral() const;
+        inline bool isLiteral() const noexcept;
 
         //- The keyType is treated as a pattern, not as literal string.
-        inline bool isPattern() const;
+        inline bool isPattern() const noexcept;
 
 
     // Infrastructure
 
-        //- Change the representation
+        //- Inherit all regular string assign() methods
+        using word::assign;
+
+        //- Assign from word or string token.
+        //  Words are treated as literals, strings as regex
+        //  \return false if the token was the incorrect type
+        bool assign(const token& tok);
+
+        //- Change the representation, optionally stripping invalid word
+        //- characters when changing to a literal
         inline void setType(option opt, bool adjust = false);
 
         //- Mark as regular expression
-        inline bool compile();
+        inline bool compile() noexcept;
+
+        //- Mark as literal string
+        inline void uncompile() noexcept;
 
-        //- Mark as literal, instead of a regular expression.
-        //  Optionally strip invalid word characters.
-        inline void uncompile(bool adjust = false);
+        //- Mark as literal string, optionally strip invalid word
+        //- characters when changing to a literal
+        inline void uncompile(bool adjust);
 
 
     // Editing
@@ -177,7 +188,7 @@ public:
         inline void clear();
 
         //- Swap contents. Self-swapping is a no-op.
-        inline void swap(keyType& s);
+        void swap(keyType& rhs);
 
 
     // Matching/Searching
@@ -193,23 +204,25 @@ public:
         //  Allows use as a predicate.
         inline bool operator()(const std::string& text) const;
 
+        //- No assignment where type could be indeterminate
+        void operator=(const std::string&) = delete;
 
         //- Copy assignment, retaining type (literal or regex)
         //  Self-assignment is a no-op.
-        inline void operator=(const keyType& s);
+        inline void operator=(const keyType& str);
 
         //- Move assignment, retaining type (literal or regex)
         //  Self-assignment is a no-op.
-        inline void operator=(keyType&& s);
+        inline void operator=(keyType&& str);
 
-        //- Assign as word, treat as literal
-        inline void operator=(const word& s);
+        //- Assign from word, treat as literal
+        inline void operator=(const word& str);
 
         //- Assign from Foam::string, treat as regular expression
-        inline void operator=(const string& s);
+        inline void operator=(const string& str);
 
-        //- Assign as word, treat as literal
-        inline void operator=(const char* s);
+        //- Assign from character array, treat as literal
+        inline void operator=(const char* str);
 
 
     // Housekeeping
diff --git a/src/OpenFOAM/primitives/strings/keyType/keyTypeI.H b/src/OpenFOAM/primitives/strings/keyType/keyTypeI.H
index cbf710e51709910b48c96c2fa5a34bec80082b66..ca6297e010bb3e796d774e8b0172d88aed3c671f 100644
--- a/src/OpenFOAM/primitives/strings/keyType/keyTypeI.H
+++ b/src/OpenFOAM/primitives/strings/keyType/keyTypeI.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -26,20 +26,12 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include <algorithm>
-
 // * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * * //
 
-inline bool Foam::keyType::valid(char c)
+inline bool Foam::keyType::valid(const char c)
 {
-    return
-    (
-        !isspace(c)
-     && c != '"'   // string quote
-     && c != '\''  // string quote
-     && c != '/'   // path separator
-     && c != ';'   // end statement
-    );
+    // Also accept '{' and '}' (for regex grouping?)
+    return (word::valid(c) || c == '{' || c == '}');
 }
 
 
@@ -52,80 +44,64 @@ inline Foam::keyType::keyType()
 {}
 
 
-inline Foam::keyType::keyType(const keyType& s)
+inline Foam::keyType::keyType(const word& str)
 :
-    word(s, false),
-    type_(s.type_)
+    word(str),
+    type_(option::LITERAL)
 {}
 
 
-inline Foam::keyType::keyType(const word& s)
+inline Foam::keyType::keyType(word&& str)
 :
-    word(s, false),
+    word(std::move(str)),
     type_(option::LITERAL)
 {}
 
 
-inline Foam::keyType::keyType(const string& s)
+inline Foam::keyType::keyType(const string& str)
 :
-    word(s, false),
+    word(str, false),  // No stripping
     type_(option::REGEX)
 {}
 
 
-inline Foam::keyType::keyType(const char* s)
+inline Foam::keyType::keyType(string&& str)
 :
-    word(s, false),
-    type_(option::LITERAL)
+    word(std::move(str), false),  // No stripping
+    type_(option::REGEX)
 {}
 
 
-inline Foam::keyType::keyType(const std::string& s, option opt)
+inline Foam::keyType::keyType(const std::string& str, option opt)
 :
-    word(s, false),
+    word(str, false),  // No stripping
     type_(option(opt & 0x0F))
 {}
 
 
-inline Foam::keyType::keyType(keyType&& s)
-:
-    word(std::move(static_cast<word&>(s)), false),
-    type_(s.type_)
-{
-    s.type_ = option::LITERAL;
-}
-
-
-inline Foam::keyType::keyType(word&& s)
+inline Foam::keyType::keyType(std::string&& str, option opt)
 :
-    word(std::move(s), false),
-    type_(option::LITERAL)
-{}
-
-
-inline Foam::keyType::keyType(string&& s)
-:
-    word(std::move(s), false),
-    type_(option::REGEX)
+    word(std::move(str), false),  // No stripping
+    type_(option(opt & 0x0F))
 {}
 
 
-inline Foam::keyType::keyType(std::string&& s, option opt)
+inline Foam::keyType::keyType(const char* str, option opt)
 :
-    word(std::move(s), false),
+    word(str, false),  // No stripping
     type_(option(opt & 0x0F))
 {}
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline bool Foam::keyType::isLiteral() const
+inline bool Foam::keyType::isLiteral() const noexcept
 {
-    return (type_ != option::REGEX);
+    return !(type_ & option::REGEX);
 }
 
 
-inline bool Foam::keyType::isPattern() const
+inline bool Foam::keyType::isPattern() const noexcept
 {
     return (type_ & option::REGEX);
 }
@@ -138,7 +114,7 @@ inline void Foam::keyType::setType(option opt, bool adjust)
     if (type_ != opt)
     {
         // Only strip when debug is active (potentially costly operation)
-        if (isPattern() && adjust && word::debug)
+        if (adjust && isPattern() && word::debug)
         {
             string::stripInvalid<word>(*this);
         }
@@ -148,35 +124,35 @@ inline void Foam::keyType::setType(option opt, bool adjust)
 }
 
 
-inline bool Foam::keyType::compile()
+inline bool Foam::keyType::compile() noexcept
 {
     type_ = option::REGEX;
     return true;
 }
 
 
-inline void Foam::keyType::uncompile(bool adjust)
+inline void Foam::keyType::uncompile() noexcept
 {
-    setType(option::LITERAL, adjust);
+    type_ = option::LITERAL;
 }
 
 
-inline void Foam::keyType::clear()
+inline void Foam::keyType::uncompile(bool adjust)
 {
-    word::clear();
+    // Only strip when debug is active (potentially costly operation)
+    if (adjust && isPattern() && word::debug)
+    {
+        string::stripInvalid<word>(*this);
+    }
+
     type_ = option::LITERAL;
 }
 
 
-inline void Foam::keyType::swap(keyType& s)
+inline void Foam::keyType::clear()
 {
-    if (this == &s)
-    {
-        return;  // Self-swap is a no-op
-    }
-
-    word::swap(static_cast<word&>(s));
-    std::swap(type_, s.type_);
+    word::clear();
+    type_ = option::LITERAL;
 }
 
 
@@ -188,48 +164,48 @@ inline bool Foam::keyType::operator()(const std::string& text) const
 }
 
 
-inline void Foam::keyType::operator=(const keyType& s)
+inline void Foam::keyType::operator=(const keyType& str)
 {
-    if (this == &s)
+    if (this == &str)
     {
         return;  // Self-assignment is a no-op
     }
 
-    assign(s); // Bypasses char checking
-    type_ = s.type_;
+    assign(str); // Bypasses char checking
+    type_ = str.type_;
 }
 
 
-inline void Foam::keyType::operator=(keyType&& s)
+inline void Foam::keyType::operator=(keyType&& str)
 {
-    if (this == &s)
+    if (this == &str)
     {
         return;  // Self-assignment is a no-op
     }
 
     clear();
-    swap(s);
+    swap(str);
 }
 
 
-inline void Foam::keyType::operator=(const word& s)
+inline void Foam::keyType::operator=(const char* str)
 {
-    assign(s); // Bypasses char checking
+    assign(str); // Bypasses char checking
     type_ = option::LITERAL;
 }
 
 
-inline void Foam::keyType::operator=(const string& s)
+inline void Foam::keyType::operator=(const word& str)
 {
-    assign(s); // Bypasses char checking
-    type_ = option::REGEX;
+    assign(str); // Bypasses char checking
+    type_ = option::LITERAL;
 }
 
 
-inline void Foam::keyType::operator=(const char* s)
+inline void Foam::keyType::operator=(const string& str)
 {
-    assign(s); // Bypasses char checking
-    type_ = option::LITERAL;
+    assign(str); // Bypasses char checking
+    type_ = option::REGEX;
 }
 
 
diff --git a/src/OpenFOAM/primitives/strings/lists/CStringList.H b/src/OpenFOAM/primitives/strings/lists/CStringList.H
index eb6742a78a4bb3770730bb6b2791be743c6b6dc3..7dff8d3f56a23bd13799d482e96fa36c7a5b98b3 100644
--- a/src/OpenFOAM/primitives/strings/lists/CStringList.H
+++ b/src/OpenFOAM/primitives/strings/lists/CStringList.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2016-2019 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -61,7 +61,7 @@ namespace Foam
 {
 
 // Forward Declarations
-template<class String> class SubStrings;
+template<class StringType> class SubStrings;
 
 /*---------------------------------------------------------------------------*\
                          Class CStringList Declaration
@@ -111,7 +111,7 @@ public:
 
     // Constructors
 
-        //- Construct empty, adding content later (via reset).
+        //- Default construct, adding content later (via reset).
         inline CStringList();
 
         //- Copy construct from a list of strings
@@ -190,7 +190,7 @@ public:
         static inline List<StringType> asList(const char * const argv[]);
 
 
-    // Member operators
+    // Member Operators
 
         //- Return element at the given index. No bounds checking.
         inline const char* operator[](int i) const;
diff --git a/src/OpenFOAM/primitives/strings/lists/SubStrings.H b/src/OpenFOAM/primitives/strings/lists/SubStrings.H
index 189865864710563f6c7baa5c26858571031e0e49..79d38b947e08c69237c12201dbbf0190405bf5ee 100644
--- a/src/OpenFOAM/primitives/strings/lists/SubStrings.H
+++ b/src/OpenFOAM/primitives/strings/lists/SubStrings.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -35,7 +35,7 @@ Description
 #ifndef SubStrings_H
 #define SubStrings_H
 
-#include <regex>
+#include <regex>  // For std::sub_match
 #include <string>
 #include <vector>
 
@@ -48,21 +48,22 @@ namespace Foam
                          Class SubStrings Declaration
 \*---------------------------------------------------------------------------*/
 
-template<class String>
+template<class StringType>
 class SubStrings
 :
-    public std::vector<std::sub_match<typename String::const_iterator>>
+    public std::vector<std::sub_match<typename StringType::const_iterator>>
 {
 public:
 
-    // Typedefs
+    // Types
 
         //- The element type
         using value_type =
-            typename std::sub_match<typename String::const_iterator>;
+            typename std::sub_match<typename StringType::const_iterator>;
 
         //- The const_iterator for the underlying string type
-        using string_iterator = typename String::const_iterator;
+        using string_iterator =
+            typename StringType::const_iterator;
 
 
     // Constructors
@@ -87,12 +88,11 @@ public:
             return len;
         }
 
-
         //- Append sub-string defined by begin/end iterators
         void append
         (
-            const typename String::const_iterator& b,
-            const typename String::const_iterator& e
+            const typename StringType::const_iterator& b,
+            const typename StringType::const_iterator& e
         )
         {
             value_type range;
@@ -103,7 +103,6 @@ public:
             this->push_back(range);
         }
 
-
         //- Const reference to the first element,
         //- for consistency with other OpenFOAM containers
         auto first() const -> decltype(this->front())
@@ -111,7 +110,6 @@ public:
             return this->front();
         }
 
-
         //- Const reference to the last element,
         //- for consistency with other OpenFOAM containers
         auto last() const -> decltype(this->back())
@@ -119,9 +117,8 @@ public:
             return this->back();
         }
 
-
-        //- Get element pos, converted to a string type.
-        String str(size_t pos) const
+        //- Get element at pos, converted to a string type.
+        StringType str(size_t pos) const
         {
             return (*this)[pos].str();
         }
diff --git a/src/OpenFOAM/primitives/strings/string/string.C b/src/OpenFOAM/primitives/strings/string/string.C
index 2c6fe97082a81fddb3520050009963e24a9c3876..04f0b91662633a3d0d06f54919c20621c685e604 100644
--- a/src/OpenFOAM/primitives/strings/string/string.C
+++ b/src/OpenFOAM/primitives/strings/string/string.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -78,34 +78,21 @@ bool Foam::string::ext(const word& ending)
 }
 
 
-bool Foam::string::hasExt(const word& ending) const
+bool Foam::string::hasExt(const wordRe& ending) const
 {
-    auto i = find_ext();
-    if (i == npos)
+    if (ending.isLiteral() || ending.empty())
     {
-        return false;
+        return hasExt(static_cast<const std::string&>(ending));
     }
 
-    ++i; // Compare *after* the dot
-    return
-    (
-        // Lengths must match
-        ((size() - i) == ending.size())
-     && !compare(i, npos, ending)
-    );
-}
-
-
-bool Foam::string::hasExt(const wordRe& ending) const
-{
     const auto i = find_ext();
     if (i == npos)
     {
         return false;
     }
 
-    const std::string end = substr(i+1);  // Compare *after* the dot
-    return ending.match(end);
+    // Regex match - compare *after* the dot
+    return ending.match(substr(i+1));
 }
 
 
diff --git a/src/OpenFOAM/primitives/strings/string/string.H b/src/OpenFOAM/primitives/strings/string/string.H
index 6a75e28fcab26218cb05c17bbaae291be245b8de..58e8dd57c13918903380776add1c3b52b32e1543 100644
--- a/src/OpenFOAM/primitives/strings/string/string.H
+++ b/src/OpenFOAM/primitives/strings/string/string.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2016-2020 OpenCFD Ltd.
+    Copyright (C) 2016-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -122,7 +122,10 @@ protected:
         inline bool hasExt() const;
 
         //- Return true if the extension is the same as the given ending.
-        bool hasExt(const word& ending) const;
+        inline bool hasExt(const char* ending) const;
+
+        //- Return true if the extension is the same as the given ending.
+        inline bool hasExt(const std::string& ending) const;
 
         //- Return true if the extension matches the given ending.
         bool hasExt(const wordRe& ending) const;
@@ -161,7 +164,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         string() = default;
 
         //- Copy construct from std::string
@@ -189,16 +192,16 @@ public:
     // Static Member Functions
 
         //- Does the string contain valid characters only?
-        template<class String>
+        template<class StringType>
         static inline bool valid(const std::string& str);
 
         //- Strip invalid characters from the given string
-        template<class String>
+        template<class StringType>
         static inline bool stripInvalid(std::string& str);
 
         //- Return a valid String from the given string
-        template<class String>
-        static inline String validate(const std::string& str);
+        template<class StringType>
+        static inline StringType validate(const std::string& str);
 
 
     // Member Functions
diff --git a/src/OpenFOAM/primitives/strings/string/stringI.H b/src/OpenFOAM/primitives/strings/string/stringI.H
index ac4d6791202dbd4e5974e77a794e77faf0d0e654..9f51cbf5b4e05e5db96140f42677efb63194f3af 100644
--- a/src/OpenFOAM/primitives/strings/string/stringI.H
+++ b/src/OpenFOAM/primitives/strings/string/stringI.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -59,6 +59,26 @@ inline bool Foam::string::hasExt() const
 }
 
 
+inline bool Foam::string::hasExt(const char* ending) const
+{
+    return (ending && string::hasExt(std::string(ending)));
+}
+
+
+inline bool Foam::string::hasExt(const std::string& ending) const
+{
+    const auto len = ending.size();
+    auto i = find_ext();
+    if (i == npos || !len)
+    {
+        return false;
+    }
+
+    ++i; // Compare *after* the dot
+    return ((size() - i) == len) && !compare(i, npos, ending);
+}
+
+
 inline bool Foam::string::removePath()
 {
     const auto i = rfind('/');
@@ -128,12 +148,12 @@ inline Foam::string::string(const size_type len, const char c)
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-template<class String>
+template<class StringType>
 inline bool Foam::string::valid(const std::string& str)
 {
     for (auto iter = str.cbegin(); iter != str.cend(); ++iter)
     {
-        if (!String::valid(*iter))
+        if (!StringType::valid(*iter))
         {
             return false;
         }
@@ -143,10 +163,10 @@ inline bool Foam::string::valid(const std::string& str)
 }
 
 
-template<class String>
+template<class StringType>
 inline bool Foam::string::stripInvalid(std::string& str)
 {
-    if (!valid<String>(str))
+    if (!string::valid<StringType>(str))
     {
         size_type nChar = 0;
         iterator outIter = str.begin();
@@ -155,7 +175,7 @@ inline bool Foam::string::stripInvalid(std::string& str)
         {
             const char c = *iter;
 
-            if (String::valid(c))
+            if (StringType::valid(c))
             {
                 *outIter = c;
                 ++outIter;
@@ -172,17 +192,17 @@ inline bool Foam::string::stripInvalid(std::string& str)
 }
 
 
-template<class String>
-inline String Foam::string::validate(const std::string& str)
+template<class StringType>
+inline StringType Foam::string::validate(const std::string& str)
 {
-    String out;
+    StringType out;
     out.resize(str.size());
 
     size_type len = 0;
     for (auto iter = str.cbegin(); iter != str.cend(); ++iter)
     {
         const char c = *iter;
-        if (String::valid(c))
+        if (StringType::valid(c))
         {
             out[len] = c;
             ++len;
diff --git a/src/OpenFOAM/primitives/strings/string/stringIO.C b/src/OpenFOAM/primitives/strings/string/stringIO.C
index d7e33c0b3a3c7de96b54782b6e5fb1e99e627699..c89aedc8d36036c9f9e1034974fd3f92f157c5ee 100644
--- a/src/OpenFOAM/primitives/strings/string/stringIO.C
+++ b/src/OpenFOAM/primitives/strings/string/stringIO.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2018 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,6 +27,7 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "string.H"
+#include "token.H"
 #include "IOstreams.H"
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
@@ -41,27 +42,27 @@ Foam::string::string(Istream& is)
 
 Foam::Istream& Foam::operator>>(Istream& is, string& val)
 {
-    token t(is);
+    token tok(is);
 
-    if (!t.good())
+    if (tok.isString())
     {
-        FatalIOErrorInFunction(is)
-            << "Bad token - could not get string"
-            << exit(FatalIOError);
-        is.setBad();
-        return is;
-    }
-
-    if (t.isString())
-    {
-        val = t.stringToken();
+        val = tok.stringToken();
     }
     else
     {
-        FatalIOErrorInFunction(is)
-            << "Wrong token type - expected string, found "
-            << t.info()
-            << exit(FatalIOError);
+        FatalIOErrorInFunction(is);
+        if (tok.good())
+        {
+            FatalIOError
+                << "Wrong token type - expected string, found "
+                << tok.info();
+        }
+        else
+        {
+            FatalIOError
+                << "Bad token - could not get string";
+        }
+        FatalIOError << exit(FatalIOError);
         is.setBad();
         return is;
     }
diff --git a/src/OpenFOAM/primitives/strings/word/word.C b/src/OpenFOAM/primitives/strings/word/word.C
index 9cc7dc15c5cf79b5da1e8f38fbc07cf5a64c5860..47c9ddd9f788118c58e9ea79fd07ac1e7637b602 100644
--- a/src/OpenFOAM/primitives/strings/word/word.C
+++ b/src/OpenFOAM/primitives/strings/word/word.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011 OpenFOAM Foundation
-    Copyright (C) 2017-2020 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -112,7 +112,7 @@ Foam::word Foam::word::validate
 
 Foam::word Foam::word::lessExt() const
 {
-    const size_type i = find_ext();
+    const auto i = find_ext();
 
     if (i == npos)
     {
@@ -136,18 +136,6 @@ Foam::word& Foam::word::ext(const word& ending)
 }
 
 
-bool Foam::word::hasExt(const word& ending) const
-{
-    return string::hasExt(ending);
-}
-
-
-bool Foam::word::hasExt(const wordRe& ending) const
-{
-    return string::hasExt(ending);
-}
-
-
 // * * * * * * * * * * * * * * * Global Operators  * * * * * * * * * * * * * //
 
 Foam::word Foam::operator&(const word& a, const word& b)
diff --git a/src/OpenFOAM/primitives/strings/word/word.H b/src/OpenFOAM/primitives/strings/word/word.H
index b58f3c50d29a49622bf41b6fc331c50c41eeedd1..d05abd312f3edeaecfed023483691d931a40870a 100644
--- a/src/OpenFOAM/primitives/strings/word/word.H
+++ b/src/OpenFOAM/primitives/strings/word/word.H
@@ -50,11 +50,10 @@ SourceFiles
 namespace Foam
 {
 
-// Forward declarations
+// Forward Declarations
 class word;
-Istream& operator>>(Istream& is, word& w);
-Ostream& operator<<(Ostream& os, const word& w);
-
+Istream& operator>>(Istream& is, word& val);
+Ostream& operator<<(Ostream& os, const word& val);
 
 /*---------------------------------------------------------------------------*\
                            Class word Declaration
@@ -80,7 +79,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct
         word() = default;
 
         //- Copy construct
@@ -169,17 +168,11 @@ public:
         //  or when the file name is empty or ended with a '/'.
         word& ext(const word& ending);
 
-        //- Return true if it has an extension or simply ends with a '.'
-        inline bool hasExt() const;
-
-        //- Return true if the extension is the same as the given ending.
-        bool hasExt(const word& ending) const;
-
-        //- Return true if the extension matches the given ending.
-        bool hasExt(const wordRe& ending) const;
+        //- Various checks for extensions
+        using string::hasExt;
 
         //- Remove extension, returning true if string changed.
-        inline bool removeExt();
+        using string::removeExt;
 
 
     // Member Operators
diff --git a/src/OpenFOAM/primitives/strings/word/wordI.H b/src/OpenFOAM/primitives/strings/word/wordI.H
index 4e93d536c16852e64e1311636a7afe6adc924a3d..bc907c744d7a2aa263f52a5fc3b9784f5793c9c4 100644
--- a/src/OpenFOAM/primitives/strings/word/wordI.H
+++ b/src/OpenFOAM/primitives/strings/word/wordI.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2017 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -26,7 +26,6 @@ License
 
 \*---------------------------------------------------------------------------*/
 
-#include <cctype>
 #include <iostream>  // For std::cerr
 
 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
@@ -57,6 +56,21 @@ inline Foam::word Foam::word::printf
 }
 
 
+inline bool Foam::word::valid(char c)
+{
+    return
+    (
+        !isspace(c)
+     && c != '"'   // string quote
+     && c != '\''  // string quote
+     && c != '/'   // path separator
+     && c != ';'   // end statement
+     && c != '{'   // beg block (eg, subdict)
+     && c != '}'   // end block (eg, subdict)
+    );
+}
+
+
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 inline Foam::word::word(const string& s, bool doStrip)
@@ -127,21 +141,6 @@ inline Foam::word::word(const char* s, size_type len, bool doStrip)
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline bool Foam::word::valid(char c)
-{
-    return
-    (
-        !isspace(c)
-     && c != '"'   // string quote
-     && c != '\''  // string quote
-     && c != '/'   // path separator
-     && c != ';'   // end statement
-     && c != '{'   // beg subdict
-     && c != '}'   // end subdict
-    );
-}
-
-
 inline void Foam::word::stripInvalid()
 {
     // Only strip when debug is active (potentially costly operation)
@@ -162,18 +161,6 @@ inline void Foam::word::stripInvalid()
 }
 
 
-inline bool Foam::word::hasExt() const
-{
-    return string::hasExt();
-}
-
-
-inline bool Foam::word::removeExt()
-{
-    return string::removeExt();
-}
-
-
 // * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
 
 inline Foam::word& Foam::word::operator=(const word& s)
diff --git a/src/OpenFOAM/primitives/strings/word/wordIO.C b/src/OpenFOAM/primitives/strings/word/wordIO.C
index 04ec553e766cccac33642a44797071a1b366c157..03e46193ca69ebb23483e10041003fb67025dd79 100644
--- a/src/OpenFOAM/primitives/strings/word/wordIO.C
+++ b/src/OpenFOAM/primitives/strings/word/wordIO.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2015 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,58 +27,59 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "word.H"
+#include "token.H"
 #include "IOstreams.H"
 
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
 Foam::word::word(Istream& is)
-:
-    string()
 {
     is >> *this;
 }
 
 
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
 Foam::Istream& Foam::operator>>(Istream& is, word& val)
 {
-    token t(is);
-
-    if (!t.good())
-    {
-        FatalIOErrorInFunction(is)
-            << "Bad token - could not get word"
-            << exit(FatalIOError);
-        is.setBad();
-        return is;
-    }
+    token tok(is);
 
-    if (t.isWord())
+    if (tok.isWord())
     {
-        val = t.wordToken();
+        val = tok.wordToken();
     }
-    else if (t.isString())
+    else if (tok.isQuotedString())
     {
         // Try a bit harder and convert string to word
-        val = t.stringToken();
+        val = tok.stringToken();
+        const auto oldLen = val.length();
         string::stripInvalid<word>(val);
 
         // Flag empty strings and bad chars as an error
-        if (val.empty() || val.size() != t.stringToken().size())
+        if (val.empty() || val.length() != oldLen)
         {
             FatalIOErrorInFunction(is)
                 << "Empty word or non-word characters "
-                << t.info()
-                << exit(FatalIOError);
+                << tok.info() << exit(FatalIOError);
             is.setBad();
             return is;
         }
     }
     else
     {
-        FatalIOErrorInFunction(is)
-            << "Wrong token type - expected word, found "
-            << t.info()
-            << exit(FatalIOError);
+        FatalIOErrorInFunction(is);
+        if (tok.good())
+        {
+            FatalIOError
+                << "Wrong token type - expected word, found "
+                << tok.info();
+        }
+        else
+        {
+            FatalIOError
+                << "Bad token - could not get word";
+        }
+        FatalIOError << exit(FatalIOError);
         is.setBad();
         return is;
     }
diff --git a/src/OpenFOAM/primitives/strings/wordRe/wordRe.C b/src/OpenFOAM/primitives/strings/wordRe/wordRe.C
index bf9e9cae67ae3199722cd6fa82fa22f50933244f..9f74c7ac23f0023f441fc93a6e1cb273aabc045a 100644
--- a/src/OpenFOAM/primitives/strings/wordRe/wordRe.C
+++ b/src/OpenFOAM/primitives/strings/wordRe/wordRe.C
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2018-2019 OpenCFD Ltd.
+    Copyright (C) 2018-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -27,8 +27,9 @@ License
 \*---------------------------------------------------------------------------*/
 
 #include "wordRe.H"
+#include "keyType.H"
+#include "token.H"
 #include "IOstreams.H"
-#include "InfoProxy.H"
 
 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
 
@@ -37,59 +38,75 @@ const Foam::wordRe Foam::wordRe::null;
 
 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
 
-Foam::wordRe::wordRe(Istream& is)
+Foam::wordRe::wordRe(const keyType& str)
 :
-    word(),
-    re_(nullptr)
+    word(str, false)  // No stripping
+{
+    if (str.isPattern())
+    {
+        compile();
+    }
+}
+
+
+Foam::wordRe::wordRe(Istream& is)
 {
     is >> *this;
 }
 
 
-// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-Foam::Ostream& Foam::wordRe::info(Ostream& os) const
+bool Foam::wordRe::assign(const token& tok)
 {
-    if (isPattern())
+    if (tok.isWord())
     {
-        os  << "wordRe(regex) " << *this;
+        // Assign from word - literal
+        assign(tok.wordToken());
+        uncompile();
+        return true;
     }
-    else
+    else if (tok.isQuotedString())
     {
-        os  << "wordRe(plain) \"" << *this << '"';
+        // Assign from quoted string - auto-detect regex
+        assign(tok.stringToken());
+        compile(wordRe::DETECT);
+        return true;
     }
 
-    return os;
+    return false;
 }
 
 
-Foam::Istream& Foam::operator>>(Istream& is, wordRe& val)
-{
-    token t(is);
+// * * * * * * * * * * * * * * * Member Operators  * * * * * * * * * * * * * //
 
-    if (!t.good())
+void Foam::wordRe::operator=(const keyType& str)
+{
+    assign(str);
+    if (str.isPattern())
     {
-        FatalIOErrorInFunction(is)
-            << "Bad token - could not get wordRe"
-            << exit(FatalIOError);
-        is.setBad();
-        return is;
+        compile();
     }
-
-    if (t.isWord())
+    else
     {
-        val = t.wordToken();
+        uncompile();
     }
-    else if (t.isString())
-    {
-        // Auto-detects regex
-        val = t.stringToken();
+}
 
-        // Flag empty strings as an error
+
+// * * * * * * * * * * * * * * * IOstream Operators  * * * * * * * * * * * * //
+
+Foam::Istream& Foam::operator>>(Istream& is, wordRe& val)
+{
+    token tok(is);
+
+    if (val.assign(tok))
+    {
         if (val.empty())
         {
+            // Empty strings are an error
             FatalIOErrorInFunction(is)
-                << "Empty word/expression"
+                << "Zero-length regex"
                 << exit(FatalIOError);
             is.setBad();
             return is;
@@ -97,10 +114,19 @@ Foam::Istream& Foam::operator>>(Istream& is, wordRe& val)
     }
     else
     {
-        FatalIOErrorInFunction(is)
-            << "Wrong token type - expected word or string, found "
-            << t.info()
-            << exit(FatalIOError);
+        FatalIOErrorInFunction(is);
+        if (tok.good())
+        {
+            FatalIOError
+                << "Wrong token type - expected word or string, found "
+                << tok.info();
+        }
+        else
+        {
+            FatalIOError
+                << "Bad token - could not get wordRe";
+        }
+        FatalIOError << exit(FatalIOError);
         is.setBad();
         return is;
     }
diff --git a/src/OpenFOAM/primitives/strings/wordRe/wordRe.H b/src/OpenFOAM/primitives/strings/wordRe/wordRe.H
index 938468185ecf3aa59aa08ee411bc2e5c241a1d54..c1467d8d428a16d4f3ed32817d59c4509ea8c015 100644
--- a/src/OpenFOAM/primitives/strings/wordRe/wordRe.H
+++ b/src/OpenFOAM/primitives/strings/wordRe/wordRe.H
@@ -6,7 +6,7 @@
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
     Copyright (C) 2011-2016 OpenFOAM Foundation
-    Copyright (C) 2017-2019 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -42,10 +42,11 @@ Description
 
 Note
     If the string contents are changed - eg, by the operator+=() or by
-    string::replace(), etc - it will be necessary to use compile() to
-    synchronize the regular expression.
+    string::replace(), etc - it will be necessary to use compile()
+    or uncompile() to synchronize the regular expression.
 
 SourceFiles
+    wordReI.H
     wordRe.C
 
 \*---------------------------------------------------------------------------*/
@@ -55,7 +56,7 @@ SourceFiles
 
 #include "word.H"
 #include "regExp.H"
-#include "keyType.H"
+#include "stdFoam.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
@@ -63,8 +64,11 @@ namespace Foam
 {
 
 // Forward Declarations
-class Istream;
-class Ostream;
+class keyType;
+class token;
+class wordRe;
+Istream& operator>>(Istream& is, wordRe& val);
+Ostream& operator<<(Ostream& os, const wordRe& val);
 
 /*---------------------------------------------------------------------------*\
                            Class wordRe Declaration
@@ -99,7 +103,7 @@ public:
             ICASE   = 2, //!< Ignore case in regular expression
             NOCASE  = 2, //!< \deprecated(2018-04) Alias for ICASE
             DETECT  = 4, //!< Detect if the string contains meta-characters
-            UNKNOWN = 4, //!< Unknown content.
+            UNKNOWN = 4, //!< Unknown content (for return value).
             REGEX_ICASE  = (REGEX|ICASE),   //!< Combined REGEX and ICASE
             DETECT_ICASE = (DETECT|ICASE),  //!< Combined DETECT and ICASE
         };
@@ -107,7 +111,7 @@ public:
 
     // Constructors
 
-        //- Construct null
+        //- Default construct, empty literal
         inline wordRe();
 
         //- Copy construct
@@ -116,69 +120,75 @@ public:
         //- Move construct
         inline wordRe(wordRe&& str);
 
-        //- Construct from keyType, using its compile information
-        inline explicit wordRe(const keyType& str);
-
-        //- Copy from character array, treat as a literal
-        inline explicit wordRe(const char* str);
-
-        //- Copy from std::string, treat as a literal
-        inline explicit wordRe(const std::string& str);
-
-        //- Copy from string, treat as a literal
-        inline explicit wordRe(const string& str);
-
-        //- Copy from word, treat as a literal
-        inline explicit wordRe(const word& str);
-
-        //- Copy from keyType, use specified compile option
-        inline wordRe(const keyType& str, const compOption opt);
-
-        //- Copy from character array, use specified compile option
-        inline wordRe(const char* str, const compOption opt);
-
-        //- Copy from std::string, use specified compile option
-        inline wordRe(const std::string& str, const compOption opt);
-
-        //- Copy from string, use specified compile option
-        inline wordRe(const string& str, const compOption opt);
-
-        //- Copy from word, use specified compile option
-        inline wordRe(const word& str, const compOption opt);
-
-        //- Construct from Istream
-        //  Words are treated as literals, strings with an auto-test
+        //- Implicit copy construct from word, as LITERAL
+        inline wordRe(const word& str);
+
+        //- Implicit move construct from word, as LITERAL
+        inline wordRe(word&& str);
+
+        //- Implicit copy construct from other string-types,
+        //- with specified compile option (default is LITERAL)
+        inline wordRe
+        (
+            const std::string& str,
+            const compOption opt = compOption::LITERAL
+        );
+
+        //- Implicit construct from character array,
+        //- with specified compile option (default is LITERAL)
+        inline wordRe
+        (
+            const char* str,
+            const compOption opt = compOption::LITERAL
+        );
+
+        //- Implicit copy construct from keyType, using its compile type
+        wordRe(const keyType& str);
+
+        //- Construct from Istream by reading a token
+        //  Words are treated as literals, strings with an auto-detect
         explicit wordRe(Istream& is);
 
 
     // Member Functions
 
-        //- Is this character valid for a wordRe?
-        //  This is largely identical with what word accepts, but also
-        //  permit brace-brackets, which are valid for some regexs.
-        inline static bool valid(char c);
+        //- Test for valid wordRe character?
+        //  Like Foam::word, but with brace-brackets,
+        //  which are valid for some regexs.
+        inline static bool valid(const char c);
 
 
     // Access
 
         //- The wordRe is treated as literal string, not as pattern.
-        inline bool isLiteral() const;
+        inline bool isLiteral() const noexcept;
 
         //- The wordRe is treated as a pattern, not as literal string.
-        inline bool isPattern() const;
+        inline bool isPattern() const noexcept;
 
 
     // Infrastructure
 
-        //- Compile the regular expression
+        //- Inherit all regular string assign() methods
+        using word::assign;
+
+        //- Assign from word or string token.
+        //  Words are treated as literals, strings with an auto-detect
+        //  \return false if the token was the incorrect type
+        bool assign(const token& tok);
+
+        //- Compile as regular expression
         inline bool compile();
 
+        //- Mark as literal string, remove any regular expression
+        inline void uncompile();
+
         //- Possibly compile the regular expression, with greater control
         inline bool compile(const compOption opt);
 
-        //- Make wordRe a literal again, instead of a regular expression.
-        //  Optionally strip invalid word characters.
-        inline void uncompile(bool adjust = false);
+        //- Mark as literal string, optionally stripping invalid word
+        //- characters when changing to a literal
+        inline void uncompile(bool adjust);
 
 
     // Editing
@@ -203,12 +213,6 @@ public:
         inline bool match(const std::string& text, bool literal=false) const;
 
 
-    // Miscellaneous
-
-        //- Output some basic info
-        Ostream& info(Ostream& os) const;
-
-
     // Member Operators
 
         //- Perform smart match on text, as per match()
@@ -225,7 +229,7 @@ public:
 
         //- Copy keyType and its type (literal or regex)
         //  Always case sensitive
-        inline void operator=(const keyType& str);
+        void operator=(const keyType& str);
 
         //- Copy string, auto-test for regular expression
         //  Always case sensitive
diff --git a/src/OpenFOAM/primitives/strings/wordRe/wordReI.H b/src/OpenFOAM/primitives/strings/wordRe/wordReI.H
index b3f7171717fc5622896e2cfc2faadc717f41d336..215f5da116c609946f7a63f5834c4f8b3166a7b4 100644
--- a/src/OpenFOAM/primitives/strings/wordRe/wordReI.H
+++ b/src/OpenFOAM/primitives/strings/wordRe/wordReI.H
@@ -28,9 +28,10 @@ License
 
 // * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * * //
 
-inline bool Foam::wordRe::valid(char c)
+inline bool Foam::wordRe::valid(const char c)
 {
-    return keyType::valid(c);
+    // Also accept '{' and '}' (for regex grouping?)
+    return (word::valid(c) || c == '{' || c == '}');
 }
 
 
@@ -45,8 +46,7 @@ inline Foam::wordRe::wordRe()
 
 inline Foam::wordRe::wordRe(const wordRe& str)
 :
-    word(str, false),
-    re_()
+    word(static_cast<const word&>(str))
 {
     if (str.isPattern())
     {
@@ -62,52 +62,23 @@ inline Foam::wordRe::wordRe(wordRe&& str)
 {}
 
 
-inline Foam::wordRe::wordRe(const keyType& str)
-:
-    word(str, false),
-    re_()
-{
-    if (str.isPattern())
-    {
-        compile();
-    }
-}
-
-
-inline Foam::wordRe::wordRe(const char* str)
-:
-    word(str, false),
-    re_()
-{}
-
-
-inline Foam::wordRe::wordRe(const std::string& str)
-:
-    word(str, false),
-    re_()
-{}
-
-
-inline Foam::wordRe::wordRe(const string& str)
+inline Foam::wordRe::wordRe(const word& str)
 :
-    word(str, false),
-    re_()
+    word(str)
 {}
 
 
-inline Foam::wordRe::wordRe(const word& str)
+inline Foam::wordRe::wordRe(word&& str)
 :
-    word(str, false),
-    re_()
+    word(std::move(str))
 {}
 
 
-inline Foam::wordRe::wordRe(const keyType& str, const compOption opt)
+inline Foam::wordRe::wordRe(const std::string& str, const compOption opt)
 :
-    word(str, false),
-    re_()
+    word(str, false)  // No stripping
 {
-    if (str.isPattern())
+    if (opt != wordRe::LITERAL)
     {
         compile(opt);
     }
@@ -116,45 +87,24 @@ inline Foam::wordRe::wordRe(const keyType& str, const compOption opt)
 
 inline Foam::wordRe::wordRe(const char* str, const compOption opt)
 :
-    wordRe(str)
-{
-    compile(opt);
-}
-
-
-inline Foam::wordRe::wordRe(const std::string& str, const compOption opt)
-:
-    wordRe(str)
+    word(str, false)  // No stripping
 {
-    compile(opt);
-}
-
-
-inline Foam::wordRe::wordRe(const string& str, const compOption opt)
-:
-    wordRe(str)
-{
-    compile(opt);
-}
-
-
-inline Foam::wordRe::wordRe(const word& str, const compOption opt)
-:
-    wordRe(str)
-{
-    compile(opt);
+    if (opt != wordRe::LITERAL)
+    {
+        compile(opt);
+    }
 }
 
 
 // * * * * * * * * * * * * * * * Member Functions  * * * * * * * * * * * * * //
 
-inline bool Foam::wordRe::isLiteral() const
+inline bool Foam::wordRe::isLiteral() const noexcept
 {
     return !re_.exists();
 }
 
 
-inline bool Foam::wordRe::isPattern() const
+inline bool Foam::wordRe::isPattern() const noexcept
 {
     return re_.exists();
 }
@@ -162,7 +112,7 @@ inline bool Foam::wordRe::isPattern() const
 
 inline bool Foam::wordRe::compile(const compOption opt)
 {
-    if (opt)
+    if (opt != wordRe::LITERAL)
     {
         bool comp = false;
 
@@ -197,13 +147,20 @@ inline bool Foam::wordRe::compile()
 }
 
 
+inline void Foam::wordRe::uncompile()
+{
+    re_.clear();
+}
+
+
 inline void Foam::wordRe::uncompile(bool adjust)
 {
     // Only strip when debug is active (potentially costly operation)
-    if (re_.clear() && adjust && word::debug)
+    if (adjust && isPattern() && word::debug)
     {
         string::stripInvalid<word>(*this);
     }
+    re_.clear();
 }
 
 
@@ -285,20 +242,6 @@ inline void Foam::wordRe::operator=(const word& str)
 }
 
 
-inline void Foam::wordRe::operator=(const keyType& str)
-{
-    assign(str);
-    if (str.isPattern())
-    {
-        compile();
-    }
-    else
-    {
-        re_.clear();
-    }
-}
-
-
 inline void Foam::wordRe::operator=(const string& str)
 {
     assign(str);
diff --git a/src/OpenFOAM/primitives/strings/wordRes/wordRes.H b/src/OpenFOAM/primitives/strings/wordRes/wordRes.H
index 076e77b47557f3dd3d9fb4971a692a00f0149f60..5b368260ed9247f58127947f8e8b2e0e14d51e41 100644
--- a/src/OpenFOAM/primitives/strings/wordRes/wordRes.H
+++ b/src/OpenFOAM/primitives/strings/wordRes/wordRes.H
@@ -56,21 +56,22 @@ class wordRes
     // Private Methods
 
         //- Smart match as literal or regex, stopping on the first match.
-        inline static bool found_match
+        //  \return index of first match, -1 if not found
+        inline static label first_match
         (
-            const UList<wordRe>& patterns,
+            const UList<wordRe>& selectors,
             const std::string& text,
-            bool literal=false
+            const bool literal=false
         );
 
-        //- Smart match across entire list, returning the match type.
+        //- Smart match across entire list, returning the best match type.
         //  Stops on the first literal match, or continues to examine
         //  if a regex match occurs.
         //  \return wordRe::LITERAL, wordRe::REGEX on match and
         //      wordRe::UNKNOWN otherwise.
         inline static wordRe::compOption found_matched
         (
-            const UList<wordRe>& patterns,
+            const UList<wordRe>& selectors,
             const std::string& text
         );
 
@@ -84,17 +85,14 @@ public:
         {
             const UList<wordRe>& values;
 
-            matcher(const UList<wordRe>& list)
+            matcher(const UList<wordRe>& selectors)
             :
-                values(list)
+                values(selectors)
             {}
 
-            //- Return true if string matches ANY of the regular expressions
+            //- True if text matches ANY of the entries.
             //  Allows use as a predicate.
-            bool operator()(const std::string& text) const
-            {
-                return found_match(values, text);
-            }
+            inline bool operator()(const std::string& text) const;
         };
 
 
@@ -104,7 +102,7 @@ public:
         inline static const wordRes& null();
 
         //- Return a wordRes with duplicate entries filtered out.
-        //  No distinction made between literals or regular expressions.
+        //  No distinction made between literals and regular expressions.
         static wordRes uniq(const UList<wordRe>& input);
 
 
@@ -121,7 +119,7 @@ public:
     // Member Functions
 
         //- Filter out duplicate entries (inplace).
-        //  No distinction made between literals or regular expressions.
+        //  No distinction made between literals and regular expressions.
         void uniq();
 
         //- Smart match as literal or regex, stopping on the first match.
@@ -133,16 +131,16 @@ public:
         //- Smart match in the list of matchers, returning the match type.
         //  It stops if there is a literal match, or continues to examine
         //  other regexs.
-        //  \return LITERAL if a lteral match was found, REGEX if a regex
-        //      match was found and UNKNOWN otherwise.
+        //  \return LITERAL if a lteral match was found,
+        //      REGEX if any regex match was found,
+        //      UNKNOWN otherwise.
         inline wordRe::compOption matched(const std::string& text) const;
 
-        //- Extract list indices for all matches.
+        //- Return list indices for all matches.
         //
         //  \param input  A list of string inputs to match against
         //  \param invert invert the matching logic
-        //  \return The locations (indices) in the input list where match()
-        //      is true
+        //  \return indices of the matches in the input list
         template<class StringType>
         inline labelList matching
         (
diff --git a/src/OpenFOAM/primitives/strings/wordRes/wordResI.H b/src/OpenFOAM/primitives/strings/wordRes/wordResI.H
index a62057aa9500fffe1fb2d3f04b5c9aa1905582d4..a0eb765fd5bd2652b7b26e38328451feacd61fc4 100644
--- a/src/OpenFOAM/primitives/strings/wordRes/wordResI.H
+++ b/src/OpenFOAM/primitives/strings/wordRes/wordResI.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2017-2018 OpenCFD Ltd.
+    Copyright (C) 2017-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -33,34 +33,36 @@ inline const Foam::wordRes& Foam::wordRes::null()
 }
 
 
-inline bool Foam::wordRes::found_match
+inline Foam::label Foam::wordRes::first_match
 (
-    const UList<wordRe>& patterns,
+    const UList<wordRe>& selectors,
     const std::string& text,
-    bool literal
+    const bool literal
 )
 {
-    for (const wordRe& select : patterns)
+    label index = 0;
+    for (const wordRe& select : selectors)
     {
         if (select.match(text, literal))
         {
-            return true;
+            return index;
         }
+        ++index;
     }
 
-    return false;
+    return -1;
 }
 
 
 inline Foam::wordRe::compOption Foam::wordRes::found_matched
 (
-    const UList<wordRe>& patterns,
+    const UList<wordRe>& selectors,
     const std::string& text
 )
 {
     auto retval(wordRe::compOption::UNKNOWN);
 
-    for (const wordRe& select : patterns)
+    for (const wordRe& select : selectors)
     {
         if (select.isLiteral())
         {
@@ -69,12 +71,14 @@ inline Foam::wordRe::compOption Foam::wordRes::found_matched
                 return wordRe::compOption::LITERAL;
             }
         }
-        else if (wordRe::compOption::UNKNOWN == retval)
+        else if
+        (
+            // Only match regex once
+            retval == wordRe::compOption::UNKNOWN
+         && select.match(text, false)
+        )
         {
-            if (select.match(text, false))
-            {
-                retval = wordRe::compOption::REGEX;
-            }
+            retval = wordRe::compOption::REGEX;
         }
     }
 
@@ -86,7 +90,7 @@ inline Foam::wordRe::compOption Foam::wordRes::found_matched
 
 inline bool Foam::wordRes::match(const std::string& text, bool literal) const
 {
-    return found_match(*this, text, literal);
+    return (first_match(*this, text, literal) >= 0);
 }
 
 
@@ -127,7 +131,13 @@ inline Foam::labelList Foam::wordRes::matching
 
 inline bool Foam::wordRes::operator()(const std::string& text) const
 {
-    return found_match(*this, text);
+    return (wordRes::first_match(*this, text) >= 0);
+}
+
+
+inline bool Foam::wordRes::matcher::operator()(const std::string& text) const
+{
+    return (wordRes::first_match(values, text) >= 0);
 }
 
 
diff --git a/src/finiteVolume/functionObjects/fieldSelections/fieldSelection/fieldInfo.H b/src/finiteVolume/functionObjects/fieldSelections/fieldSelection/fieldInfo.H
index b9e282e2c32edb425e944268a486b94cd8d1147b..db79172a3883781b3e3affa0a000453a0c4ceef4 100644
--- a/src/finiteVolume/functionObjects/fieldSelections/fieldSelection/fieldInfo.H
+++ b/src/finiteVolume/functionObjects/fieldSelections/fieldSelection/fieldInfo.H
@@ -5,7 +5,7 @@
     \\  /    A nd           | www.openfoam.com
      \\/     M anipulation  |
 -------------------------------------------------------------------------------
-    Copyright (C) 2019 OpenCFD Ltd.
+    Copyright (C) 2019-2021 OpenCFD Ltd.
 -------------------------------------------------------------------------------
 License
     This file is part of OpenFOAM.
@@ -35,8 +35,8 @@ Description
 #ifndef functionObjects_fieldInfo_H
 #define functionObjects_fieldInfo_H
 
-#include "wordRe.H"
 #include "label.H"
+#include "wordRes.H"
 #include "Switch.H"
 
 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@@ -46,19 +46,20 @@ namespace Foam
 namespace functionObjects
 {
 
-/*---------------------------------------------------------------------------*\
-                          Class fieldInfo Declaration
-\*---------------------------------------------------------------------------*/
-
+// Forward Declarations
 class fieldInfo;
 Istream& operator>>(Istream&, fieldInfo&);
 Ostream& operator<<(Ostream&, const fieldInfo&);
 
+/*---------------------------------------------------------------------------*\
+                          Class fieldInfo Declaration
+\*---------------------------------------------------------------------------*/
+
 class fieldInfo
 {
-    // Pivate data
+    // Pivate Data
 
-        //- Pattern for the field name
+        //- Pattern for the field name(s)
         wordRe name_;
 
         //- Field component
@@ -72,10 +73,10 @@ public:
 
     // Constructors
 
-        //- Null constructor
+        //- Default construct
         fieldInfo()
         :
-            name_(word::null),
+            name_(),
             component_(-1),
             found_(false)
         {}
@@ -153,6 +154,7 @@ public:
 } // End namespace functionObjects
 } // End namespace Foam
 
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
 
 #endif
 
diff --git a/src/meshTools/coordinate/systems/coordinateSystems.C b/src/meshTools/coordinate/systems/coordinateSystems.C
index d84a32301ee711e3c27aa6701075db33aae711dd..944fb6ded5d15840e3c69f120d6cf2e3ce678a4e 100644
--- a/src/meshTools/coordinate/systems/coordinateSystems.C
+++ b/src/meshTools/coordinate/systems/coordinateSystems.C
@@ -321,12 +321,20 @@ Foam::wordList Foam::coordinateSystems::names(const keyType& key) const
 
 Foam::wordList Foam::coordinateSystems::names(const wordRe& matcher) const
 {
+    if (matcher.empty())
+    {
+        return wordList();
+    }
     return PtrListOps::names(*this, matcher);
 }
 
 
 Foam::wordList Foam::coordinateSystems::names(const wordRes& matcher) const
 {
+    if (matcher.empty())
+    {
+        return wordList();
+    }
     return PtrListOps::names(*this, matcher);
 }
 
diff --git a/tutorials/IO/dictionary/good-if.dict b/tutorials/IO/dictionary/good-if1.dict
similarity index 100%
rename from tutorials/IO/dictionary/good-if.dict
rename to tutorials/IO/dictionary/good-if1.dict
diff --git a/tutorials/IO/dictionary/good-if3.dict b/tutorials/IO/dictionary/good-if3.dict
new file mode 100644
index 0000000000000000000000000000000000000000..c859ba2245b3526577824e8fabe3aab7d9945488
--- /dev/null
+++ b/tutorials/IO/dictionary/good-if3.dict
@@ -0,0 +1,31 @@
+/*--------------------------------*- C++ -*----------------------------------*\
+| =========                 |                                                 |
+| \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox           |
+|  \\    /   O peration     | Version:  v2012                                 |
+|   \\  /    A nd           | Website:  www.openfoam.com                      |
+|    \\/     M anipulation  |                                                 |
+\*---------------------------------------------------------------------------*/
+FoamFile
+{
+    version     2.0;
+    format      ascii;
+    class       dictionary;
+    object      dictionary;
+}
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+
+// (label == scalar) ?
+#ifeq 0 0.0
+labelEqScalar true;
+#else
+labelEqScalar false; } // Provoke parse error if we see this branch
+#endif
+
+// (scalar == label) ?
+#ifeq 0.0 0
+scalarEqLabel true;
+#else
+scalarEqLabel false; } // Provoke parse error if we see this branch
+#endif
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //