ENH: refactor function arg splitting -> stringOps::splitFunctionArgs

This commit is contained in:
Mark Olesen 2021-05-18 08:21:55 +02:00
parent 44a243a94d
commit c9fda67b5f
10 changed files with 514 additions and 74 deletions

View File

@ -0,0 +1,3 @@
Test-splitFunctionArgs.C
EXE = $(FOAM_USER_APPBIN)/Test-splitFunctionArgs

View File

@ -0,0 +1,2 @@
/* EXE_INC = */
/* EXE_LIBS = */

View File

@ -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 <http://www.gnu.org/licenses/>.
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<Tuple2<word, string>> 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<Tuple2<word, string>> 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<fileName>(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;
}
// ************************************************************************* //

View File

@ -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)
# -----------------------------------------------------------------------------

View File

@ -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

View File

@ -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<Tuple2<word, string>> 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<word, string>
(
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);

View File

@ -693,7 +693,7 @@ static void expandString
} // End namespace Foam
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * //
std::string::size_type Foam::stringOps::count
(

View File

@ -57,6 +57,7 @@ namespace Foam
// Forward Declarations
class OSstream;
template<class T1, class T2> 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<class StringType, class UnaryPredicate>
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<Tuple2<word, string>>& namedArgs
);
//- Split string into sub-strings at the delimiter character.
// Empty sub-strings are normally suppressed.

View File

@ -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 <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "stringOps.H"
#include "Pair.H"
#include "Tuple2.H"
#include <vector>
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
namespace
{
inline Foam::word validateVariableName(const std::string& str)
{
return Foam::stringOps::validate<Foam::word>
(
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<Tuple2<word, string>>& 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<std::string::size_type> rangeType;
// For unnamed: beg/end range of each arg
std::vector<rangeType> unnamed;
// For named: list of beg/end ranges for (name, arg)
std::vector<rangeType> 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<label>(unnamed.size());
args.resize(nInputArgs);
label ngood = 0;
for (label i = 0; i < nInputArgs; ++i)
{
const auto& arg = unnamed[i];
args[ngood] = wordRe
(
word::validate
(
str.substr(arg.first(), (arg.second()-arg.first()))
)
);
// Only retain if non-empty
if (!args[ngood].empty())
{
++ngood;
}
}
args.resize(ngood);
}
// named
{
const label nInputArgs = static_cast<label>(named.size());
namedArgs.resize(nInputArgs/2);
label ngood = 0;
for (label i = 0; i < nInputArgs; i += 2)
{
const auto& name = named[i];
const auto& arg = named[i+1];
namedArgs[ngood].first() =
validateVariableName
(
str.substr(name.first(), (name.second()-name.first()))
);
namedArgs[ngood].second() =
stringOps::trim
(
str.substr(arg.first(), (arg.second()-arg.first()))
);
// Only retain if name is non-empty
if (!namedArgs[ngood].first().empty())
{
++ngood;
}
}
namedArgs.resize(ngood);
}
// Return total number of arguments
return (args.size() + namedArgs.size());
}
// ************************************************************************* //

View File

@ -66,6 +66,33 @@ StringType Foam::stringOps::quotemeta
}
template<class StringType, class UnaryPredicate>
StringType Foam::stringOps::validate
(
const std::string& str,
const UnaryPredicate& accept,
const bool invert
)
{
StringType out;
out.resize(str.length());
std::string::size_type len = 0;
for (std::string::size_type i = 0; i < str.length(); ++i)
{
const char c = str[i];
if (accept(c) ? !invert : invert)
{
out[len++] += c;
}
}
out.erase(len);
return out;
}
template<class StringType>
Foam::SubStrings<StringType> Foam::stringOps::split
(