Commit 9e6683f7 authored by Mark Olesen's avatar Mark Olesen Committed by Andrew Heather
Browse files

ENH: add conditionals to #eval (string to scalar)

Example,

     ($radius > 10) ? sin(degToRad(45)) : cos(degToRad(30))

- protect division and modulo against zero-divide.

- add scanner/parser debugging switches in the namespace,
  selectable as "stringToScalar". For example,

    debug parser:  foamDictionary -debug-switch stringToScalar=2
    debug scanner: foamDictionary -debug-switch stringToScalar=4
    debug both:    foamDictionary -debug-switch stringToScalar=6
parent a5a222f7
api=1909
patch=191001
api=1910
patch=191111
/*--------------------------------*- C++ -*----------------------------------*\
| ========= | |
| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox |
| \\ / O peration | Version: Any |
| \\ / A nd | Website: www.openfoam.com |
| \\/ M anipulation | |
\*---------------------------------------------------------------------------*/
FoamFile
{
version 2.0;
format ascii;
class dictionary;
object testDictEval1;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
expr1 "hypot(3,4)";
expr2 hypot(6,8);
eval1 #eval
#{
(
${FOAM_API} >= 1812 && !${FOAM_API} > 2100
? $expr1
: $expr2
)
#};
eval2 #eval
#{
(false ? pi() * 2 * $expr1 : pi() * 2 * $expr2)
#};
eval3 #eval
#{
(bool(0.1) ? 10 : 0)
#};
eval4 #eval
#{
2 * mag(true) * pi()
#};
eval4b #eval
#{
2 * mag(!false) * pi()
#};
eval5 #eval
#{
2 * mag(${FOAM_API} >= 1909) * pi()
#};
// Can use round or floor for this:
apiYear #eval{ round($FOAM_API / 100) };
// Should not need round or floor:
apiMonth #eval{ ($FOAM_API % 100) };
// Could flag as an input error or treat as zero
empty #eval "";
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
......@@ -136,7 +136,7 @@ $(strings)/parsing/genericRagelLemonDriver.C
$(strings)/stringOps/stringOps.C
$(strings)/stringOps/stringOpsSort.C
$(strings)/stringOps/toScalar/evalStringToScalarDriver.C
$(strings)/stringOps/toScalar/evalStringToScalarLemonParser.lyy
$(strings)/stringOps/toScalar/evalStringToScalarLemonParser.lyy-m4
$(strings)/stringOps/toScalar/evalStringToScalarScanner.cc
ops = primitives/ops
......
......@@ -66,8 +66,8 @@ Foam::scalar Foam::functionEntries::evalEntry::evaluate
{
#ifdef FULLDEBUG
DetailInfo
<< "Using #eval at line " << is.lineNumber()
<< " in file " << parentDict.name() << nl;
<< "Using #eval - line "
<< is.lineNumber() << " in file " << parentDict.name() << nl;
#endif
// String to evaluate
......@@ -109,12 +109,31 @@ Foam::scalar Foam::functionEntries::evalEntry::evaluate
stringOps::inplaceRemoveComments(s);
stringOps::inplaceExpand(s, parentDict, true, true);
stringOps::inplaceTrim(s);
// A common input error, so catch it now
if (std::string::npos != s.find(';'))
{
FatalIOErrorInFunction(is)
<< "Invalid input for #eval" << nl
<< s << endl
<< exit(FatalIOError);
}
#ifdef FULLDEBUG
DetailInfo
<< "expanded: " << s << endl;
#endif
if (s.empty())
{
InfoErr
<< "Empty #eval (treat as 0) - line "
<< is.lineNumber() << " in file " << parentDict.name() << nl;
return scalar(0);
}
return stringOps::toScalar(s);
}
......
......@@ -7,7 +7,7 @@ prefix=evalStringToScalar
"${WM_PROJECT_DIR:?}/wmake/scripts/makeParser" \
-prefix="$prefix" \
-scanner=Scanner.rl \
-parser=LemonParser.lyy \
-parser=LemonParser.lyy-m4 \
"$@"
#------------------------------------------------------------------------------
......@@ -47,16 +47,22 @@ namespace stringOps
//- expressions. For trivial input, use readScalar instead (faster).
//
// The evaluation supports the following:
// - operations: - + * /
// - operations: - + * / %
// - functions: exp, log, log10, pow, sqrt, cbrt, sqr, mag, magSqr
// - trigonometric: sin, cos, tan, asin, acos, atan, atan2, hypot
// - hyperbolic: sinh, cosh, tanh
// - conversions: degToRad, radToDeg
// - constants: pi()
// - misc: floor, ceil, round, rand, rand(seed)
// - constants: pi(), true, false
// - misc: floor, ceil, round, rand, rand(seed), bool()
// - logic: ! ? : == != <= => < >
//
// \note The rand() function returns a uniform scalar on [0-1] interval
// and uses a constant seed
// \note
// Unlike C/C++, the ternary and logical operations are \b not
// short-circuiting. So additional guards may be required.
//
// \note
// The rand() function returns a uniform scalar on [0-1] interval
// and uses a constant seed.
scalar toScalar
(
const std::string& s,
......
......@@ -30,6 +30,28 @@ License
#include "evalStringToScalarScanner.H"
#include "error.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
namespace Foam
{
namespace parsing
{
int evalStringToScalar::debug
(
::Foam::debug::debugSwitch("stringToScalar", 0)
);
registerDebugSwitchWithName
(
evalStringToScalar,
evalStringToScalar,
"stringToScalar"
);
} // End namespace parsing
} // End namespace Foam
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::parsing::evalStringToScalar::parseDriver::parseDriver()
......@@ -65,12 +87,11 @@ Foam::scalar Foam::stringOps::toScalar
size_t len
)
{
Foam::parsing::evalStringToScalar::parseDriver driver;
parsing::evalStringToScalar::parseDriver driver;
scalar val = driver.execute(s, pos, len);
// val = driver.value();
driver.execute(s, pos, len);
return val;
return driver.value();
}
......
......@@ -50,6 +50,12 @@ namespace parsing
namespace evalStringToScalar
{
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
//- The debug flag. 2 = debug parser, 4 = debug scanner
extern int debug;
/*---------------------------------------------------------------------------*\
Class parseDriver Declaration
\*---------------------------------------------------------------------------*/
......
#define TOK_PLUS 1
#define TOK_MINUS 2
#define TOK_TIMES 3
#define TOK_DIVIDE 4
#define TOK_NEGATE 5
#define TOK_NUMBER 6
#define TOK_LPAREN 7
#define TOK_RPAREN 8
#define TOK_PI 9
#define TOK_DEG_TO_RAD 10
#define TOK_RAD_TO_DEG 11
#define TOK_EXP 12
#define TOK_LOG 13
#define TOK_LOG10 14
#define TOK_POW 15
#define TOK_COMMA 16
#define TOK_SQR 17
#define TOK_SQRT 18
#define TOK_CBRT 19
#define TOK_SIN 20
#define TOK_COS 21
#define TOK_TAN 22
#define TOK_ASIN 23
#define TOK_ACOS 24
#define TOK_ATAN 25
#define TOK_ATAN2 26
#define TOK_HYPOT 27
#define TOK_SINH 28
#define TOK_COSH 29
#define TOK_TANH 30
#define TOK_MIN 31
#define TOK_MAX 32
#define TOK_MAG 33
#define TOK_MAGSQR 34
#define TOK_FLOOR 35
#define TOK_CEIL 36
#define TOK_ROUND 37
#define TOK_RAND 38
#define TOK_QUESTION 1
#define TOK_COLON 2
#define TOK_LOR 3
#define TOK_LAND 4
#define TOK_EQUAL 5
#define TOK_NOT_EQUAL 6
#define TOK_LESS_EQ 7
#define TOK_GREATER_EQ 8
#define TOK_LESS 9
#define TOK_GREATER 10
#define TOK_PLUS 11
#define TOK_MINUS 12
#define TOK_TIMES 13
#define TOK_DIVIDE 14
#define TOK_PERCENT 15
#define TOK_NEGATE 16
#define TOK_NOT 17
#define TOK_NUMBER 18
#define TOK_LPAREN 19
#define TOK_RPAREN 20
#define TOK_MAG 21
#define TOK_PI 22
#define TOK_DEG_TO_RAD 23
#define TOK_RAD_TO_DEG 24
#define TOK_EXP 25
#define TOK_LOG 26
#define TOK_LOG10 27
#define TOK_SQR 28
#define TOK_SQRT 29
#define TOK_CBRT 30
#define TOK_SIN 31
#define TOK_COS 32
#define TOK_TAN 33
#define TOK_ASIN 34
#define TOK_ACOS 35
#define TOK_ATAN 36
#define TOK_SINH 37
#define TOK_COSH 38
#define TOK_TANH 39
#define TOK_MAGSQR 40
#define TOK_FLOOR 41
#define TOK_CEIL 42
#define TOK_ROUND 43
#define TOK_POW 44
#define TOK_COMMA 45
#define TOK_ATAN2 46
#define TOK_HYPOT 47
#define TOK_MIN 48
#define TOK_MAX 49
#define TOK_RAND 50
#define TOK_BOOL_FALSE 51
#define TOK_BOOL_TRUE 52
#define TOK_BOOL 53
%include {
%include
{
/*--------------------------------*- C++ -*----------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
......@@ -31,18 +32,144 @@ Description
Interface code wrapping is near the bottom of the file.
\*---------------------------------------------------------------------------*/
}
/*---------------------------------------------------------------------------*\
dnl Begin m4 definitions
dnl
undefine(`substr')dnl> Avoid collision with C++ string substr() method
dnl
dnl
dnl ---------------------------------------------------------------------------
dnl rule_binary_op(out, in1, in2, tok, op)
dnl Production rule for binary field operations
dnl
dnl Example:
dnl rule_binary_op(sfield, sfield, sfield, PLUS, +)
dnl
dnl sfield (lhs) ::= sfield (a) PLUS sfield (b) .
dnl {
dnl lhs = (a) + (b);
dnl }
dnl
dnl ---------------------------------------------------------------------------
define(`rule_binary_op',
`$1 (lhs) ::= $2 (a) $4 $3 (b) .
{
lhs = (a) $5 (b);
}'
)dnl>
dnl
dnl ---------------------------------------------------------------------------
dnl rule_binary_logical_op(type, valType, compare, tok, op)
dnl Production rule for binary logical field operations
dnl Operates on identical field types, producing an lfield.
dnl
dnl Example:
dnl rule_binary_logical_op(vfield, Foam::vector, greaterOp, GREATER, >)
dnl
dnl lfield (lhs) ::= vfield (a) GREATER vfield (b) .
dnl {
dnl lhs = Foam::greaterOp<Foam::vector>()(a, b);
dnl }
dnl ---------------------------------------------------------------------------
define(`rule_binary_logical_op',
`lfield (lhs) ::= $1 (a) $4 $1 (b) .
{
lhs = Foam::$3<$2>()(a, b);
}'
)dnl>
dnl
dnl ---------------------------------------------------------------------------
dnl rule_ternary_op(inOut)
dnl Production rule for ternary field operations
dnl
dnl Example:
dnl rule_ternary_op(sfield)
dnl
dnl sfield (lhs) ::= lfield(cond) QUESTION sfield (a) COLON sfield (b) .
dnl {
dnl lhs = (cond ? a : b);
dnl }
dnl
define(`rule_ternary_op',
`$1 (lhs) ::= lfield(cond) QUESTION $1 (a) COLON $1 (b) .
{
lhs = (cond ? a : b);
}'
)dnl>
dnl
dnl ---------------------------------------------------------------------------
dnl rule_unary_func(out, in1, tok, func)
dnl Production rule for unary functions:
dnl
dnl Example:
dnl rule_unary_func(sfield, vfield, MAG, Foam::mag)
dnl
dnl sfield (lhs) ::= MAG LPAREN vfield (a) RPAREN .
dnl {
dnl lhs = Foam::mag(a);
dnl }
dnl
define(`rule_unary_func',
`$1 (lhs) ::= $3 LPAREN $2 (a) RPAREN .
{
lhs = $4 (a);
}'
)dnl>
dnl
dnl ---------------------------------------------------------------------------
dnl rule_binary_func(out, in1, in2, tok, func)
dnl Production rule for binary functions:
dnl
dnl Example:
dnl rule_binary_func(sfield, sfield, sfield, POW, Foam::pow)
dnl
dnl sfield (lhs) ::= POW LPAREN sfield (a) COMMA sfield (b) RPAREN .
dnl {
dnl lhs = Foam::pow((a), (b));
dnl }
dnl
define(`rule_binary_func',
`$1 (lhs) ::= $4 LPAREN $2 (a) COMMA $3 (b) RPAREN .
{
lhs = $5((a), (b));
}'
)dnl>
dnl
dnl End m4 definitions
\*---------------------------------------------------------------------------*/
%include
{
#include "evalStringToScalarDriver.H"
#include "evalStringToScalarParser.H"
#include "unitConversion.H"
#include "Random.H"
#include "Switch.H"
#include "error.H"
#pragma GCC diagnostic ignored "-Wold-style-cast"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wsign-compare"
} // End of %include
// Enable ParseTrace
#undef NDEBUG
// Local Functions
//- Test for value close to zero
template<class T>
bool equalZero(const T& val)
{
return (Foam::mag(val) < Foam::ROOTVSMALL);
}
} // %include
// ------------------------------------------------------------------------- //
%namespace {}
......@@ -56,184 +183,147 @@ Description
// Terminals
%token_type {Foam::scalar}
// Non-terminals
%type exp {Foam::scalar}
%left PLUS MINUS.
%left TIMES DIVIDE.
%left NEGATE.
%type lfield {bool}
%type sfield {Foam::scalar}
%start_symbol evaluate
evaluate ::= exp(a).
{
driver->setValue(a);
}
// (https://en.cppreference.com/w/cpp/language/operator_precedence)
exp(lhs) ::= NUMBER(a). { lhs = a; }
exp(lhs) ::= MINUS exp(a). [NEGATE] { lhs = -a; }
// Operations
exp(lhs) ::= exp(a) PLUS exp(b). { lhs = a + b; }
exp(lhs) ::= exp(a) MINUS exp(b). { lhs = a - b; }
exp(lhs) ::= exp(a) TIMES exp(b). { lhs = a * b; }
exp(lhs) ::= exp(a) DIVIDE exp(b). { lhs = a / b; }
exp(lhs) ::= LPAREN exp(a) RPAREN. { lhs = a; }
// Constants
exp(lhs) ::= PI LPAREN RPAREN. { lhs = Foam::constant::mathematical::pi; }
exp(lhs) ::= DEG_TO_RAD LPAREN RPAREN. { lhs = Foam::degToRad(); }
exp(lhs) ::= RAD_TO_DEG LPAREN RPAREN. { lhs = Foam::radToDeg(); }
%right QUESTION COLON . // 16: right-to-left
%left LOR . // 15:
%left LAND . // 14:
// %left BIT_OR . // 13
// %left BIT_XOR . // 12
// %left BIT_AND . // 11
%left EQUAL NOT_EQUAL . // 10
%left LESS_EQ GREATER_EQ LESS GREATER . // 9
%left PLUS MINUS . // 6
%left TIMES DIVIDE PERCENT . // 5
%right NEGATE NOT . // 3: right-to-left
%start_symbol evaluate
// Functions
exp(lhs) ::= DEG_TO_RAD LPAREN exp(a) RPAREN.
{
lhs = Foam::degToRad(a);
}
exp(lhs) ::= RAD_TO_DEG LPAREN exp(a) RPAREN.
{
lhs = Foam::radToDeg(a);
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
exp(lhs) ::= EXP LPAREN exp(a) RPAREN.
{
lhs = Foam::exp(a);
}
/*---------------------------------------------------------------------------*\
* Productions (Foam::scalar)
\*---------------------------------------------------------------------------*/
exp(lhs) ::= LOG LPAREN exp(a) RPAREN.
evaluate ::= sfield(a).
{
lhs = Foam::log(a);
driver->setValue(a);
}
exp(lhs) ::= LOG10 LPAREN exp(a) RPAREN.
{
lhs = Foam::log10(a);
}
exp(lhs) ::= POW LPAREN exp(a) COMMA exp(b) RPAREN.
{
lhs = Foam::pow(a, b);
}
// Basics
exp(lhs) ::= SQR LPAREN exp(a) RPAREN.
{
lhs = Foam::sqr(a);
}
sfield(lhs) ::= NUMBER(a). { lhs = a; } // From scanToken
sfield(lhs) ::= LPAREN sfield(a) RPAREN. { lhs = a; }
sfield(lhs) ::= MINUS sfield(a). [NEGATE] { lhs = -a; }
exp(lhs) ::= SQRT LPAREN exp(a) RPAREN.
{
lhs = Foam::sqrt(a);
}
// Conversion (cast)
sfield(lhs) ::= MAG LPAREN lfield(a) RPAREN . { lhs = Foam::scalar(a); }
exp(lhs) ::= CBRT LPAREN exp(a) RPAREN.
{
lhs = Foam::cbrt(a);
}
// Constants
exp(lhs) ::= SIN LPAREN exp(a) RPAREN.
{
lhs = Foam::sin(a);
}
sfield(lhs) ::= PI LPAREN RPAREN. { lhs = Foam::constant::mathematical::pi; }
sfield(lhs) ::= DEG_TO_RAD LPAREN RPAREN. { lhs = Foam::degToRad(); }
sfield(lhs) ::= RAD_TO_DEG LPAREN RPAREN. { lhs = Foam::radToDeg(); }
exp(lhs) ::= COS LPAREN exp(a) RPAREN.
{
lhs = Foam::cos(a);
}
// Operations