diff --git a/applications/test/exprEntry/Make/files b/applications/test/exprEntry/Make/files new file mode 100644 index 0000000000000000000000000000000000000000..8249208d65a9f43e2027d419586f2735fbffab0b --- /dev/null +++ b/applications/test/exprEntry/Make/files @@ -0,0 +1,3 @@ +Test-exprEntry.C + +EXE = $(FOAM_USER_APPBIN)/Test-exprEntry diff --git a/applications/test/exprEntry/Make/options b/applications/test/exprEntry/Make/options new file mode 100644 index 0000000000000000000000000000000000000000..41306609f208806f0c6f42a2426867d3e10d4897 --- /dev/null +++ b/applications/test/exprEntry/Make/options @@ -0,0 +1 @@ +EXE_INC = diff --git a/applications/test/exprEntry/Test-exprEntry.C b/applications/test/exprEntry/Test-exprEntry.C new file mode 100644 index 0000000000000000000000000000000000000000..e5b76da7b70396021f687737c958f58113b2a56e --- /dev/null +++ b/applications/test/exprEntry/Test-exprEntry.C @@ -0,0 +1,145 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2019 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-exprEntry + +Description + Read in the given dictionaries and attempt to use exprEntry expansion + on any strings. + +Note + Since this is only for testing purposes, only handles simple dictionary + entries without attempting to descend into sub-dicts. + +\*---------------------------------------------------------------------------*/ + +#include "argList.H" +#include "IOstreams.H" +#include "IOobject.H" +#include "IFstream.H" +#include "dictionary.H" +#include "stringOps.H" +#include "exprString.H" + +using namespace Foam; + +bool hasStrings(const primitiveEntry& e) +{ + for (const token& tok : e.stream()) + { + if (tok.isString()) + { + return true; + } + } + + return false; +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +// Main program: + +int main(int argc, char *argv[]) +{ + argList::noBanner(); + argList::noParallel(); + argList::addArgument("dict .. dictN"); + argList args(argc, argv, false, true); + + if (args.size() <= 1) + { + Info<< "Must supply a dictionary name!" << nl; + } + + for (label argi=1; argi < args.size(); ++argi) + { + IOobject::writeDivider(Info); + + IFstream is(args[argi]); + + const dictionary dict(is); + + Info<< "Input dictionary:" << dict << nl + << "With any expansions" << nl << endl; + + for (const entry& dEntry : dict) + { + const auto* eptr = isA<primitiveEntry>(dEntry); + if (!eptr || !hasStrings(*eptr)) + { + continue; + } + + const primitiveEntry& e = *eptr; + Info<< e << endl; + + for (const token& t : e.stream()) + { + if (t.isString()) + { + string str(t.stringToken()); + + const bool throwingErr = FatalError.throwExceptions(); + const bool throwingIOErr = FatalIOError.throwExceptions(); + + try + { + // Can get an error if we have things like + // ${{ ... $[...] }} + Info<< "str : " << stringOps::expand(str, dict) << nl; + } + catch (const Foam::error& err) + { + Info<< err.message().c_str() << nl; + } + + try + { + // Should not trigger any errors + expressions::exprString expr(str, dict, false); + Info<< "expr: " << expr << nl; + } + catch (const Foam::error& err) + { + Info<< err.message().c_str() << nl; + } + + FatalError.throwExceptions(throwingErr); + FatalIOError.throwExceptions(throwingIOErr); + Info<< nl; + } + } + } + } + + Info<< "\nEnd\n" << endl; + + return 0; +} + + +// ************************************************************************* // diff --git a/applications/test/exprEntry/testDict1 b/applications/test/exprEntry/testDict1 new file mode 100644 index 0000000000000000000000000000000000000000..06993eca2cb97867f2eaa912e21ffec73dd5c7d9 --- /dev/null +++ b/applications/test/exprEntry/testDict1 @@ -0,0 +1,69 @@ +/*--------------------------------*- C++ -*----------------------------------*\ +| ========= | | +| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | +| \\ / O peration | Version: v1912 | +| \\ / A nd | Website: www.openfoam.com | +| \\/ M anipulation | | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object testDict; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +scalar1 10; + +scalar2 20; + +vector1 (1 2 3); + +vector2 (2 3 4); + +aVector 1; +bVector 2; + +string1 "This is a scalar $scalar1, or $[ scalar1 ]"; + +string2 "This is a vector $vector1, or $[vector1]"; + +string3 "This is a vector $vector1, or $[(vector)vector1]"; + +string3b "This is a vector ${vector1}, or $[(vector)vector1]"; + +string4 "This is a vector ${{ 5 * 12 }} or $[(vector)vector1]"; + +string5 "This is a vector ${{ 5 * 12 }} or $[(vector)vector1]"; + +string8 "This is a vector ${{ 5 * 12 * $[(vector)vector1] }}"; + +// These actually work +string10 #{ + Cond is ${{ ${{ sin(degToRad(4*$scalar1)) }} * $[(vector) vector${aVector}] }} +#}; + +// These actually work +string10b #{ + Cond is ${{ ${{ sin(degToRad(4*$scalar1)) }} * $[(vector) vector$bVector] }} +#}; + + +// Fairly simple idea +angle 35; +valueExpr1 "vector(${{cos(degToRad($angle))}}, 2, 3)"; + + +// Slightly stranger ideas: + +axis1 (1 0 0); +axis2 (0 1 0); +axis3 (0 0 1); + +index 100; + +valueExpr2 "$[(vector) axis${{ ($index % 3) +1 }}] / ${{max(1, $index)}}"; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/src/OpenFOAM/expressions/exprEntry/expressionEntry.C b/src/OpenFOAM/expressions/exprEntry/expressionEntry.C index 8d7090242f8eb7c1adb4a6008210d0377940b0f3..a350c3ed18bdc41c74aa99bb9a6bd39e07189697 100644 --- a/src/OpenFOAM/expressions/exprEntry/expressionEntry.C +++ b/src/OpenFOAM/expressions/exprEntry/expressionEntry.C @@ -81,70 +81,8 @@ addNamedToRunTimeSelectionTable // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // -namespace -{ -// Same code as in stringOps.C - -// Acceptable values for $variable names. -// -// Similar to word::valid(), except we don't have the benefit of a parser -// to filter out other unacceptable entries for us. -// -// Does not currently accept '/' in a variable name. -// We would like "$file/$name" to expand as two variables. -static inline bool validVariableChar(char c) -{ - return - ( - std::isalnum(c) - || c == '.' - || c == ':' - || c == '_' - ); -} - - -// For input string of "$variable with other" return the length of -// the variable. -// -// Intentionally will not capture ':+', ':-' alterations. Use ${ .. } for that -static inline std::string::size_type findVariableLen -( - const std::string& s, - std::string::size_type pos, - const char sigil = '$' -) -{ - std::string::size_type len = 0; - - if (pos < s.length()) - { - if (s[pos] == sigil) - { - // Skip leading '$' in the count! - ++pos; - } - - for - ( - auto iter = s.cbegin() + pos; - iter != s.cend() && validVariableChar(*iter); - ++iter - ) - { - ++len; - } - } - - return len; -} - -} // End anonymous namespace - - namespace Foam { - inline static const entry* getVariableOrDie ( const word& name, @@ -170,7 +108,6 @@ inline static const entry* getVariableOrDie return eptr; } - } // End namespace Foam @@ -200,60 +137,51 @@ Foam::exprTools::expressionEntry::New // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // -Foam::expressions::exprString -Foam::exprTools::expressionEntry::expand +void Foam::exprTools::expressionEntry::inplaceExpand ( - const std::string& orig, + std::string& s, const dictionary& dict ) { // This is much like stringOps::inplaceExpand constexpr const char sigil = '$'; - // Copy to a exprString, without any validation (using assign) - expressions::exprString s; - s.assign(orig); + // Step 1: + // Handle $[] special expansions first std::string::size_type varBeg = 0; while ( (varBeg = s.find(sigil, varBeg)) != std::string::npos - // && varBeg < s.size()-1 + && varBeg < s.size()-1 ) { - // No handling of escape characters - - if (varBeg == s.size()-1) + if (varBeg && s[varBeg-1] == '\\') { - // Die if we ended with a '$' - FatalErrorInFunction - << "'" << sigil << "' found at end of " << s - << "(originally " << orig << ')' << nl - << exit(FatalError); + // Escaped character - pass through + ++varBeg; + continue; } - std::string::size_type varEnd = varBeg; - std::string::size_type delim = 0; - - word castTo, varName; - if (s[varBeg+1] == '[') { // An expression pattern with $[...] - varEnd = s.find(']', varBeg); - delim = 1; + std::string::size_type varEnd = s.find(']', varBeg); + std::string::size_type delim = 1; if (varEnd == std::string::npos) { + // Parsed '$[...' without closing ']' - error FatalErrorInFunction - << "No correct terminating ']' found in " << s - << " (originally " << orig << ")" << nl + << "No correct terminating ']' found in " << s << nl << exit(FatalError); + break; } // Look for embedded (type) cast + word castTo, varName; const auto lparen = varBeg+2; if (lparen < s.size() && s[lparen] == '(') @@ -276,8 +204,7 @@ Foam::exprTools::expressionEntry::expand } err << " substring " - << s.substr(varBeg, varEnd-varBeg) - << " (" << orig << ')' << nl + << s.substr(varBeg, varEnd-varBeg) << nl << exit(FatalError); } @@ -292,86 +219,63 @@ Foam::exprTools::expressionEntry::expand ); } + // Likely no spaces there, but for extra safety... stringOps::inplaceTrim(varName); - } - else - { - if (s[varBeg+1] == '{') - { - varEnd = s.find('}', varBeg); - delim = 1; - } - else - { - // Handling regular $var construct - varEnd += findVariableLen(s, varBeg, sigil); - } - if (varEnd == std::string::npos) - { - // Likely parsed '${...' without closing '}' - abort - break; - } - else if (varEnd == varBeg) + // Allow recursive plain expansion for the *variable* name. + // This means "$[(vector) var${index] ]" should work + stringOps::inplaceExpand(varName, dict); + + // Length of original text to replace (incl. decorators) + const auto replaceLen = (varEnd - varBeg + 1); + + const entry* eptr = getVariableOrDie(varName, dict); + + std::string varValue; + + if (castTo.empty()) { - // Parsed '${}' or $badChar - skip over or die? - FatalErrorInFunction - << "No valid character after the $ in " << s - << "(originally " << orig << ")" << endl - << exit(FatalError); + // Serialized with spaces + varValue = eptr->stream().toString(); } else { - // Assign - assumed to be validated with findVariableLen() - varName.assign - ( - s.substr(varBeg + 1 + delim, varEnd - varBeg - 2*delim) - ); + varValue = expressionEntry::New(castTo)->toExpr(*eptr); } - } - - // Length of original text to replace (incl. decorators) - const auto replaceLen = (varEnd - varBeg + 1); - - const entry* eptr = getVariableOrDie(varName, dict); - - std::string varValue; - - if (castTo.empty()) - { - // Serialized with spaces - varValue = eptr->stream().toString(); + s.std::string::replace(varBeg, replaceLen, varValue); + varBeg += varValue.size(); } else { - varValue = expressionEntry::New(castTo)->toExpr(*eptr); + ++varBeg; } - - s.std::string::replace(varBeg, replaceLen, varValue); - varBeg += varValue.size(); } - return s; + + // Step 2: + // Handle all ${}, $var and ${{ ... }} expansions. + // - this is done second such that $[(vector) xyz] entries will have + // been properly expanded by this stage + + stringOps::inplaceExpand(s, dict); } Foam::expressions::exprString -Foam::exprTools::expressionEntry::getExpression +Foam::exprTools::expressionEntry::expand ( - const word& name, - const dictionary& dict, - const bool stripComments + const std::string& orig, + const dictionary& dict ) { - string str(dict.get<string>(name)); + // Copy without validation (use assign) + expressions::exprString s; + s.assign(orig); - if (stripComments) - { - stringOps::inplaceRemoveComments(str); - } + inplaceExpand(s, dict); - return expand(str, dict); + return s; } diff --git a/src/OpenFOAM/expressions/exprEntry/expressionEntry.H b/src/OpenFOAM/expressions/exprEntry/expressionEntry.H index 65a83ca23797dda5a652dc57282b40e809592a0e..b42c8efd9475e7ee877f13ff07bf263423aeb2b8 100644 --- a/src/OpenFOAM/expressions/exprEntry/expressionEntry.H +++ b/src/OpenFOAM/expressions/exprEntry/expressionEntry.H @@ -134,20 +134,18 @@ public: //- Generic concatenate tokens to space-separated string. inline static string evaluate(const entry& e); - //- Expand expression with dictionary entries - static expressions::exprString expand + //- Inplace expand expression with dictionary entries + static void inplaceExpand ( - const std::string& str, + std::string& s, const dictionary& dict ); - //- Get and expand expression with dictionary entries, - //- and strip C/C++ comments from the input - static expressions::exprString getExpression + //- Expand expression with dictionary entries + static expressions::exprString expand ( - const word& name, - const dictionary& dict, - const bool stripComments = false + const std::string& str, + const dictionary& dict ); diff --git a/src/OpenFOAM/expressions/exprString/exprString.C b/src/OpenFOAM/expressions/exprString/exprString.C index 32d0cf6506aea2a32a209daa764d4bfcd36714f0..2d6bd03bc094df7c86cba4e7d3d53ab42d31b599 100644 --- a/src/OpenFOAM/expressions/exprString/exprString.C +++ b/src/OpenFOAM/expressions/exprString/exprString.C @@ -29,31 +29,58 @@ License #include "stringOps.H" #include "expressionEntry.H" -// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // +// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * // -Foam::expressions::exprString& -Foam::expressions::exprString::expand +void Foam::expressions::exprString::inplaceExpand ( + std::string& str, const dictionary& dict, const bool stripComments ) { if (stripComments) { - stringOps::inplaceRemoveComments(*this); + stringOps::inplaceRemoveComments(str); } - // Not quite as efficient as it could be, but wish to have a copy - // of the original input for the sake of reporting errors + exprTools::expressionEntry::inplaceExpand(str, dict); +} - if (std::string::npos != find('$')) - { - (*this) = exprTools::expressionEntry::expand(*this, dict); - #ifdef FULLDEBUG - (void)valid(); - #endif - } +Foam::expressions::exprString +Foam::expressions::exprString::getExpression +( + const word& name, + const dictionary& dict, + const bool stripComments +) +{ + string orig(dict.get<string>(name)); + + // No validation + expressions::exprString expr; + expr.assign(std::move(orig)); + + inplaceExpand(expr, dict, stripComments); + + return expr; +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +Foam::expressions::exprString& +Foam::expressions::exprString::expand +( + const dictionary& dict, + const bool stripComments +) +{ + inplaceExpand(*this, dict, stripComments); + + #ifdef FULLDEBUG + (void)valid(); + #endif return *this; } diff --git a/src/OpenFOAM/expressions/exprString/exprString.H b/src/OpenFOAM/expressions/exprString/exprString.H index 8d8faf857e961aaf8e305665d98fd7b9d64e8d89..612022d0fed47d80507f61e49e071377f6a7dd4a 100644 --- a/src/OpenFOAM/expressions/exprString/exprString.H +++ b/src/OpenFOAM/expressions/exprString/exprString.H @@ -115,7 +115,25 @@ public: ~exprString() = default; - // Member Functions + // Static Member Functions + + //- Inplace expansion with dictionary variables, + //- and strip C/C++ comments from the input + static void inplaceExpand + ( + std::string& str, + const dictionary& dict, + const bool stripComments = true + ); + + //- Get and expand expression with dictionary entries, + //- optionally strip C/C++ comments from the input + static exprString getExpression + ( + const word& name, + const dictionary& dict, + const bool stripComments = false + ); //- Copy convert string to exprString. // No expansions, know what you are doing. @@ -125,6 +143,9 @@ public: // No expansions, know what you are doing. inline static exprString toExpr(std::string&& str); + + // Member Functions + //- Inplace expansion with dictionary variables, //- and strip C/C++ comments from the input exprString& expand