From c9fda67b5f70130ef2528770d1977504bf17546e Mon Sep 17 00:00:00 2001 From: Mark Olesen Date: Tue, 18 May 2021 08:21:55 +0200 Subject: [PATCH] ENH: refactor function arg splitting -> stringOps::splitFunctionArgs --- .../test/splitFunctionArgs/Make/files | 3 + .../test/splitFunctionArgs/Make/options | 2 + .../Test-splitFunctionArgs.C | 155 ++++++++++++ .../test/splitFunctionArgs/testNames1 | 47 ++++ src/OpenFOAM/Make/files | 1 + .../functionObjectList/functionObjectList.C | 97 ++------ .../primitives/strings/stringOps/stringOps.C | 2 +- .../primitives/strings/stringOps/stringOps.H | 33 +++ .../strings/stringOps/stringOpsSplit.C | 221 ++++++++++++++++++ .../strings/stringOps/stringOpsTemplates.C | 27 +++ 10 files changed, 514 insertions(+), 74 deletions(-) create mode 100644 applications/test/splitFunctionArgs/Make/files create mode 100644 applications/test/splitFunctionArgs/Make/options create mode 100644 applications/test/splitFunctionArgs/Test-splitFunctionArgs.C create mode 100644 applications/test/splitFunctionArgs/testNames1 create mode 100644 src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C 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