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 * * * * * * * * * * * * * * //
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,44 +219,12 @@ 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)
{
// 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)
);
}
}
// 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);
@ -351,28 +246,37 @@ Foam::exprTools::expressionEntry::expand
s.std::string::replace(varBeg, replaceLen, varValue);
varBeg += varValue.size();
}
else
{
++varBeg;
}
}
// 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;
}
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.
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
static expressions::exprString expand
(
@ -141,15 +148,6 @@ public:
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

View File

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

View File

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