- follows the principle of least surprise if the expansion behaviour
for #eval and expressions (eg, exprFixedValue) are the same. This
is possible now that we harness the regular stringOps::expand()
within exprString::expand()
- replace stringOps::toScalar with a more generic stringOps::evaluate
method that handles scalars, vectors etc.
- improve #eval to handle various mathematical operations.
Previously only handled scalars. Now produce vectors, tensors etc
for the entries. These tokens are streamed directly into the entry.
- Now accept '/' when reading variables without requiring
a surrounding '{}'
- fix some degenerate parsing cases when the first character is
already bad.
Eg, $"abc" would have previously parsed as a <$"> variable, even
although a double quote is not a valid variable character.
Now emits a warning and parses as a '$' token and a string token.
- add toScalar evaluation, embedded as "${{EXPR}}".
For example,
"repeat ${{5 * 7}} times or ${{ pow(3, 10) }}"
- use direct string concatenation if primitive entry is only a string
type. This prevents spurious quotes from appearing in the expansion.
radius "(2+4)";
angle "3*15";
#eval "$radius*sin(degToRad($angle))";
We want to have
'(2+4)*sin(degToRad(3*15))'
and not
'"(2+4)"*sin(degToRad("3*15"))'
ENH: code refactoring
- refactored expansion code with low-level service routines now
belonging to file-scope. All expansion routines use a common
multi-parameter backend to handle with/without dictionary etc.
This removes a large amount of code duplication.
- add floor/ceil/round methods
- support evaluation of sub-strings
STYLE: add blockMeshDict1.calc, blockMeshDict1.eval test dictionaries
- useful for testing and simple demonstration of equivalence
- the #eval directive is similar to the #calc directive, but for evaluating
string expressions into scalar values. It uses an internal parser for
the evaluation instead of dynamic code compilation. This can make it
more suitable for 'quick' evaluations.
The evaluation supports the following:
- 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: rand(), rand(seed)
- use keyType::option enum to consolidate searching options.
These enumeration names should be more intuitive to use
and improve code readability.
Eg, lookupEntry(key, keyType::REGEX);
vs lookupEntry(key, false, true);
or
Eg, lookupEntry(key, keyType::LITERAL_RECURSIVE);
vs lookupEntry(key, true, false);
- new findEntry(), findDict(), findScoped() methods with consolidated
search options for shorter naming and access names more closely
aligned with other components. Behave simliarly to the
methods lookupEntryPtr(), subDictPtr(), lookupScopedEntryPtr(),
respectively. Default search parameters consistent with lookupEntry().
Eg, const entry* e = dict.findEntry(key);
vs const entry* e = dict.lookupEntryPtr(key, false, true);
- added '*' and '->' dereference operators to dictionary searchers.
- flags the following type of problems:
* mismatches:
keyword mismatch ( set of { brackets ) in the } entry;
* underflow (too many closing brackets:
keyword too many ( set of ) brackets ) in ) entry;
- a missing semi-colon
dict
{
keyword entry with missing semi-colon
}
will be flagged as 'underflow', since it parses through the '}' but
did not open with it.
Max monitoring depth is 60 levels of nesting, to avoid incurring any
memory allocation.
This class is largely a pre-C++11 holdover. It is now possible to
simply use move construct/assignment directly.
In a few rare cases (eg, polyMesh::resetPrimitives) it has been
replaced by an autoPtr.
- Instead of relying on #inputMode to effect a global change it is now
possible (and recommended) to a temporary change in the inputMode
for the following entry.
#default : provide default value if entry is not already defined
#overwrite : silently remove a previously existing entry
#warn : warn about duplicate entries
#error : error if any duplicate entries occur
#merge : merge sub-dictionaries when possible (the default mode)
This is generally less cumbersome than the switching the global
inputMode. For example to provide a set of fallback values.
#includeIfPresent "user-files"
...
#default value uniform 10;
vs.
#includeIfPresent "user-files"
#inputMode protect
...
value uniform 10;
#inputMode merge // _Assuming_ we actually had this before
These directives can also be used to suppress the normal dictionary
merge semantics:
#overwrite dict { entry val; ... }
- patterns only supported for the final element.
To create an element as a pattern instead of a word, an embedded
string quote (single or double) is used for that element.
Any of the following examples:
"/top/sub/dict/'(p|U).*" 100;
"/top/sub/dict/'(p|U).*'" 100;
"/top/sub/dict/\"(p|U).*" 100;
"/top/sub/dict/\"(p|U).*\"" 100;
are equivalent to the longer form:
top
{
sub
{
dict
{
"(p|U).*" 100;
}
}
}
It is not currently possible to auto-vivify intermediate
dictionaries with patterns.
NOK "/nonexistent.*/value" 100;
OK "/existing.*/value" 100;
- full scoping also works for the #remove directive
#remove "/dict1/subdict2/entry1"
- The logic for switching input-mode was previously completely
encapsulated within the #inputMode directive, but without any
programming equivalent. Furthermore, the encapsulation in inputMode
made the logic less clear in other places.
Exposing the inputMode as an enum with direct access from entry
simplifies things a fair bit.
- eliminate one level of else/if nesting in entryIO.C for clearer logic
- for dictionary function entries, simply use
addNamedToMemberFunctionSelectionTable() and avoid defining a type()
as a static. For most function entries the information is only used
to get a name for the selection table lookup anyhow.
- The existing ':' anchor works for rvalue substitutions
(Eg, ${:subdict.name}), but fails for lvalues, since it is
a punctuation token and parse stops there.
- Introduce dictionary::writeEntries for better code-reuse.
Before
======
os << nl << indent << "name";
dict.write(os);
After
=====
dict.write(os, "name");