diff --git a/applications/test/splitFunctionArgs/Make/files b/applications/test/splitFunctionArgs/Make/files
new file mode 100644
index 0000000000..5bb22f6261
--- /dev/null
+++ b/applications/test/splitFunctionArgs/Make/files
@@ -0,0 +1,3 @@
+Test-splitFunctionArgs.C
+
+EXE = $(FOAM_USER_APPBIN)/Test-splitFunctionArgs
diff --git a/applications/test/splitFunctionArgs/Make/options b/applications/test/splitFunctionArgs/Make/options
new file mode 100644
index 0000000000..18e6fe47af
--- /dev/null
+++ b/applications/test/splitFunctionArgs/Make/options
@@ -0,0 +1,2 @@
+/* EXE_INC = */
+/* EXE_LIBS = */
diff --git a/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C b/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C
new file mode 100644
index 0000000000..8deaf6b5d0
--- /dev/null
+++ b/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C
@@ -0,0 +1,155 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / 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 .
+
+Application
+ Test-splitFunctionArgs
+
+Description
+ Test splitting of function name args
+
+\*---------------------------------------------------------------------------*/
+
+#include "argList.H"
+#include "IOstreams.H"
+#include "IOobject.H"
+#include "IFstream.H"
+#include "dictionary.H"
+#include "stringOps.H"
+#include "Tuple2.H"
+
+using namespace Foam;
+
+// Split out function name and any arguments
+// - use as per functionObjectList
+void testFunctionNameAndArgsSplit(const std::string& line)
+{
+ word funcName;
+ wordRes args;
+ List> namedArgs;
+
+ const auto lbracket = line.find('(');
+ if (lbracket == std::string::npos)
+ {
+ funcName = word::validate(line);
+ // No args
+ }
+ else
+ {
+ funcName = word::validate(line.substr(0, lbracket));
+ std::string params;
+
+ const auto rbracket = line.rfind(')');
+ if (rbracket != std::string::npos && lbracket < rbracket)
+ {
+ params = line.substr(lbracket+1, (rbracket - lbracket - 1));
+ }
+ else
+ {
+ params = line.substr(lbracket+1);
+ }
+
+ Info<<"parsing: " << params << nl;
+
+ stringOps::splitFunctionArgs(params, args, namedArgs);
+ }
+
+ Info<< nl
+ << line << nl
+ << "function: <" << funcName << '>' << nl
+ << " args: " << args << nl
+ << " named: " << namedArgs << nl;
+}
+
+
+// Split out any arguments
+void testArgsSplit(const std::string& line)
+{
+ wordRes args;
+ List> namedArgs;
+ stringOps::splitFunctionArgs(line, args, namedArgs);
+
+ Info<< nl
+ << line << nl
+ << " args: " << args << nl
+ << " named: " << namedArgs << nl;
+}
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// Main program:
+
+int main(int argc, char *argv[])
+{
+ argList::noBanner();
+ argList::noParallel();
+ argList::addArgument("file1 .. fileN");
+ argList args(argc, argv, false, true);
+
+ if (args.size() <= 1)
+ {
+ InfoErr<< "Provide a file or files to test" << nl;
+ }
+ else
+ {
+ for (label argi=1; argi < args.size(); ++argi)
+ {
+ IOobject::writeDivider(Info);
+
+ const auto inputFile = args.get(argi);
+ IFstream is(inputFile);
+
+ string line;
+ while (is.getLine(line))
+ {
+ if (line.empty() || line[0] == '#')
+ {
+ continue;
+ }
+
+ if (line.starts_with("function:"))
+ {
+ auto trim = line.find(':');
+ ++trim;
+ while (isspace(line[trim]))
+ {
+ ++trim;
+ }
+
+ line.erase(0, trim);
+ testFunctionNameAndArgsSplit(line);
+ }
+ else
+ {
+ testArgsSplit(line);
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+// ************************************************************************* //
diff --git a/applications/test/splitFunctionArgs/testNames1 b/applications/test/splitFunctionArgs/testNames1
new file mode 100644
index 0000000000..8d771f5e5d
--- /dev/null
+++ b/applications/test/splitFunctionArgs/testNames1
@@ -0,0 +1,47 @@
+# -----------------------------------------------------------------------------
+# Test names for splitting. Comment character as per -*- sh -*- mode
+#
+# Prefix with "function: " to test with function name splitting
+
+function: basic
+
+function: func1( a , b );
+
+function: func2(a, value=10);
+
+# discard or flag bad/missing parameters?
+function: func3(a , , , value=10);
+
+function: func4();
+
+function: func5( abc );
+
+start=1, end=2
+
+start=1, end=2,
+
+start=100, end= 200, abc
+
+value=100
+
+start=1, end=2
+
+origin=(0 0 0), scale=2, normal=(0 0 1)
+
+
+# Canonical with named args
+function: patchAverage(patch=inlet, p)
+
+# Canonical with unnamed and named args
+function: patchAverage(other, patch=inlet, pval)
+
+
+function: patchAverage(patch=(inlet|outlet), p)
+
+# General
+(a, b)
+
+allow=(inlet|outlet), deny=false, regular(value)
+
+
+# -----------------------------------------------------------------------------
diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files
index 2bfb23509a..beeaf6afb4 100644
--- a/src/OpenFOAM/Make/files
+++ b/src/OpenFOAM/Make/files
@@ -145,6 +145,7 @@ $(strings)/parsing/genericRagelLemonDriver.C
$(strings)/stringOps/stringOps.C
$(strings)/stringOps/stringOpsEvaluate.C
$(strings)/stringOps/stringOpsSort.C
+$(strings)/stringOps/stringOpsSplit.C
expr = expressions
$(expr)/exprEntry/expressionEntry.C
diff --git a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C
index 837402f0fa..1c7f5a9186 100644
--- a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C
+++ b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C
@@ -6,7 +6,7 @@
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation
- Copyright (C) 2015-2020 OpenCFD Ltd.
+ Copyright (C) 2015-2021 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
@@ -243,91 +243,42 @@ bool Foam::functionObjectList::readFunctionObject
// 'patchAverage(patch=inlet, p)' -> funcName = patchAverage;
// args = (patch=inlet, p); field = p
- word funcName(funcNameArgs);
-
- int argLevel = 0;
+ word funcName;
wordRes args;
-
List> namedArgs;
- bool hasNamedArg = false;
- word argName;
- word::size_type start = 0;
- word::size_type i = 0;
-
- for
- (
- word::const_iterator iter = funcNameArgs.begin();
- iter != funcNameArgs.end();
- ++iter
- )
{
- char c = *iter;
-
- if (c == '(')
+ const auto argsBeg = funcNameArgs.find('(');
+ if (argsBeg == std::string::npos)
{
- if (argLevel == 0)
- {
- funcName = funcNameArgs.substr(start, i - start);
- start = i+1;
- }
- ++argLevel;
+ // Function name only, no args
+ funcName = word::validate(funcNameArgs);
}
- else if (c == ',' || c == ')')
+ else
{
- if (argLevel == 1)
- {
- if (hasNamedArg)
- {
- namedArgs.append
- (
- Tuple2
- (
- argName,
- funcNameArgs.substr(start, i - start)
- )
- );
- hasNamedArg = false;
- }
- else
- {
- args.append
- (
- wordRe
- (
- word::validate
- (
- funcNameArgs.substr(start, i - start)
- )
- )
- );
- }
- start = i+1;
- }
+ // Leading function name
+ funcName = word::validate(funcNameArgs.substr(0, argsBeg));
- if (c == ')')
- {
- if (argLevel == 1)
- {
- break;
- }
- --argLevel;
- }
- }
- else if (c == '=')
- {
- argName = word::validate
+ const auto argsEnd = funcNameArgs.rfind(')');
+
+ stringOps::splitFunctionArgs
(
- funcNameArgs.substr(start, i - start)
+ funcNameArgs.substr
+ (
+ (argsBeg + 1),
+ (
+ (argsEnd != std::string::npos && argsBeg < argsEnd)
+ ? (argsEnd - argsBeg - 1)
+ : std::string::npos
+ )
+ ),
+ args,
+ namedArgs
);
-
- start = i+1;
- hasNamedArg = true;
}
-
- ++i;
}
+
// Search for the functionObject dictionary
fileName path = functionObjectList::findDict(funcName);
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
index be512d45d6..63a7a5a60c 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C
@@ -693,7 +693,7 @@ static void expandString
} // End namespace Foam
-// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * //
std::string::size_type Foam::stringOps::count
(
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
index 33424ea873..29d26b2bee 100644
--- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H
@@ -57,6 +57,7 @@ namespace Foam
// Forward Declarations
class OSstream;
+template class Tuple2;
/*---------------------------------------------------------------------------*\
Namespace stringOps Declaration
@@ -282,6 +283,14 @@ namespace stringOps
// Return true if a replacement was successful.
bool inplaceReplaceVar(std::string& s, const word& varName);
+ //- Return a copy of the input string with validated characters
+ template
+ StringType validate
+ (
+ const std::string& str,
+ const UnaryPredicate& accept,
+ const bool invert=false //!< Invert the test logic
+ );
//- Find (first, last) non-space locations in string or sub-string.
// This may change to std::string_view in the future.
@@ -334,6 +343,30 @@ namespace stringOps
//- Inplace transform string with std::toupper on each character
void inplaceUpper(std::string& s);
+ //- Split out arguments (named or unnamed) from an input string.
+ //
+ // For example,
+ // \verbatim
+ // (U)
+ // -> named = ()
+ // -> unnamed = (U)
+ //
+ // (patch=inlet, p)
+ // -> named = ((patch inlet))
+ // -> unnamed = (p)
+ //
+ // testing, start=100, stop=200
+ // -> named = ((start 100)(stop 200))
+ // -> unnamed = (testing)
+ // \endverbatim
+ //
+ // \return total number of arguments
+ label splitFunctionArgs
+ (
+ const std::string& str,
+ wordRes& args,
+ List>& namedArgs
+ );
//- Split string into sub-strings at the delimiter character.
// Empty sub-strings are normally suppressed.
diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C b/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C
new file mode 100644
index 0000000000..05de8a2d6a
--- /dev/null
+++ b/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C
@@ -0,0 +1,221 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration |
+ \\ / A nd | www.openfoam.com
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+ Copyright (C) 2016 OpenFOAM Foundation
+ 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 .
+
+\*---------------------------------------------------------------------------*/
+
+#include "stringOps.H"
+#include "Pair.H"
+#include "Tuple2.H"
+#include
+
+// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
+
+namespace
+{
+
+inline Foam::word validateVariableName(const std::string& str)
+{
+ return Foam::stringOps::validate
+ (
+ str,
+ [](char c)
+ {
+ return (Foam::word::valid(c) || c == '/' || c == '{' || c == '}');
+ }
+ );
+}
+
+} // End anonymous namespace
+
+
+// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * //
+
+Foam::label Foam::stringOps::splitFunctionArgs
+(
+ const std::string& str,
+ wordRes& args,
+ List>& namedArgs
+)
+{
+ args.clear();
+ namedArgs.clear();
+
+ // Similar to code originally in functionObjectList (v2012 and earlier)
+ // except that the function-name handling is now done prior to calling
+
+ // (U)
+ // -> named = ()
+ // -> unnamed = (U)
+ //
+ // (patch=inlet, p)
+ // -> named = ((patch inlet))
+ // -> unnamed = (p)
+ //
+ // start=100, stop=200
+ // -> named = ((start 100) (stop 200) )
+ // -> unnamed = ()
+ //
+ // origin=(0 0 0) , scale=2 , normal=(0 0 1)
+
+
+ // Use begin/end parse positions
+ typedef Pair rangeType;
+
+ // For unnamed: beg/end range of each arg
+ std::vector unnamed;
+
+ // For named: list of beg/end ranges for (name, arg)
+ std::vector named;
+
+ // The beg/end range of the argument name
+ rangeType argName(0, 0);
+
+ // If argName is valid
+ bool isNamed = false;
+
+ // The depth of the argument parsing
+ int argLevel = 0;
+
+ const auto strLen = str.length();
+
+ // Pass 1: parsing begin/end parse positions.
+
+ for (std::string::size_type pos = 0, beg = 0; pos < strLen; ++pos)
+ {
+ const bool penultimate = ((pos + 1) == strLen);
+ const char c = str[pos];
+
+ if (c == ')')
+ {
+ --argLevel;
+ }
+
+ if (c == '=')
+ {
+ // Introducer for named argument
+ argName = rangeType(beg, pos);
+ isNamed = true;
+ beg = pos + 1;
+ }
+ else if (c == '(')
+ {
+ ++argLevel;
+ }
+ else if (penultimate || (c == ',')) // OR: (c == ',' || c == ';')
+ {
+ if (penultimate && (c != ',')) // OR: (c != ',' && c != ';')
+ {
+ ++pos; // Until the end, but do not include comma
+ }
+
+ if (argLevel == 0)
+ {
+ if (isNamed)
+ {
+ named.push_back(argName);
+ named.push_back(rangeType(beg, pos));
+ }
+ else
+ {
+ unnamed.push_back(rangeType(beg, pos));
+ }
+ isNamed = false;
+ beg = pos + 1;
+ }
+ }
+ }
+
+
+ // Stage 2: Convert to concrete string and store
+
+
+ // unnamed
+ {
+ const label nInputArgs = static_cast