ENH: improve expression string expansions

- reuse more of stringOps expansions to reduce code and improve the
  syntax flexiblity.

  We can now embed "pre-calculated" values into an expression.
  For example,

       angle       35;
       valueExpr   "vector(${{cos(degToRad($angle))}}, 2, 3)";

  and the ${{..}} will be evaluated with the regular string evaluation
  and used to build the entire expression for boundary condition
  evaluation.

  Could also use for fairly wild indirect referencing:

       axis1   (1 0 0);
       axis2   (0 1 0);
       axis3   (0 0 1);
       index   100;
       expr   "$[(vector) axis${{ ($index % 3) +1 }}] / ${{max(1,$index)}}";
This commit is contained in:
Mark Olesen
2019-12-14 00:11:28 +01:00
parent b63721f8bc
commit 33e0c4ba88
8 changed files with 348 additions and 180 deletions

View File

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

View File

@ -0,0 +1 @@
EXE_INC =

View File

@ -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;
}
// ************************************************************************* //

View File

@ -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)}}";
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

View File

@ -81,70 +81,8 @@ addNamedToRunTimeSelectionTable
// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * 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 namespace Foam
{ {
inline static const entry* getVariableOrDie inline static const entry* getVariableOrDie
( (
const word& name, const word& name,
@ -170,7 +108,6 @@ inline static const entry* getVariableOrDie
return eptr; return eptr;
} }
} // End namespace Foam } // End namespace Foam
@ -200,60 +137,51 @@ Foam::exprTools::expressionEntry::New
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
Foam::expressions::exprString void Foam::exprTools::expressionEntry::inplaceExpand
Foam::exprTools::expressionEntry::expand
( (
const std::string& orig, std::string& s,
const dictionary& dict const dictionary& dict
) )
{ {
// This is much like stringOps::inplaceExpand // This is much like stringOps::inplaceExpand
constexpr const char sigil = '$'; constexpr const char sigil = '$';
// Copy to a exprString, without any validation (using assign) // Step 1:
expressions::exprString s; // Handle $[] special expansions first
s.assign(orig);
std::string::size_type varBeg = 0; std::string::size_type varBeg = 0;
while while
( (
(varBeg = s.find(sigil, varBeg)) != std::string::npos (varBeg = s.find(sigil, varBeg)) != std::string::npos
// && varBeg < s.size()-1 && varBeg < s.size()-1
) )
{ {
// No handling of escape characters if (varBeg && s[varBeg-1] == '\\')
if (varBeg == s.size()-1)
{ {
// Die if we ended with a '$' // Escaped character - pass through
FatalErrorInFunction ++varBeg;
<< "'" << sigil << "' found at end of " << s continue;
<< "(originally " << orig << ')' << nl
<< exit(FatalError);
} }
std::string::size_type varEnd = varBeg;
std::string::size_type delim = 0;
word castTo, varName;
if (s[varBeg+1] == '[') if (s[varBeg+1] == '[')
{ {
// An expression pattern with $[...] // An expression pattern with $[...]
varEnd = s.find(']', varBeg); std::string::size_type varEnd = s.find(']', varBeg);
delim = 1; std::string::size_type delim = 1;
if (varEnd == std::string::npos) if (varEnd == std::string::npos)
{ {
// Parsed '$[...' without closing ']' - error
FatalErrorInFunction FatalErrorInFunction
<< "No correct terminating ']' found in " << s << "No correct terminating ']' found in " << s << nl
<< " (originally " << orig << ")" << nl
<< exit(FatalError); << exit(FatalError);
break;
} }
// Look for embedded (type) cast // Look for embedded (type) cast
word castTo, varName;
const auto lparen = varBeg+2; const auto lparen = varBeg+2;
if (lparen < s.size() && s[lparen] == '(') if (lparen < s.size() && s[lparen] == '(')
@ -276,8 +204,7 @@ Foam::exprTools::expressionEntry::expand
} }
err << " substring " err << " substring "
<< s.substr(varBeg, varEnd-varBeg) << s.substr(varBeg, varEnd-varBeg) << nl
<< " (" << orig << ')' << nl
<< exit(FatalError); << exit(FatalError);
} }
@ -292,87 +219,64 @@ Foam::exprTools::expressionEntry::expand
); );
} }
// Likely no spaces there, but for extra safety...
stringOps::inplaceTrim(varName); stringOps::inplaceTrim(varName);
}
else // Allow recursive plain expansion for the *variable* name.
{ // This means "$[(vector) var${index] ]" should work
if (s[varBeg+1] == '{') 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())
{ {
varEnd = s.find('}', varBeg); // Serialized with spaces
delim = 1; varValue = eptr->stream().toString();
} }
else else
{ {
// Handling regular $var construct varValue = expressionEntry::New(castTo)->toExpr(*eptr);
varEnd += findVariableLen(s, varBeg, sigil);
} }
if (varEnd == std::string::npos) s.std::string::replace(varBeg, replaceLen, varValue);
{ varBeg += varValue.size();
// Likely parsed '${...' without closing '}' - abort
break;
}
else if (varEnd == varBeg)
{
// Parsed '${}' or $badChar - skip over or die?
FatalErrorInFunction
<< "No valid character after the $ in " << s
<< "(originally " << orig << ")" << endl
<< exit(FatalError);
}
else
{
// Assign - assumed to be validated with findVariableLen()
varName.assign
(
s.substr(varBeg + 1 + delim, varEnd - varBeg - 2*delim)
);
}
}
// 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();
} }
else else
{ {
varValue = expressionEntry::New(castTo)->toExpr(*eptr); ++varBeg;
} }
s.std::string::replace(varBeg, replaceLen, varValue);
varBeg += varValue.size();
} }
// 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::expand
(
const std::string& orig,
const dictionary& dict
)
{
// Copy without validation (use assign)
expressions::exprString s;
s.assign(orig);
inplaceExpand(s, dict);
return s; return s;
} }
Foam::expressions::exprString
Foam::exprTools::expressionEntry::getExpression
(
const word& name,
const dictionary& dict,
const bool stripComments
)
{
string str(dict.get<string>(name));
if (stripComments)
{
stringOps::inplaceRemoveComments(str);
}
return expand(str, dict);
}
// ************************************************************************* // // ************************************************************************* //

View File

@ -134,6 +134,13 @@ public:
//- Generic concatenate tokens to space-separated string. //- Generic concatenate tokens to space-separated string.
inline static string evaluate(const entry& e); inline static string evaluate(const entry& e);
//- Inplace expand expression with dictionary entries
static void inplaceExpand
(
std::string& s,
const dictionary& dict
);
//- Expand expression with dictionary entries //- Expand expression with dictionary entries
static expressions::exprString expand static expressions::exprString expand
( (
@ -141,15 +148,6 @@ public:
const dictionary& dict const dictionary& dict
); );
//- Get and expand expression with dictionary entries,
//- and strip C/C++ comments from the input
static expressions::exprString getExpression
(
const word& name,
const dictionary& dict,
const bool stripComments = false
);
// Member Functions // Member Functions

View File

@ -29,6 +29,44 @@ License
#include "stringOps.H" #include "stringOps.H"
#include "expressionEntry.H" #include "expressionEntry.H"
// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
void Foam::expressions::exprString::inplaceExpand
(
std::string& str,
const dictionary& dict,
const bool stripComments
)
{
if (stripComments)
{
stringOps::inplaceRemoveComments(str);
}
exprTools::expressionEntry::inplaceExpand(str, dict);
}
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 * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
Foam::expressions::exprString& Foam::expressions::exprString&
@ -38,22 +76,11 @@ Foam::expressions::exprString::expand
const bool stripComments const bool stripComments
) )
{ {
if (stripComments) inplaceExpand(*this, dict, stripComments);
{
stringOps::inplaceRemoveComments(*this);
}
// Not quite as efficient as it could be, but wish to have a copy #ifdef FULLDEBUG
// of the original input for the sake of reporting errors (void)valid();
#endif
if (std::string::npos != find('$'))
{
(*this) = exprTools::expressionEntry::expand(*this, dict);
#ifdef FULLDEBUG
(void)valid();
#endif
}
return *this; return *this;
} }

View File

@ -115,7 +115,25 @@ public:
~exprString() = default; ~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. //- Copy convert string to exprString.
// No expansions, know what you are doing. // No expansions, know what you are doing.
@ -125,6 +143,9 @@ public:
// No expansions, know what you are doing. // No expansions, know what you are doing.
inline static exprString toExpr(std::string&& str); inline static exprString toExpr(std::string&& str);
// Member Functions
//- Inplace expansion with dictionary variables, //- Inplace expansion with dictionary variables,
//- and strip C/C++ comments from the input //- and strip C/C++ comments from the input
exprString& expand exprString& expand