From efd1ac4b5fc766bc9b1f3b1c6ff6879a6f7354f5 Mon Sep 17 00:00:00 2001 From: Mark Olesen Date: Mon, 17 May 2021 16:50:15 +0200 Subject: [PATCH 1/5] ENH: adjust token in preparation for separate expression tokenization - minor simplification of #if/#endif handling ENH: improve input robustness with negative-prefixed expansions (#2095) - especially in blockMeshDict it is useful to negate an value directly. Eg, ``` xmax 100; xmin -$xmax; ``` However, this fails since the dictionary expansion is a two-step process of tokenization followed by expansion. After the expansion the given input would now be the following: ``` xmax 100; xmin - 100; ``` and retrieving a scalar value for 'xmin' fails. Counteract this by being more generous on tokenized input when attempting to retrieve a label or scalar value. If a '-' is found where a number is expected, use it to negate the subsequent value. The previous solution was to invoke an 'eval': ``` xmax 100; xmin #eval{-$xmax}; ``` which adds additional clutter. --- applications/test/dictionary/testDict | 2 +- applications/test/dictionary/testDict2 | 2 +- applications/test/dictionary/testDictAPI | 2 +- applications/test/dictionary/testDictCalc1 | 2 +- applications/test/dictionary/testDictEval1 | 2 +- applications/test/dictionary/testDictEval2 | 2 +- applications/test/dictionary/testDictEval3 | 2 +- applications/test/dictionary/testDictEval4 | 121 ++++++++++++++++++ applications/test/dictionary/testDictList | 2 +- applications/test/dictionary/testDictRegex | 2 +- .../test/dictionary/testPrimitiveEntry | 2 +- applications/test/dictionary/testSubkeyword | 2 +- applications/test/dictionary3/Make/files | 3 + applications/test/dictionary3/Make/options | 2 + .../test/dictionary3/Test-dictionary3.C | 78 +++++++++++ .../db/IOstreams/Pstreams/UIPstream.C | 13 +- .../db/IOstreams/Pstreams/UOPstream.C | 7 +- src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C | 6 +- src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C | 26 ++-- src/OpenFOAM/db/IOstreams/token/token.H | 59 +++++---- src/OpenFOAM/db/IOstreams/token/tokenI.H | 18 ++- src/OpenFOAM/db/IOstreams/token/tokenIO.C | 11 +- .../functionEntry/functionEntry.C | 6 +- .../functionEntries/ifeqEntry/ifeqEntry.C | 118 +++++++++-------- .../functionEntries/ifeqEntry/ifeqEntry.H | 75 ++++++----- src/OpenFOAM/primitives/Scalar/Scalar.C | 31 ++++- src/OpenFOAM/primitives/ints/int32/int32IO.C | 38 +++++- src/OpenFOAM/primitives/ints/int64/int64IO.C | 39 +++++- 28 files changed, 507 insertions(+), 166 deletions(-) create mode 100644 applications/test/dictionary/testDictEval4 create mode 100644 applications/test/dictionary3/Make/files create mode 100644 applications/test/dictionary3/Make/options create mode 100644 applications/test/dictionary3/Test-dictionary3.C diff --git a/applications/test/dictionary/testDict b/applications/test/dictionary/testDict index 5b27ede5d7..19020de7ed 100644 --- a/applications/test/dictionary/testDict +++ b/applications/test/dictionary/testDict @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDict2 b/applications/test/dictionary/testDict2 index 9d481de49a..52967f024d 100644 --- a/applications/test/dictionary/testDict2 +++ b/applications/test/dictionary/testDict2 @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictAPI b/applications/test/dictionary/testDictAPI index d0ae9a9a8c..4c4d56743c 100644 --- a/applications/test/dictionary/testDictAPI +++ b/applications/test/dictionary/testDictAPI @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictCalc1 b/applications/test/dictionary/testDictCalc1 index 85dac5911e..5396d387c2 100644 --- a/applications/test/dictionary/testDictCalc1 +++ b/applications/test/dictionary/testDictCalc1 @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDictCalc1; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictEval1 b/applications/test/dictionary/testDictEval1 index bd94dcab6b..13491110df 100644 --- a/applications/test/dictionary/testDictEval1 +++ b/applications/test/dictionary/testDictEval1 @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDictEval1; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictEval2 b/applications/test/dictionary/testDictEval2 index d154bf7700..77cefec4c1 100644 --- a/applications/test/dictionary/testDictEval2 +++ b/applications/test/dictionary/testDictEval2 @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDictEval1; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictEval3 b/applications/test/dictionary/testDictEval3 index 79bebc9c20..6c6ffe9b61 100644 --- a/applications/test/dictionary/testDictEval3 +++ b/applications/test/dictionary/testDictEval3 @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDictEval1; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testDictEval4 b/applications/test/dictionary/testDictEval4 new file mode 100644 index 0000000000..81a4563ebc --- /dev/null +++ b/applications/test/dictionary/testDictEval4 @@ -0,0 +1,121 @@ +/*--------------------------------*- C++ -*----------------------------------*\ +| ========= | | +| \\ / F ield | OpenFOAM: The Open Source CFD Toolbox | +| \\ / O peration | Version: v2012 | +| \\ / A nd | Website: www.openfoam.com | +| \\/ M anipulation | | +\*---------------------------------------------------------------------------*/ +FoamFile +{ + version 2.0; + format ascii; + class dictionary; + object dictionary; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +// Test expansion with negative signs + +value 0.5; + +radius 3; + +negValue -$value; + +select1 10; + +sqrt05 #eval{ sqrt(0.5) }; + +vector ( -10 ${{-$sqrt05}} $value ); + +corner ( ${{ -$radius*sqrt(0.5) }} 1 0 ); + +corner2 ${{ + vector(-${radius}*sqrt(0.5), ${radius}*sqrt(0.5), 2) +}}; + + +// Just a future idea (2021-05-14) - does not yet work! +#if 0 +corner3 #eval #{ + variables ( "outer = $radius*sqrt(0.5)" ); + vector(-outer, outer, 2) +#}; +#endif + + +// The brace-bracket #eval with multi-lines failed for v2012 and earlier + +corner2b #eval +{ + vector(-${radius}*sqrt(0.5), $radius*sqrt(0.5), 2) +}; + +corner2c #eval +${{ + vector(-${radius}*sqrt(0.5), $radius*sqrt(0.5), 2) +}}; + + +longSlurp #eval +{ + // This is actually a very simple expression + 1 + 2 +// With a long comment that is stripped out +// during reading anyhow. +}; + + +longVariable +${{ + // This is actually a very simple expression in variable syntax + 1 + 2 +/* +// With a long comment that is stripped out +// during reading anyhow. +*/ +}}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +// Geometric parameters +rxo 2; +ryo 3; +rzo 4; + +// Geometric parameters +outerRadius 1; +innerRatio 0.75; + +geometry +{ + sphere + { + type sphere; + origin (0 0 0); + radius ($rxo $ryo $rzo); + } + + innerSphere + { + $sphere + + // Different solutions to the same problem + radius_v1 + ( + ${{ $innerRatio*$rxo }} + ${{ $innerRatio*$ryo }} + ${{ $innerRatio*$rzo }} + ); + + radius_v2 #eval{ $innerRatio*vector($rxo, $ryo, $rzo) }; + radius_v3 ${{ $innerRatio*$[(vector) ../sphere/radius] }}; + + // Inplace overwrite the same value + radius ${{ $innerRatio*$[(vector) radius] }}; + } +} + + +// ************************************************************************* // diff --git a/applications/test/dictionary/testDictList b/applications/test/dictionary/testDictList index a8c7197356..08cafe93c0 100644 --- a/applications/test/dictionary/testDictList +++ b/applications/test/dictionary/testDictList @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // Test some parsing diff --git a/applications/test/dictionary/testDictRegex b/applications/test/dictionary/testDictRegex index 15aae07200..3970712478 100644 --- a/applications/test/dictionary/testDictRegex +++ b/applications/test/dictionary/testDictRegex @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDictRegex; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // #inputMode merge diff --git a/applications/test/dictionary/testPrimitiveEntry b/applications/test/dictionary/testPrimitiveEntry index f4bffb9cf8..2596012243 100644 --- a/applications/test/dictionary/testPrimitiveEntry +++ b/applications/test/dictionary/testPrimitiveEntry @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary/testSubkeyword b/applications/test/dictionary/testSubkeyword index 21f3579f69..c995e0040f 100644 --- a/applications/test/dictionary/testSubkeyword +++ b/applications/test/dictionary/testSubkeyword @@ -10,7 +10,7 @@ FoamFile version 2.0; format ascii; class dictionary; - object testDict; + object dictionary; note "test with foamDictionary -expand"; } // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // diff --git a/applications/test/dictionary3/Make/files b/applications/test/dictionary3/Make/files new file mode 100644 index 0000000000..bdcfe73b04 --- /dev/null +++ b/applications/test/dictionary3/Make/files @@ -0,0 +1,3 @@ +Test-dictionary3.C + +EXE = $(FOAM_USER_APPBIN)/Test-dictionary3 diff --git a/applications/test/dictionary3/Make/options b/applications/test/dictionary3/Make/options new file mode 100644 index 0000000000..18e6fe47af --- /dev/null +++ b/applications/test/dictionary3/Make/options @@ -0,0 +1,2 @@ +/* EXE_INC = */ +/* EXE_LIBS = */ diff --git a/applications/test/dictionary3/Test-dictionary3.C b/applications/test/dictionary3/Test-dictionary3.C new file mode 100644 index 0000000000..11c1835b28 --- /dev/null +++ b/applications/test/dictionary3/Test-dictionary3.C @@ -0,0 +1,78 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2021 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 . + +Application + Test-dictionary3 + +Description + Test expressions and re-expansions + +\*---------------------------------------------------------------------------*/ + +#include "argList.H" +#include "IOstreams.H" +#include "dictionary.H" +#include "vector.H" +#include "StringStream.H" + +using namespace Foam; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +// Main program: + +int main(int argc, char *argv[]) +{ + argList::noParallel(); + + { + IStringStream is + ( + "value 10;" + "scalar1 $value;" + "scalar2 -$value;" + + // Use #eval expansion entirely + "vector1 ${{vector($value, -$value, $value)}};" + "vector2 ($value -$value $value);" + ); + + dictionary dict(is); + + Info<< "input dictionary:" << dict << nl; + + Info<< "value: " << dict.get("value") << nl; + + Info<< "scalar1: " << dict.get("scalar1") << nl; + Info<< "scalar2: " << dict.get("scalar2") << nl; + + Info<< "vector1: " << dict.get("vector1") << nl; + Info<< "vector2: " << dict.get("vector2") << nl; + } + + return 0; +} + + +// ************************************************************************* // diff --git a/src/OpenFOAM/db/IOstreams/Pstreams/UIPstream.C b/src/OpenFOAM/db/IOstreams/Pstreams/UIPstream.C index eed1e261cb..f999a3f093 100644 --- a/src/OpenFOAM/db/IOstreams/Pstreams/UIPstream.C +++ b/src/OpenFOAM/db/IOstreams/Pstreams/UIPstream.C @@ -218,8 +218,8 @@ Foam::Istream& Foam::UIPstream::read(token& t) case token::COLON : case token::COMMA : case token::ASSIGN : - case token::ADD : - case token::SUBTRACT : + case token::PLUS : + case token::MINUS : case token::MULTIPLY : case token::DIVIDE : { @@ -227,12 +227,12 @@ Foam::Istream& Foam::UIPstream::read(token& t) return *this; } - // Word/directive + // The word-variants case token::tokenType::WORD : case token::tokenType::DIRECTIVE : { word val; - if (read(val)) + if (readStringFromBuffer(val)) { if (token::compound::isCompound(val)) { @@ -251,13 +251,14 @@ Foam::Istream& Foam::UIPstream::read(token& t) return *this; } - // String types + // The string-variants case token::tokenType::STRING : + case token::tokenType::EXPRESSION : case token::tokenType::VARIABLE : case token::tokenType::VERBATIM : { string val; - if (read(val)) + if (readStringFromBuffer(val)) { t = std::move(val); t.setType(token::tokenType(c)); diff --git a/src/OpenFOAM/db/IOstreams/Pstreams/UOPstream.C b/src/OpenFOAM/db/IOstreams/Pstreams/UOPstream.C index f550056ca0..5480f63a73 100644 --- a/src/OpenFOAM/db/IOstreams/Pstreams/UOPstream.C +++ b/src/OpenFOAM/db/IOstreams/Pstreams/UOPstream.C @@ -202,14 +202,19 @@ bool Foam::UOPstream::write(const token& tok) return true; } + // The word-variants + case token::tokenType::WORD : case token::tokenType::DIRECTIVE : { - writeToBuffer(char(token::tokenType::DIRECTIVE)); + writeToBuffer(char(tok.type())); writeStringToBuffer(tok.wordToken()); return true; } + // The string-variants + case token::tokenType::STRING : + case token::tokenType::EXPRESSION : case token::tokenType::VARIABLE : case token::tokenType::VERBATIM : { diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C index d1c4adc93d..10969f20f2 100644 --- a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C +++ b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C @@ -199,8 +199,8 @@ Foam::Istream& Foam::ISstream::read(token& t) case token::COLON : case token::COMMA : case token::ASSIGN : - case token::ADD : - // NB: token::SUBTRACT handled later as the possible start of a Number + case token::PLUS : + // NB: token::MINUS handled later as the possible start of a Number case token::MULTIPLY : case token::DIVIDE : { @@ -368,7 +368,7 @@ Foam::Istream& Foam::ISstream::read(token& t) if (nChar == 1 && buf[0] == '-') { // A single '-' is punctuation - t = token::punctuationToken(token::SUBTRACT); + t = token::punctuationToken(token::MINUS); } else if (labelVal && Foam::read(buf, labelVal)) { diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C b/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C index bf7e25c88a..eea67551a8 100644 --- a/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C +++ b/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2016 OpenFOAM Foundation - Copyright (C) 2017-2020 OpenCFD Ltd. + Copyright (C) 2017-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -47,27 +47,33 @@ bool Foam::OSstream::write(const token& tok) case token::tokenType::DIRECTIVE : { - // The '#' sigil is already part of the wordToken + // Token stored with leading '#' sigil - output directly write(tok.wordToken()); - return true; } - case token::tokenType::VERBATIM : + case token::tokenType::EXPRESSION : { - // Surrounding '#{ .. #}' to be recognized as verbatim - write(char(token::HASH)); - write(char(token::BEGIN_BLOCK)); + // Token stored with surrounding '${{ .. }}' - output directly writeQuoted(tok.stringToken(), false); - write(char(token::HASH)); - write(char(token::END_BLOCK)); - return true; } case token::tokenType::VARIABLE : { + // Token stored with leading '$' sigil - output directly writeQuoted(tok.stringToken(), false); + return true; + } + + case token::tokenType::VERBATIM : + { + // Token stored without surrounding '#{ .. #}'. Add on output + write(char(token::HASH)); + write(char(token::BEGIN_BLOCK)); + writeQuoted(tok.stringToken(), false); + write(char(token::HASH)); + write(char(token::END_BLOCK)); return true; } diff --git a/src/OpenFOAM/db/IOstreams/token/token.H b/src/OpenFOAM/db/IOstreams/token/token.H index eeb0d342ef..e1ccac5e9f 100644 --- a/src/OpenFOAM/db/IOstreams/token/token.H +++ b/src/OpenFOAM/db/IOstreams/token/token.H @@ -71,11 +71,13 @@ class token public: //- Enumeration defining the types of token. - // Since these values are also used to tag content in Pstream, - // the maximum number of types is limited to 30. - enum tokenType + // Since the enumeration is used to tag content in Pstream, it is of + // type \c char and shall have values that do not overlap with regular + // punctuation characters. + enum tokenType : char { - UNDEFINED = 0, //!< An undefined token-type + UNDEFINED = '\0', //!< An undefined token-type + ERROR = '\x80', //!< Token error encountered // Fundamental types FLAG, //!< stream flag (1-byte bitmask) @@ -86,14 +88,18 @@ public: DOUBLE, //!< double (double-precision) type // Pointer types - WORD, //!< A Foam::word - STRING, //!< A string (usually double-quoted) - DIRECTIVE, //!< A dictionary \c \#directive (word variant) - VARIABLE, //!< A dictionary \c \$variable (string variant) - VERBATIM, //!< Verbatim string content + WORD, //!< Foam::word + STRING, //!< Foam::string (usually double-quoted) COMPOUND, //!< Compound type such as \c List\ etc. - ERROR, //!< A token error encountered + DIRECTIVE, //!< Word-variant: dictionary \c \#directive + //!< stored with sigil + EXPRESSION, //!< String-variant: math expression for evaluation + //!< stored with delimiters + VARIABLE, //!< String-variant: dictionary \c \$variable + //!< stored with sigil + VERBATIM, //!< String-variant: verbatim string content + //!< stored without delimiters // Aliases FLOAT_SCALAR = FLOAT, @@ -122,16 +128,16 @@ public: COLON = ':', //!< Colon [#isseparator] SEMICOLON = ';', //!< Semicolon [#isseparator] COMMA = ',', //!< Comma [#isseparator] - HASH = '#', //!< Hash - directive or verbatim string - DOLLAR = '$', //!< Dollar - start variable + HASH = '#', //!< Hash - directive or start verbatim string + DOLLAR = '$', //!< Dollar - start variable or expression QUESTION = '?', //!< Question mark (eg, ternary) ATSYM = '@', //!< The 'at' symbol SQUOTE = '\'', //!< Single quote DQUOTE = '"', //!< Double quote ASSIGN = '=', //!< Assignment/equals [#isseparator] - ADD = '+', //!< Addition [#isseparator] - SUBTRACT = '-', //!< Subtract or start of negative number + PLUS = '+', //!< Addition [#isseparator] + MINUS = '-', //!< Subtract or start of negative number MULTIPLY = '*', //!< Multiply [#isseparator] DIVIDE = '/', //!< Divide [#isseparator] @@ -144,6 +150,8 @@ public: // With semantically meaning + ADD = PLUS, //!< Addition [#isseparator] + SUBTRACT = MINUS, //!< Subtract or start of negative number END_STATEMENT = SEMICOLON, //!< End entry [#isseparator] BEGIN_LIST = LPAREN, //!< Begin list [#isseparator] END_LIST = RPAREN, //!< End list [#isseparator] @@ -471,10 +479,10 @@ public: //- Token is LABEL, FLOAT or DOUBLE inline bool isNumber() const noexcept; - //- Token is WORD or DIRECTIVE word + //- Token is word-variant (WORD, DIRECTIVE) inline bool isWord() const noexcept; - //- Token is WORD or DIRECTIVE word and equal to parameter + //- Token is word-variant and equal to parameter inline bool isWord(const std::string& s) const; //- Token is DIRECTIVE (word variant) @@ -483,16 +491,20 @@ public: //- Token is (quoted) STRING (string variant) inline bool isQuotedString() const noexcept; - //- Token is STRING, VARIABLE or VERBATIM (string variant) + //- Token is string-variant (STRING, EXPRESSION, VARIABLE, VERBATIM) inline bool isString() const noexcept; + //- Token is EXPRESSION (string variant) + inline bool isExpression() const noexcept; + //- Token is VARIABLE (string variant) inline bool isVariable() const noexcept; //- Token is VERBATIM string (string variant) inline bool isVerbatim() const noexcept; - //- Token is WORD, DIRECTIVE, STRING, VARIABLE or VERBATIM + //- Token is word-variant or string-variant + //- (WORD, DIRECTIVE, STRING, EXPRESSION, VARIABLE, VERBATIM) inline bool isStringType() const noexcept; //- Token is COMPOUND @@ -542,7 +554,8 @@ public: //- Return const reference to the string contents. // Report FatalIOError and return \b "" if token is not a - // STRING, VARIABLE, VERBATIM or an upcast WORD or DIRECTIVE + // STRING, EXPRESSION, VARIABLE, VERBATIM + // or an upcast WORD or DIRECTIVE inline const string& stringToken() const; //- Read access for compound token @@ -599,16 +612,16 @@ public: //- Copy assign from double inline void operator=(const doubleScalar val); - //- Copy assign from word + //- Copy assign from word content inline void operator=(const word& w); - //- Copy assign from string + //- Copy assign from string content inline void operator=(const string& str); - //- Move assign from word + //- Move assign from word content inline void operator=(word&& w); - //- Move assign from string + //- Move assign from string content inline void operator=(string&& str); //- Assign compound with reference counting to token diff --git a/src/OpenFOAM/db/IOstreams/token/tokenI.H b/src/OpenFOAM/db/IOstreams/token/tokenI.H index c55ea6ff8a..aa401e3496 100644 --- a/src/OpenFOAM/db/IOstreams/token/tokenI.H +++ b/src/OpenFOAM/db/IOstreams/token/tokenI.H @@ -66,8 +66,8 @@ inline bool Foam::token::isseparator(int c) noexcept case token::COLON : case token::COMMA : case token::ASSIGN : - case token::ADD : - // Excluded token::SUBTRACT since it could start a number + case token::PLUS : + // Excluded token::MINUS since it could start a number case token::MULTIPLY : case token::DIVIDE : { @@ -121,6 +121,7 @@ inline Foam::token::token(const token& tok) } case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: { @@ -265,6 +266,7 @@ inline void Foam::token::reset() } case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: { @@ -357,6 +359,7 @@ inline bool Foam::token::setType(token::tokenType tokType) noexcept break; case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: { @@ -364,6 +367,7 @@ inline bool Foam::token::setType(token::tokenType tokType) noexcept { // could also go from WORD to STRING etc - to be decided case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: type_ = tokType; @@ -651,12 +655,19 @@ inline bool Foam::token::isString() const noexcept return ( type_ == tokenType::STRING + || type_ == tokenType::EXPRESSION || type_ == tokenType::VARIABLE || type_ == tokenType::VERBATIM ); } +inline bool Foam::token::isExpression() const noexcept +{ + return (type_ == tokenType::EXPRESSION); +} + + inline bool Foam::token::isVariable() const noexcept { return (type_ == tokenType::VARIABLE); @@ -680,6 +691,7 @@ inline const Foam::string& Foam::token::stringToken() const if ( type_ == tokenType::STRING + || type_ == tokenType::EXPRESSION || type_ == tokenType::VARIABLE || type_ == tokenType::VERBATIM ) @@ -754,6 +766,7 @@ inline void Foam::token::operator=(const token& tok) break; case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: { @@ -903,6 +916,7 @@ inline bool Foam::token::operator==(const token& tok) const return *data_.wordPtr == *tok.data_.wordPtr; case tokenType::STRING: + case tokenType::EXPRESSION: case tokenType::VARIABLE: case tokenType::VERBATIM: return *data_.stringPtr == *tok.data_.stringPtr; diff --git a/src/OpenFOAM/db/IOstreams/token/tokenIO.C b/src/OpenFOAM/db/IOstreams/token/tokenIO.C index da1a39a3ca..a37a37cec3 100644 --- a/src/OpenFOAM/db/IOstreams/token/tokenIO.C +++ b/src/OpenFOAM/db/IOstreams/token/tokenIO.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2015 OpenFOAM Foundation - Copyright (C) 2017-2020 OpenCFD Ltd. + Copyright (C) 2017-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -82,6 +82,10 @@ static OS& printTokenInfo(OS& os, const token& tok) os << "string " << tok.stringToken(); break; + case token::tokenType::EXPRESSION: + os << "expression " << tok.stringToken(); + break; + case token::tokenType::VARIABLE: os << "variable " << tok.stringToken(); break; @@ -141,6 +145,7 @@ Foam::word Foam::token::name() const case token::tokenType::WORD: return "word"; case token::tokenType::DIRECTIVE: return "directive"; case token::tokenType::STRING: return "string"; + case token::tokenType::EXPRESSION: return "expression"; case token::tokenType::VERBATIM: return "verbatim"; case token::tokenType::VARIABLE: return "variable"; case token::tokenType::COMPOUND: return "compound"; @@ -194,8 +199,10 @@ Foam::Ostream& Foam::operator<<(Ostream& os, const token& tok) os << tok.data_.doubleVal; break; - // Different behaviour for (serial/parallel) streams: preserve types + // Possibly different behaviour for serial/parallel streams: + // preserve types case token::tokenType::DIRECTIVE: + case token::tokenType::EXPRESSION: case token::tokenType::VARIABLE: case token::tokenType::VERBATIMSTRING: os.write(tok); diff --git a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C index 6caf662316..837221ad30 100644 --- a/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C +++ b/src/OpenFOAM/db/dictionary/functionEntries/functionEntry/functionEntry.C @@ -90,7 +90,8 @@ bool Foam::functionEntry::execute if (!executedictionaryIstreamMemberFunctionTablePtr_) { - cerr<< FUNCTION_NAME << nl + std::cerr + << FUNCTION_NAME << nl << "Not yet initialized, function = " << functionName.c_str() << std::endl; @@ -128,7 +129,8 @@ bool Foam::functionEntry::execute if (!executeprimitiveEntryIstreamMemberFunctionTablePtr_) { - cerr<< FUNCTION_NAME << nl + std::cerr + << FUNCTION_NAME << nl << "Not yet initialized, function = " << functionName.c_str() << std::endl; diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C index 0115cf0427..ab4979f5a9 100644 --- a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C +++ b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.C @@ -58,12 +58,7 @@ void Foam::functionEntries::ifeqEntry::readToken(token& t, Istream& is) // Skip dummy tokens - avoids entry::getKeyword consuming #else, #endif do { - if - ( - is.read(t).bad() - || is.eof() - || !t.good() - ) + if (is.read(t).bad() || is.eof() || !t.good()) { return; } @@ -72,7 +67,7 @@ void Foam::functionEntries::ifeqEntry::readToken(token& t, Istream& is) } -Foam::token Foam::functionEntries::ifeqEntry::expand +Foam::token Foam::functionEntries::ifeqEntry::expandToken ( const dictionary& dict, const string& keyword, @@ -109,7 +104,7 @@ Foam::token Foam::functionEntries::ifeqEntry::expand } -Foam::token Foam::functionEntries::ifeqEntry::expand +Foam::token Foam::functionEntries::ifeqEntry::expandToken ( const dictionary& dict, const token& t @@ -117,15 +112,15 @@ Foam::token Foam::functionEntries::ifeqEntry::expand { if (t.isWord()) { - return expand(dict, t.wordToken(), t); + return expandToken(dict, t.wordToken(), t); } else if (t.isVariable()) { - return expand(dict, t.stringToken(), t); + return expandToken(dict, t.stringToken(), t); } else if (t.isString()) { - return expand(dict, t.stringToken(), t); + return expandToken(dict, t.stringToken(), t); } return t; @@ -230,6 +225,9 @@ bool Foam::functionEntries::ifeqEntry::equalToken } return false; + case token::EXPRESSION: + return false; + case token::COMPOUND: return false; @@ -245,7 +243,7 @@ void Foam::functionEntries::ifeqEntry::skipUntil ( DynamicList& stack, const dictionary& parentDict, - const word& endWord, + const word& endDirective, Istream& is ) { @@ -258,28 +256,26 @@ void Foam::functionEntries::ifeqEntry::skipUntil { continue; } - else if - ( - t.wordToken() == "#if" - || t.wordToken() == "#ifeq" - ) + else if (t.wordToken() == "#if" || t.wordToken() == "#ifeq") { stack.append(filePos(is.name(), is.lineNumber())); skipUntil(stack, parentDict, "#endif", is); stack.remove(); } - else if (t.wordToken() == endWord) + else if (t.wordToken() == endDirective) { return; } } FatalIOErrorInFunction(parentDict) - << "Did not find matching " << endWord << nl + << "Did not find matching " << endDirective << nl << exit(FatalIOError); } +// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * // + bool Foam::functionEntries::ifeqEntry::evaluate ( const bool doIf, @@ -292,35 +288,47 @@ bool Foam::functionEntries::ifeqEntry::evaluate { token t; readToken(t, is); + bool pending = false; - if (t.isWord() && t.wordToken() == "#ifeq") + if (t.isDirective()) { - // Recurse to evaluate - execute(stack, parentDict, is); - } - else if (t.isWord() && t.wordToken() == "#if") - { - // Recurse to evaluate - ifEntry::execute(stack, parentDict, is); - } - else if - ( - doIf - && t.isWord() - && (t.wordToken() == "#else" || t.wordToken() == "#elif") - ) - { - // Now skip until #endif - skipUntil(stack, parentDict, "#endif", is); - stack.remove(); - break; - } - else if (t.isWord() && t.wordToken() == "#endif") - { - stack.remove(); - break; + if (t.wordToken() == "#ifeq") + { + // Recurse to evaluate + execute(stack, parentDict, is); + } + else if (t.wordToken() == "#if") + { + // Recurse to evaluate + ifEntry::execute(stack, parentDict, is); + } + else if + ( + doIf + && (t.wordToken() == "#else" || t.wordToken() == "#elif") + ) + { + // Now skip until #endif + skipUntil(stack, parentDict, "#endif", is); + stack.remove(); + break; + } + else if (t.wordToken() == "#endif") + { + stack.remove(); + break; + } + else + { + pending = true; + } } else + { + pending = true; + } + + if (pending) { is.putBack(t); bool ok = entry::New(parentDict, is); @@ -354,21 +362,23 @@ bool Foam::functionEntries::ifeqEntry::execute { readToken(t, is); - if - ( - t.isWord() - && (t.wordToken() == "#if" || t.wordToken() == "#ifeq") - ) + // Only consider directives + if (!t.isDirective()) + { + continue; + } + + if (t.wordToken() == "#if" || t.wordToken() == "#ifeq") { stack.append(filePos(is.name(), is.lineNumber())); skipUntil(stack, parentDict, "#endif", is); stack.remove(); } - else if (t.isWord() && t.wordToken() == "#else") + else if (t.wordToken() == "#else") { break; } - else if (t.isWord() && t.wordToken() == "#elif") + else if (t.wordToken() == "#elif") { // const label lineNo = is.lineNumber(); @@ -386,7 +396,7 @@ bool Foam::functionEntries::ifeqEntry::execute break; } } - else if (t.isWord() && t.wordToken() == "#endif") + else if (t.wordToken() == "#endif") { stack.remove(); break; @@ -421,11 +431,11 @@ bool Foam::functionEntries::ifeqEntry::execute // Read first token and expand any string token cond1(is); - cond1 = expand(parentDict, cond1); + cond1 = expandToken(parentDict, cond1); // Read second token and expand any string token cond2(is); - cond2 = expand(parentDict, cond2); + cond2 = expandToken(parentDict, cond2); const bool equal = equalToken(cond1, cond2); diff --git a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H index a04b91fc81..a925964fcd 100644 --- a/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H +++ b/src/OpenFOAM/db/dictionary/functionEntries/ifeqEntry/ifeqEntry.H @@ -6,6 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2018 OpenFOAM Foundation + Copyright (C) 2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -31,7 +32,7 @@ Description E.g. \verbatim - a #calc "0.123"; + a #eval "0.123"; b 1.23e-1; #ifeq $a $b @@ -90,46 +91,15 @@ class ifeqEntry : public functionEntry { - protected: + // Data Types + typedef Tuple2 filePos; + // Protected Member Functions - //- Read tokens. Skip dummy tokens - static void readToken(token& t, Istream& is); - - //- Expand a variable (string/word/var starting with '$') - static token expand - ( - const dictionary& dict, - const string& keyword, - const token& t - ); - - //- Expand a string/word/var token - static token expand - ( - const dictionary& dict, - const token& t - ); - - static bool equalToken - ( - const token& t1, - const token& t2 - ); - - //- Consume tokens until reached a specific word - static void skipUntil - ( - DynamicList& stack, - const dictionary& parentDict, - const word& endWord, - Istream& is - ); - static bool evaluate ( const bool doIf, @@ -158,6 +128,41 @@ protected: ); +private: + + // Private Member Functions + + //- Read tokens. Skip dummy tokens + static void readToken(token& t, Istream& is); + + //- Expand a variable (string/word/var starting with '$') + static token expandToken + ( + const dictionary& dict, + const string& keyword, + const token& t + ); + + //- Expand a string/word/var token + static token expandToken + ( + const dictionary& dict, + const token& t + ); + + //- Test for equality + static bool equalToken(const token& t1, const token& t2); + + //- Consume tokens until reached a specific directive + static void skipUntil + ( + DynamicList& stack, + const dictionary& parentDict, + const word& endDirective, //!< end directive (without '#') + Istream& is + ); + + public: //- Runtime type information diff --git a/src/OpenFOAM/primitives/Scalar/Scalar.C b/src/OpenFOAM/primitives/Scalar/Scalar.C index c276674d1c..cc096ebeb4 100644 --- a/src/OpenFOAM/primitives/Scalar/Scalar.C +++ b/src/OpenFOAM/primitives/Scalar/Scalar.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2016 OpenFOAM Foundation - Copyright (C) 2016-2019 OpenCFD Ltd. + Copyright (C) 2016-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -145,16 +145,37 @@ Istream& operator>>(Istream& is, Scalar& val) return is; } + // Accept separated '-' (or '+') while expecting a number. + // This can arise during dictionary expansions (Eg, -$value) + + char prefix = 0; + if (t.isPunctuation()) + { + prefix = t.pToken(); + if (prefix == token::PLUS || prefix == token::MINUS) + { + is >> t; + } + } + if (t.isNumber()) { - val = t.number(); + val = + ( + (prefix == token::MINUS) + ? (0 - t.number()) + : t.number() + ); } else { FatalIOErrorInFunction(is) - << "Wrong token type - expected scalar value, found " - << t.info() - << exit(FatalIOError); + << "Wrong token type - expected scalar value, found "; + if (prefix == token::PLUS || prefix == token::MINUS) + { + FatalIOError << '\'' << prefix << "' followed by "; + } + FatalIOError << t.info() << exit(FatalIOError); is.setBad(); return is; } diff --git a/src/OpenFOAM/primitives/ints/int32/int32IO.C b/src/OpenFOAM/primitives/ints/int32/int32IO.C index 6ff287a12f..5099d4baa1 100644 --- a/src/OpenFOAM/primitives/ints/int32/int32IO.C +++ b/src/OpenFOAM/primitives/ints/int32/int32IO.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2014-2016 OpenFOAM Foundation - Copyright (C) 2016-2020 OpenCFD Ltd. + Copyright (C) 2016-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -100,13 +100,36 @@ Foam::Istream& Foam::operator>>(Istream& is, int32_t& val) return is; } + // Accept separated '-' (or '+') while expecting a number. + // This can arise during dictionary expansions (Eg, -$value) + + char prefix = 0; + if (t.isPunctuation()) + { + prefix = t.pToken(); + if (prefix == token::PLUS || prefix == token::MINUS) + { + is >> t; + } + } + if (t.isLabel()) { - val = int32_t(t.labelToken()); + val = int32_t + ( + (prefix == token::MINUS) + ? (0 - t.labelToken()) + : t.labelToken() + ); } else if (t.isScalar()) { - const scalar sval(t.scalarToken()); + const scalar sval + ( + (prefix == token::MINUS) + ? (0 - t.scalarToken()) + : t.scalarToken() + ); const intmax_t parsed = intmax_t(std::round(sval)); val = 0 + int32_t(parsed); @@ -135,9 +158,12 @@ Foam::Istream& Foam::operator>>(Istream& is, int32_t& val) else { FatalIOErrorInFunction(is) - << "Wrong token type - expected label (int32), found " - << t.info() - << exit(FatalIOError); + << "Wrong token type - expected label (int32), found "; + if (prefix == token::PLUS || prefix == token::MINUS) + { + FatalIOError << '\'' << prefix << "' followed by "; + } + FatalIOError << t.info() << exit(FatalIOError); is.setBad(); return is; } diff --git a/src/OpenFOAM/primitives/ints/int64/int64IO.C b/src/OpenFOAM/primitives/ints/int64/int64IO.C index 8ffc448b7e..93f73585ce 100644 --- a/src/OpenFOAM/primitives/ints/int64/int64IO.C +++ b/src/OpenFOAM/primitives/ints/int64/int64IO.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2014-2016 OpenFOAM Foundation - Copyright (C) 2017-2020 OpenCFD Ltd. + Copyright (C) 2017-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -100,13 +100,37 @@ Foam::Istream& Foam::operator>>(Istream& is, int64_t& val) return is; } + // Accept separated '-' (or '+') while expecting a number. + // This can arise during dictionary expansions (Eg, -$value) + + char prefix = 0; + if (t.isPunctuation()) + { + prefix = t.pToken(); + if (prefix == token::PLUS || prefix == token::MINUS) + { + is >> t; + } + } + if (t.isLabel()) { - val = int64_t(t.labelToken()); + val = int64_t + ( + (prefix == token::MINUS) + ? (0 - t.labelToken()) + : t.labelToken() + ); } else if (t.isScalar()) { - const scalar sval(t.scalarToken()); + const scalar sval + ( + (prefix == token::MINUS) + ? (0 - t.scalarToken()) + : t.scalarToken() + ); + const intmax_t parsed = intmax_t(std::round(sval)); val = 0 + int64_t(parsed); @@ -135,9 +159,12 @@ Foam::Istream& Foam::operator>>(Istream& is, int64_t& val) else { FatalIOErrorInFunction(is) - << "Wrong token type - expected label (int64), found " - << t.info() - << exit(FatalIOError); + << "Wrong token type - expected label (int64), found "; + if (prefix == token::PLUS || prefix == token::MINUS) + { + FatalIOError << '\'' << prefix << "' followed by "; + } + FatalIOError << t.info() << exit(FatalIOError); is.setBad(); return is; } From 44a243a94d9e43b85674c6675f0aa970b58c8994 Mon Sep 17 00:00:00 2001 From: Mark Olesen Date: Mon, 17 May 2021 17:25:20 +0200 Subject: [PATCH 2/5] ENH: improve stream handling of expansions (#2095) * removed internal upper limit on word/string length for parsed input. - Although it has not caused many problems, no reason to retain these limits. - simplify some of the internal logic for reading string-like items. - localize parsers for better separation from the header - expose new function seekCommentEnd_Cstyle(), as useful handler of C-style comments * exclude imbalanced closing ')' from word/variable - previously included this into the word/variable, but makes more sense to leave on the parser for the following token. Prevents content like 'vector (10 20 $zmax);' from being parsed as '$zmax)' instead of as '$zmax' followed by a ')'. No conceivable reason that the former would actually be desirable, but can still be obtained with brace notation: Eg, '${zmax)}' * consistent handling of ${{ ... }} expressions - within a dictionary content, the following construct was incorrectly processed: value ${{2*sqrt(0.5)}}; Complains about no dictionary/env variable "{2*sqrt(0.5)}" Now trap expressions directly and assign their own token type while reading. Later expansion can then be properly passed to the exprDriver (evalEntry) instead of incorrectly trying variable expansion. Does not alter the use of expressions embedded within other expansions. Eg, "file${{10*2}}" * improve #eval { ... } brace slurping - the initial implementation of this was rudimentary and simply grabbed everything until the next '}'. Now continue to grab content until braces are properly balanced Eg, the content: value #eval{${radius}*2}; would have previously terminated prematurely with "${radius" for the expression! NOTE: both the ${{ expr }} parsed input and the #eval { ... } input discard C/C++ comments during reading to reduce intermediate overhead for content that will be discarded before evaluation anyhow. * tighten recognition of verbatim strings and expressions. - parser was previously sloppy and would have accepted content such as "# { ..." (for example) as an verbatim string introducer. Now only accept parse if there are no intermediate characters discarded. --- src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C | 746 ++++++++++-------- src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H | 19 +- src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C | 6 +- .../functionEntries/evalEntry/evalEntry.C | 246 +++++- .../functionEntries/evalEntry/evalEntry.H | 22 +- .../primitiveEntry/primitiveEntry.C | 10 +- .../primitiveEntry/primitiveEntryIO.C | 52 +- src/fileFormats/obj/OBJstream.C | 10 +- 8 files changed, 692 insertions(+), 419 deletions(-) diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C index 10969f20f2..bdb22307ae 100644 --- a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C +++ b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.C @@ -30,6 +30,7 @@ License #include "int.H" #include "token.H" #include +#include // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // @@ -42,14 +43,14 @@ namespace { // Convert a single character to a word with length 1 -inline static Foam::word charToWord(char c) +inline Foam::word charToWord(char c) { return Foam::word(std::string(1, c), false); } // Permit slash-scoping of entries -static inline bool validVariableChar(char c) +inline bool validVariableChar(char c) { return (Foam::word::valid(c) || c == '/'); } @@ -59,23 +60,51 @@ static inline bool validVariableChar(char c) // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * // +bool Foam::ISstream::seekCommentEnd_Cstyle() +{ + // Search for end of C-style comment - "*/" + + // Can use getLine(nullptr, '*') in the logic, + // but written out looks less obscure + + char c = 0; + bool star = false; + + while (get(c)) + { + if (c == '*') + { + star = true; + } + else if (star) + { + star = false; + if (c == '/') + { + // Matched "*/" + return true; + } + } + } + + // Exhausted stream without finding "*/" sequence + return false; +} + + char Foam::ISstream::nextValid() { char c = 0; - while (true) + // Get next non-whitespace character + while (get(c)) { - // Get next non-whitespace character - while (get(c) && isspace(c)) - {} - - // Return if stream is bad - ie, previous get() failed - if (bad() || isspace(c)) + if (isspace(c)) { - return 0; + continue; } - // Is this the start of a C/C++ comment? + // Check if this starts a C/C++ comment if (c == '/') { if (!get(c)) @@ -86,37 +115,15 @@ char Foam::ISstream::nextValid() if (c == '/') { - // C++ style single-line comment - skip through past end-of-line - while (get(c) && c != '\n') - {} + // C++ comment: discard through newline + (void) getLine(nullptr, '\n'); } else if (c == '*') { - // Within a C-style comment - while (true) + // C-style comment: discard through to "*/" ending + if (!seekCommentEnd_Cstyle()) { - // Search for end of C-style comment - '*/' - if (get(c) && c == '*') - { - if (get(c)) - { - if (c == '/') - { - // matched '*/' - break; - } - else if (c == '*') - { - // check again - putback(c); - } - } - } - - if (!good()) - { - return 0; - } + return 0; } } else @@ -137,28 +144,261 @@ char Foam::ISstream::nextValid() } -void Foam::ISstream::readWordToken(token& t) +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace Foam { - word val; - if (read(val).bad()) + +// Read a verbatim string (excluding block delimiters), +// continuing until a closing "#}" has been found. +// +// The leading "#{" removed from stream prior to calling. +static ISstream& readVerbatim +( + ISstream& is, + std::string& str +) +{ + constexpr const unsigned bufLen = 8000; + static char buf[bufLen]; + + unsigned nChar = 0; + char c; + + str.clear(); + while (is.get(c)) { - t.setBad(); - } - else if (token::compound::isCompound(val)) - { - t = token::compound::New(val, *this).ptr(); - } - else - { - t = std::move(val); // Move contents to token + if (c == token::HASH) + { + char nextC; + is.get(nextC); + if (nextC == token::END_BLOCK) + { + // Found closing "#}" sequence + str.append(buf, nChar); + return is; + } + else + { + // Re-analyze the character + is.putback(nextC); + } + } + + buf[nChar++] = c; + if (nChar == bufLen) // Flush full buffer + { + str.append(buf, nChar); + nChar = 0; + } } + + + // Abnormal exit of the loop + str.append(buf, nChar); // Finalize pending content + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; + + FatalIOErrorInFunction(is) + << "Problem while reading verbatim \"" << buf + << "...\" [after " << str.length() << " chars]\n" + << exit(FatalIOError); + + return is; } +// Read a variable or expression. +// Handles "$var" and "${var}" forms, permits '/' scoping character. +// Also handles "${{expr}}". +// +// Return the token type or ERROR +// +// The leading "${" or "$c" removed from stream prior to calling. +static token::tokenType readVariable +( + ISstream& is, + std::string& str, + char c // Next character after '$' +) +{ + constexpr const unsigned bufLen = 1024; + static char buf[bufLen]; + + token::tokenType tokType(token::tokenType::VARIABLE); + + // The first two characters are known: + buf[0] = token::DOLLAR; + buf[1] = c; + + unsigned nChar = 2; // Starts with two characters + unsigned depth = 0; // Depth of {..} nesting + + str.clear(); + if (c == token::BEGIN_BLOCK) + { + // Processing '${variable}' or '${{expr}}' + ++depth; + + int lookahead = is.peek(); + if (lookahead == token::BEGIN_BLOCK) + { + // Looks like '${{expr...' + tokType = token::tokenType::EXPRESSION; + } + else if (lookahead == token::END_BLOCK) + { + // Looks like '${}' + IOWarningInFunction(is) + << "Ignoring empty ${}" << endl; + return token::tokenType::ERROR; + } + + while (is.get(c)) + { + buf[nChar++] = c; + + if (c == token::BEGIN_BLOCK) + { + ++depth; + } + else if (c == token::END_BLOCK) + { + --depth; + if (!depth) + { + // Found closing '}' character + str.append(buf, nChar); + return tokType; + } + } + else if (c == '/' && tokType == token::tokenType::EXPRESSION) + { + // Strip C/C++ comments from expressions + // Note: could also peek instead of get/putback + + if (!is.get(c)) + { + break; // Premature end of stream + } + else if (c == '/') + { + --nChar; // Remove initial '/' from buffer + + // C++ comment: discard through newline + (void) is.getLine(nullptr, '\n'); + } + else if (c == '*') + { + --nChar; // Remove initial '/' from buffer + + // C-style comment: seek "*/" ending + if (!is.seekCommentEnd_Cstyle()) + { + break; // Premature end of stream + } + } + else + { + // Re-analyze the character + is.putback(c); + } + } + + if (nChar == bufLen) // Flush full buffer + { + str.append(buf, nChar); + nChar = 0; + } + } + + + // Abnormal exit of the loop + + str.append(buf, nChar); // Finalize pending content + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; + + FatalIOErrorInFunction(is) + << "stream terminated while reading variable '" << buf + << "...' [after " << str.length() << " chars]\n" + << exit(FatalIOError); + + return token::tokenType::ERROR; + } + else if (validVariableChar(c)) + { + // Processing '$variable' + + while (is.get(c)) + { + if (!validVariableChar(c)) + { + is.putback(c); + break; + } + + if (c == token::BEGIN_LIST) + { + ++depth; + } + else if (c == token::END_LIST) + { + if (!depth) + { + // Closed ')' without opening '(': + // - don't consider it part of our input + is.putback(c); + break; + } + --depth; + } + + buf[nChar++] = c; + if (nChar == bufLen) // Flush full buffer + { + str.append(buf, nChar); + nChar = 0; + } + } + + str.append(buf, nChar); // Finalize pending content + + if (depth) + { + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; + + IOWarningInFunction(is) + << "Missing " << depth + << " closing ')' while parsing" << nl << nl + << buf << endl; + } + + return tokType; + } + else + { + // Invalid character. Terminate string (for message) + + buf[nChar--] = '\0'; + + IOWarningInFunction(is) + << "Ignoring bad variable name: " << buf << nl << endl; + } + + return token::tokenType::ERROR; +} + +} // End namespace Foam + + +// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * // + Foam::Istream& Foam::ISstream::read(token& t) { - constexpr const unsigned maxLen = 128; // Max length for labels/scalars - static char buf[maxLen]; + constexpr const unsigned bufLen = 128; // Max length for labels/scalars + static char buf[bufLen]; // Return the put back token if it exists if (Istream::getBack(t)) @@ -209,7 +449,7 @@ Foam::Istream& Foam::ISstream::read(token& t) } // String: enclosed by double quotes. - case token::BEGIN_STRING : + case token::DQUOTE : { putback(c); @@ -226,21 +466,21 @@ Foam::Istream& Foam::ISstream::read(token& t) return *this; } - // Possible verbatim string or dictionary functionEntry + // Verbatim string '#{ .. #}' or dictionary '#directive' case token::HASH : { char nextC; - if (read(nextC).bad()) - { - // Return lone '#' as word - t = charToWord(c); - } - else if (nextC == token::BEGIN_BLOCK) + int lookahead = peek(); + + if (lookahead == token::BEGIN_BLOCK) { // Verbatim string: #{ ... #} + // Token stored without the surrounding delimiters + + (void) get(nextC); // Discard '{' lookahead string val; - if (readVerbatim(val).bad()) + if (readVerbatim(*this, val).bad()) { t.setBad(); } @@ -250,9 +490,14 @@ Foam::Istream& Foam::ISstream::read(token& t) t.setType(token::tokenType::VERBATIM); } } - else + else if (read(nextC).bad()) { - // Word beginning with '#'. Eg, "#include" + // Return lone '#' as word + t = charToWord(c); + } + else if (word::valid(nextC)) + { + // Directive (wordToken) beginning with '#'. Eg, "#include" // Put back both so that '#...' is included in the directive putback(nextC); @@ -269,34 +514,44 @@ Foam::Istream& Foam::ISstream::read(token& t) t.setType(token::tokenType::DIRECTIVE); } } + else + { + // '#' followed by non-word. Just ignore leading '#'? + putback(nextC); + + IOWarningInFunction(*this) + << "Invalid sequence #" << char(nextC) + << " ... ignoring the leading '#'" << nl << endl; + } return *this; } - // Dictionary variable (as rvalue) + // Dictionary variable or ${{ expression }} case token::DOLLAR : { char nextC; if (read(nextC).bad()) { - // Return lone '$' as word + // Return lone '$' as word. Could also ignore t = charToWord(c); } else { - // Put back both so that '$...' is included in the variable - putback(nextC); - putback(c); + // NB: the parser is slightly generous here. + // It will also accept '$ {' as input. + // - to be revisited (2021-05-17) string val; - if (readVariable(val).bad()) + token::tokenType tokType = readVariable(*this, val, nextC); + if (tokType == token::tokenType::ERROR) { t.setBad(); } else { t = std::move(val); // Move contents to token - t.setType(token::tokenType::VARIABLE); + t.setType(tokType); } } @@ -340,14 +595,14 @@ Foam::Istream& Foam::ISstream::read(token& t) } buf[nChar++] = c; - if (nChar == maxLen) + if (nChar == bufLen) { // Runaway argument - avoid buffer overflow - buf[maxLen-1] = '\0'; + buf[bufLen-1] = '\0'; FatalIOErrorInFunction(*this) - << "number '" << buf << "...'\n" - << " is too long (max. " << maxLen << " characters)" + << "Number '" << buf << "...'\n" + << " is too long (max. " << bufLen << " characters)" << exit(FatalIOError); t.setBad(); @@ -397,7 +652,20 @@ Foam::Istream& Foam::ISstream::read(token& t) default: { putback(c); - readWordToken(t); + + word val; + if (read(val).bad()) + { + t.setBad(); + } + else if (token::compound::isCompound(val)) + { + t = token::compound::New(val, *this).ptr(); + } + else + { + t = std::move(val); // Move contents to token + } return *this; } @@ -414,20 +682,22 @@ Foam::Istream& Foam::ISstream::read(char& c) Foam::Istream& Foam::ISstream::read(word& str) { - constexpr const unsigned maxLen = 1024; - static char buf[maxLen]; + constexpr const unsigned bufLen = 1024; + static char buf[bufLen]; unsigned nChar = 0; - unsigned depth = 0; // Track depth of (..) nesting + unsigned depth = 0; // Depth of (..) nesting char c; - while - ( - (nChar < maxLen) - && get(c) - && word::valid(c) - ) + str.clear(); + while (get(c)) { + if (!word::valid(c)) + { + putback(c); + break; + } + if (c == token::BEGIN_LIST) { ++depth; @@ -436,42 +706,40 @@ Foam::Istream& Foam::ISstream::read(word& str) { if (!depth) { - break; // Closed ')' without an opening '(' ? ... stop + // Closed ')' without opening '(': + // - don't consider it part of our input + putback(c); + break; } --depth; } buf[nChar++] = c; + if (nChar == bufLen) // Flush full buffer + { + str.append(buf, nChar); + nChar = 0; + } } - if (nChar >= maxLen) - { - buf[errLen] = '\0'; - - FatalIOErrorInFunction(*this) - << "word '" << buf << "...'\n" - << " is too long (max. " << maxLen << " characters)" - << exit(FatalIOError); - - return *this; - } - - buf[nChar] = '\0'; // Terminate string + str.append(buf, nChar); // Finalize pending content if (bad()) { // Could probably skip this check + + strncpy(buf, str.c_str(), errLen); buf[errLen] = '\0'; FatalIOErrorInFunction(*this) - << "Problem while reading word '" << buf << "...' after " - << nChar << " characters\n" + << "Problem while reading word '" << buf + << "...' [after " << str.length() << " chars]\n" << exit(FatalIOError); return *this; } - if (nChar == 0) + if (str.empty()) { FatalIOErrorInFunction(*this) << "Invalid first character found : " << c @@ -479,25 +747,25 @@ Foam::Istream& Foam::ISstream::read(word& str) } else if (depth) { + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; + IOWarningInFunction(*this) << "Missing " << depth << " closing ')' while parsing" << nl << nl << buf << nl << endl; } - // Finalize: content already validated, assign without additional checks. - str.assign(buf, nChar); - putback(c); - return *this; } Foam::Istream& Foam::ISstream::read(string& str) { - constexpr const unsigned maxLen = 1024; - static char buf[maxLen]; + constexpr const unsigned bufLen = 1024; + static char buf[bufLen]; + unsigned nChar = 0; char c; if (!get(c)) @@ -510,7 +778,7 @@ Foam::Istream& Foam::ISstream::read(string& str) } // Note, we could also handle single-quoted strings here (if desired) - if (c != token::BEGIN_STRING) + if (c != token::DQUOTE) { FatalIOErrorInFunction(*this) << "Incorrect start of string character found : " << c @@ -519,26 +787,25 @@ Foam::Istream& Foam::ISstream::read(string& str) return *this; } - unsigned nChar = 0; + str.clear(); bool escaped = false; - - while - ( - (nChar < maxLen) - && get(c) - ) + while (get(c)) { - if (c == token::END_STRING) + if (c == '\\') + { + escaped = !escaped; // Toggle state (retains backslashes) + } + else if (c == token::DQUOTE) { if (escaped) { escaped = false; - --nChar; // Overwrite backslash + --nChar; // Overwrite backslash } else { // Done reading - str.assign(buf, nChar); + str.append(buf, nChar); return *this; } } @@ -547,253 +814,44 @@ Foam::Istream& Foam::ISstream::read(string& str) if (escaped) { escaped = false; - --nChar; // Overwrite backslash + --nChar; // Overwrite backslash } else { - buf[errLen] = buf[nChar] = '\0'; + str.append(buf, nChar); // Finalize pending content + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; FatalIOErrorInFunction(*this) - << "found '\\n' while reading string \"" - << buf << "...\"" + << "Unescaped '\\n' while reading string \"" << buf + << "...\" [after " << str.length() << " chars]\n" << exit(FatalIOError); return *this; } } - else if (c == '\\') - { - escaped = !escaped; // toggle state (retains backslashes) - } else { escaped = false; } buf[nChar++] = c; + if (nChar == bufLen) // Flush full buffer + { + // Keep lookback character (eg, for backslash escaping) + str.append(buf, nChar-1); + nChar = 1; + buf[0] = c; + } } - if (nChar >= maxLen) - { - buf[errLen] = '\0'; - - FatalIOErrorInFunction(*this) - << "string \"" << buf << "...\"\n" - << " is too long (max. " << maxLen << " characters)" - << exit(FatalIOError); - - return *this; - } + // Abnormal exit of the loop // Don't worry about a dangling backslash if string terminated prematurely - buf[errLen] = buf[nChar] = '\0'; - FatalIOErrorInFunction(*this) - << "Problem while reading string \"" << buf << "...\"" - << exit(FatalIOError); - - return *this; -} - - -Foam::Istream& Foam::ISstream::readVariable(std::string& str) -{ - constexpr const unsigned maxLen = 1024; - static char buf[maxLen]; - - unsigned nChar = 0; - unsigned depth = 0; // Track depth of (..) or {..} nesting - char c; - - // First character must be '$' - if (!get(c) || c != token::DOLLAR) - { - FatalIOErrorInFunction(*this) - << "Invalid first character found : " << c << nl - << exit(FatalIOError); - } - buf[nChar++] = c; - - // Next character should also exist. - // This should never fail, since it was checked before calling. - if (!get(c)) - { - str.assign(buf, nChar); - - IOWarningInFunction(*this) - << "Truncated variable name : " << str << nl; - - return *this; - } - buf[nChar++] = c; - - str.clear(); - if (c == token::BEGIN_BLOCK) - { - // Processing ${...} style. - ++depth; - - // Could check that the next char is good and not one of '{}' - // since this would indicate "${}", "${{..." or truncated "${" - - while (get(c)) - { - buf[nChar++] = c; - if (nChar == maxLen) - { - str.append(buf, nChar); - nChar = 0; - } - if (c == token::BEGIN_BLOCK) - { - ++depth; - } - else if (c == token::END_BLOCK) - { - --depth; - if (!depth) - { - // Found closing '}' character - str.append(buf, nChar); - return *this; - } - } - } - - // Should never reach here on normal input - - str.append(buf, nChar); // Finalize pending buffer input - - nChar = str.length(); - if (str.length() > errLen) - { - str.erase(errLen); - } - - FatalIOErrorInFunction(*this) - << "stream terminated while reading variable '" - << str.c_str() << "...' [" << nChar << "]\n" - << exit(FatalIOError); - - return *this; - } - else if (validVariableChar(c)) - { - // Processing $var style - - while - ( - (nChar < maxLen) && get(c) - && (validVariableChar(c)) - ) - { - if (c == token::BEGIN_LIST) - { - ++depth; - } - else if (c == token::END_LIST) - { - if (!depth) - { - break; // Closed ')' without an opening '(' ? ... stop - } - --depth; - } - - buf[nChar++] = c; - } - } - else - { - // Invalid character. Terminate string (for message) without - // including the invalid character in the count. - - buf[nChar--] = '\0'; - - IOWarningInFunction(*this) - << "Bad variable name: " << buf << nl << endl; - } - - if (nChar >= maxLen) - { - buf[errLen] = '\0'; - - FatalIOErrorInFunction(*this) - << "variable '" << buf << "...'\n" - << " is too long (max. " << maxLen << " characters)" - << exit(FatalIOError); - - return *this; - } - - buf[nChar] = '\0'; // Terminate string - - if (bad()) - { - // Could probably skip this check - buf[errLen] = '\0'; - - FatalIOErrorInFunction(*this) - << "Problem while reading variable '" << buf << "...' after " - << nChar << " characters\n" - << exit(FatalIOError); - - return *this; - } - - if (depth) - { - IOWarningInFunction(*this) - << "Missing " << depth - << " closing ')' while parsing" << nl << nl - << buf << nl << endl; - } - - // Finalize - str.assign(buf, nChar); - putback(c); - - return *this; -} - - -Foam::Istream& Foam::ISstream::readVerbatim(std::string& str) -{ - constexpr const unsigned maxLen = 8000; - static char buf[maxLen]; - - unsigned nChar = 0; - char c; - - str.clear(); - while (get(c)) - { - if (c == token::HASH) - { - char nextC; - get(nextC); - if (nextC == token::END_BLOCK) - { - // Found closing "#}" sequence - str.append(buf, nChar); - return *this; - } - else - { - putback(nextC); - } - } - - buf[nChar++] = c; - if (nChar == maxLen) - { - str.append(buf, nChar); - nChar = 0; - } - } - - // Truncated terminated prematurely - buf[errLen] = buf[nChar] = '\0'; + str.append(buf, nChar); // Finalize pending content + strncpy(buf, str.c_str(), errLen); + buf[errLen] = '\0'; FatalIOErrorInFunction(*this) << "Problem while reading string \"" << buf << "...\"" diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H index 14f264595b..db8bba40fa 100644 --- a/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H +++ b/src/OpenFOAM/db/IOstreams/Sstreams/ISstream.H @@ -69,18 +69,6 @@ class ISstream //- Get the next valid character char nextValid(); - //- Read a word token - void readWordToken(token& t); - - //- Read a verbatim string (excluding block delimiters). - // The leading "#{" has been removed prior to calling, - // continues until the closing "#}" has been found. - Istream& readVerbatim(std::string& str); - - //- Read a variable name starting with '$'. - // Handles "$var" and "${var}" forms, permits '/' scoping character. - Istream& readVariable(std::string& str); - //- No copy assignment void operator=(const ISstream&) = delete; @@ -137,6 +125,13 @@ public: virtual ios_base::fmtflags flags() const; + // Special-purpose Functions + + //- Discard until end of C-style comment '*/' + // \return False if stream exhausted before finding the comment end + bool seekCommentEnd_Cstyle(); + + // Read Functions //- Raw, low-level get character function. diff --git a/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C b/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C index eea67551a8..78644b4e89 100644 --- a/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C +++ b/src/OpenFOAM/db/IOstreams/Sstreams/OSstream.C @@ -133,7 +133,7 @@ Foam::Ostream& Foam::OSstream::writeQuoted // Output with surrounding quotes and backslash escaping - os_ << token::BEGIN_STRING; + os_ << token::DQUOTE; unsigned backslash = 0; for (auto iter = str.cbegin(); iter != str.cend(); ++iter) @@ -150,7 +150,7 @@ Foam::Ostream& Foam::OSstream::writeQuoted ++lineNumber_; ++backslash; // backslash escape for newline } - else if (c == token::END_STRING) + else if (c == token::DQUOTE) { ++backslash; // backslash escape for quote } @@ -167,7 +167,7 @@ Foam::Ostream& Foam::OSstream::writeQuoted // silently drop any trailing backslashes // they would otherwise appear like an escaped end-quote - os_ << token::END_STRING; + os_ << token::DQUOTE; setState(os_.rdstate()); return *this; diff --git a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C index 6673631339..efce884ea6 100644 --- a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C +++ b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.C @@ -56,60 +56,163 @@ namespace functionEntries } // End namespace Foam -// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace +{ + // This is akin to a SafeIOWarning, which does not yet exist + inline void safeIOWarning + ( + const Foam::IOstream& is, + const std::string& msg + ) + { + std::cerr + << "--> FOAM Warning :\n" + << " Reading \"" << is.name() << "\" at line " + << is.lineNumber() << '\n' + << " " << msg << std::endl; + } + +} // End anonymous namespace + + +namespace Foam +{ + +// Slurp a string until a closing '}' is found. +// Track balanced bracket/brace pairs, with max stack depth of 60. +static bool slurpUntilBalancedBrace(ISstream& is, std::string& str) +{ + constexpr const unsigned bufLen = 1024; + static char buf[bufLen]; + + is.fatalCheck(FUNCTION_NAME); + + unsigned nChar = 0; + unsigned depth = 1; // Initial '{' already seen by caller + char c; + + str.clear(); + while (is.get(c)) + { + buf[nChar++] = c; + + if (c == token::BEGIN_BLOCK) + { + ++depth; + } + else if (c == token::END_BLOCK) + { + --depth; + if (!depth) + { + // Closing '}' character - do not include in output + --nChar; + str.append(buf, nChar); + return true; + } + } + else if (c == '/') + { + // Strip C/C++ comments from expressions + // Note: could also peek instead of get/putback + + if (!is.get(c)) + { + break; // Premature end of stream + } + else if (c == '/') + { + --nChar; // Remove initial '/' from buffer + + // C++ comment: discard through newline + (void) is.getLine(nullptr, '\n'); + } + else if (c == '*') + { + --nChar; // Remove initial '/' from buffer + + // C-style comment: discard through to "*/" ending + if (!is.seekCommentEnd_Cstyle()) + { + break; // Premature end of stream + } + } + else + { + // Reanalyze the char + is.putback(c); + } + } + + if (nChar == bufLen) + { + str.append(buf, nChar); // Flush full buffer + nChar = 0; + } + } + + + // Abnormal exit of the loop + + str.append(buf, nChar); // Finalize pending content + + safeIOWarning(is, "Premature end while reading expression - missing '}'?"); + + is.fatalCheck(FUNCTION_NAME); + return false; +} + +} // End namespace Foam + + +// * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * // Foam::tokenList Foam::functionEntries::evalEntry::evaluate ( const dictionary& parentDict, - Istream& is + const string& inputExpr, + label fieldWidth, + const Istream& is ) { - #ifdef FULLDEBUG - DetailInfo - << "Using #eval - line " - << is.lineNumber() << " in file " << parentDict.name() << nl; - #endif - - token tok(is); - label fieldWidth(1); // Field width for the result - if (tok.isLabel()) - { - // - #eval INT "expr" - // - #eval INT { expr } - // - #eval INT #{ expr #} - fieldWidth = max(1, tok.labelToken()); - is >> tok; - } - - string s; // String to evaluate - if (tok.isString()) - { - // - #eval "expr" - // - #eval #{ expr #} - s = tok.stringToken(); - } - else if (tok.isPunctuation(token::BEGIN_BLOCK)) - { - // - #eval { expr } - dynamic_cast(is).getLine(s, token::END_BLOCK); - } - else + // Field width for the result + if (fieldWidth < 1) { FatalIOErrorInFunction(is) - << "Invalid input for #eval." - " Expecting a string or block to evaluate, but found" << nl - << tok.info() << endl + << "Invalid field width: " << fieldWidth << nl << endl << exit(FatalIOError); } #ifdef FULLDEBUG DetailInfo - << "input: " << s << endl; + << "input: " << inputExpr << endl; #endif // Expand with env=true, empty=true, subDict=false // with comments stripped. // Special handling of $[...] syntax enabled. + + string s; + + // Passed '${{ expr }}' by accident, or on purpuse + if + ( + inputExpr[0] == token::DOLLAR + && inputExpr[1] == token::BEGIN_BLOCK + && inputExpr[2] == token::BEGIN_BLOCK + && inputExpr[inputExpr.length()-1] == token::END_BLOCK + && inputExpr[inputExpr.length()-2] == token::END_BLOCK + ) + { + s.assign(inputExpr, 3, inputExpr.length()-5); + } + else + { + s.assign(inputExpr); + } + expressions::exprString::inplaceExpand(s, parentDict, true); stringOps::inplaceTrim(s); @@ -184,6 +287,60 @@ Foam::tokenList Foam::functionEntries::evalEntry::evaluate } +Foam::tokenList Foam::functionEntries::evalEntry::evaluate +( + const dictionary& parentDict, + Istream& is +) +{ + #ifdef FULLDEBUG + DetailInfo + << "Using #eval - line " + << is.lineNumber() << " in file " << parentDict.name() << nl; + #endif + + token tok(is); + label fieldWidth(1); // Field width for the result + if (tok.isLabel()) + { + // - #eval INT "expr" + // - #eval INT { expr } + // - #eval INT #{ expr #} + fieldWidth = max(1, tok.labelToken()); + is >> tok; + } + + string str; // The string to evaluate + if (tok.isString()) + { + // - #eval "expr" + // - #eval #{ expr #} + // - #eval ${{ expr }} - wierd but handled + str = tok.stringToken(); + } + else if (tok.isPunctuation(token::BEGIN_BLOCK)) + { + // - #eval { expr } + slurpUntilBalancedBrace(dynamic_cast(is), str); + } + else + { + FatalIOErrorInFunction(is) + << "Invalid input for #eval." + " Expecting a string or block to evaluate, but found" << nl + << tok.info() << endl + << exit(FatalIOError); + } + + tokenList toks + ( + evalEntry::evaluate(parentDict, str, fieldWidth, is) + ); + + return toks; +} + + // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // bool Foam::functionEntries::evalEntry::execute @@ -201,4 +358,21 @@ bool Foam::functionEntries::evalEntry::execute } +bool Foam::functionEntries::evalEntry::execute +( + const dictionary& parentDict, + primitiveEntry& entry, + const string& inputExpr, + label fieldWidth, + Istream& is +) +{ + tokenList toks(evaluate(parentDict, inputExpr, fieldWidth, is)); + + entry.append(std::move(toks), true); // Lazy resizing + + return true; +} + + // ************************************************************************* // diff --git a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H index c4e482b624..58d450dd9f 100644 --- a/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H +++ b/src/OpenFOAM/db/dictionary/functionEntries/evalEntry/evalEntry.H @@ -87,18 +87,36 @@ class evalEntry { //- Evaluate and return a token list - static tokenList evaluate(const dictionary& parentDict, Istream& is); + static tokenList evaluate + ( + const dictionary& parentDict, + const string& inputExpr, //!< String to expand and evaluate + label fieldWidth, //!< Field width for the result + const Istream& is //!< For reporting errors + ); + //- Evaluate and return a token list + static tokenList evaluate(const dictionary& parentDict, Istream& is); public: - //- Execute in a primitiveEntry context + //- Execute in a primitiveEntry context, extracts token or line static bool execute ( const dictionary& parentDict, primitiveEntry& thisEntry, Istream& is ); + + //- Execute in a primitiveEntry context, evaluating the given content + static bool execute + ( + const dictionary& parentDict, + primitiveEntry& entry, + const string& inputExpr, + label fieldWidth, + Istream& is + ); }; diff --git a/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntry.C b/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntry.C index 53c5b301ef..20a7b23333 100644 --- a/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntry.C +++ b/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntry.C @@ -35,7 +35,7 @@ License // Find the type/position of the ":-" or ":+" alternative values // Returns 0, '-', '+' corresponding to not-found or ':-' or ':+' -static inline int findParameterAlternative +static inline char findParameterAlternative ( const std::string& s, std::string::size_type& pos, @@ -50,7 +50,7 @@ static inline int findParameterAlternative if (pos < endPos) { // in-range: check for '+' or '-' following the ':' - const int altType = s[pos+1]; + const char altType = s[pos+1]; if (altType == '+' || altType == '-') { return altType; @@ -78,11 +78,13 @@ bool Foam::primitiveEntry::expandVariable const dictionary& dict ) { - int altType = 0; // Type ('-' or '+') for ":-" or ":+" alternatives + char altType = 0; // Type ('-' or '+') for ":-" or ":+" alternatives word expanded; string altValue; - if (varName.size() > 1 && varName[0] == token::BEGIN_BLOCK) + // Any ${{ expr }} entries have been trapped and processed elsewhere + + if (varName[0] == token::BEGIN_BLOCK && varName.size() > 1) { // Replace content between {} with string expansion and // handle ${parameter:-word} or ${parameter:+word} diff --git a/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntryIO.C b/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntryIO.C index eb1f21d224..5dccf2fb0a 100644 --- a/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntryIO.C +++ b/src/OpenFOAM/db/dictionary/primitiveEntry/primitiveEntryIO.C @@ -28,6 +28,7 @@ License #include "primitiveEntry.H" #include "functionEntry.H" +#include "evalEntry.H" // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // @@ -63,25 +64,51 @@ bool Foam::primitiveEntry::acceptToken if (tok.isDirective()) { - // Directive: wordToken starts with '#' + // Directive (wordToken) begins with '#'. Eg, "#include" + // Remove leading '#' sigil before dispatching + const word& key = tok.wordToken(); + // Min-size is 2: sigil '#' with any content accept = ( - disableFunctionEntries - || key.size() == 1 + (disableFunctionEntries || key.size() < 2) || !expandFunction(key.substr(1), dict, is) ); } + else if (tok.isExpression()) + { + // Expression (stringToken): ${{ expr }} + // Surrounding delimiters are stripped as required in evalEntry + + const string& key = tok.stringToken(); + + // Min-size is 6: decorators '${{}}' with any content + accept = + ( + (disableFunctionEntries || key.size() < 6) + || !functionEntries::evalEntry::execute + ( + dict, + *this, + key, + 1, // Field width is 1 + is // For error messages + ) + ); + } else if (tok.isVariable()) { - // Variable: stringToken starts with '$' + // Variable (stringToken): starts with '$' + // Eg, "$varName" or "${varName}" + // Remove leading '$' sigil before dispatching + const string& key = tok.stringToken(); + // Min-size is 2: sigil '$' with any content accept = ( - disableFunctionEntries - || key.size() == 1 + (disableFunctionEntries || key.size() < 2) || !expandVariable(key.substr(1), dict) ); } @@ -116,7 +143,7 @@ bool Foam::primitiveEntry::read(const dictionary& dict, Istream& is) // - similarly, the bitmask is tested *after* decreasing depth uint64_t balanced = 0u; - label depth = 0; + int depth = 0; token tok; while @@ -274,19 +301,18 @@ void Foam::primitiveEntry::write(Ostream& os, const bool contentsOnly) const os.writeKeyword(keyword()); } - bool addSpace = false; // Separate from previous tokens with a space + bool addSpace = false; // Separate from previous token with a space for (const token& tok : *this) { if (addSpace) os << token::SPACE; + addSpace = true; - // Try to output token directly, with special handling in Ostreams. - + // Output token with direct handling in Ostream(s), + // or use normal '<<' output operator if (!os.write(tok)) { - os << tok; // Revert to normal '<<' output operator + os << tok; } - - addSpace = true; // Separate from following tokens } if (!contentsOnly) diff --git a/src/fileFormats/obj/OBJstream.C b/src/fileFormats/obj/OBJstream.C index 893ea3f55a..904b585095 100644 --- a/src/fileFormats/obj/OBJstream.C +++ b/src/fileFormats/obj/OBJstream.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2012-2016 OpenFOAM Foundation - Copyright (C) 2020 OpenCFD Ltd. + Copyright (C) 2020-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -120,8 +120,8 @@ Foam::Ostream& Foam::OBJstream::writeQuoted return *this; } - - OFstream::write(static_cast(token::BEGIN_STRING)); + // Output with surrounding quotes and backslash escaping + OFstream::write(static_cast(token::DQUOTE)); unsigned backslash = 0; for (auto iter = str.cbegin(); iter != str.cend(); ++iter) @@ -138,7 +138,7 @@ Foam::Ostream& Foam::OBJstream::writeQuoted ++lineNumber_; ++backslash; // backslash escape for newline } - else if (c == token::END_STRING) + else if (c == token::DQUOTE) { ++backslash; // backslash escape for quote } @@ -155,7 +155,7 @@ Foam::Ostream& Foam::OBJstream::writeQuoted // silently drop any trailing backslashes // they would otherwise appear like an escaped end-quote - OFstream::write(static_cast(token::END_STRING)); + OFstream::write(static_cast(token::DQUOTE)); return *this; } From c9fda67b5f70130ef2528770d1977504bf17546e Mon Sep 17 00:00:00 2001 From: Mark Olesen Date: Tue, 18 May 2021 08:21:55 +0200 Subject: [PATCH 3/5] ENH: refactor function arg splitting -> stringOps::splitFunctionArgs --- .../test/splitFunctionArgs/Make/files | 3 + .../test/splitFunctionArgs/Make/options | 2 + .../Test-splitFunctionArgs.C | 155 ++++++++++++ .../test/splitFunctionArgs/testNames1 | 47 ++++ src/OpenFOAM/Make/files | 1 + .../functionObjectList/functionObjectList.C | 97 ++------ .../primitives/strings/stringOps/stringOps.C | 2 +- .../primitives/strings/stringOps/stringOps.H | 33 +++ .../strings/stringOps/stringOpsSplit.C | 221 ++++++++++++++++++ .../strings/stringOps/stringOpsTemplates.C | 27 +++ 10 files changed, 514 insertions(+), 74 deletions(-) create mode 100644 applications/test/splitFunctionArgs/Make/files create mode 100644 applications/test/splitFunctionArgs/Make/options create mode 100644 applications/test/splitFunctionArgs/Test-splitFunctionArgs.C create mode 100644 applications/test/splitFunctionArgs/testNames1 create mode 100644 src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C diff --git a/applications/test/splitFunctionArgs/Make/files b/applications/test/splitFunctionArgs/Make/files new file mode 100644 index 0000000000..5bb22f6261 --- /dev/null +++ b/applications/test/splitFunctionArgs/Make/files @@ -0,0 +1,3 @@ +Test-splitFunctionArgs.C + +EXE = $(FOAM_USER_APPBIN)/Test-splitFunctionArgs diff --git a/applications/test/splitFunctionArgs/Make/options b/applications/test/splitFunctionArgs/Make/options new file mode 100644 index 0000000000..18e6fe47af --- /dev/null +++ b/applications/test/splitFunctionArgs/Make/options @@ -0,0 +1,2 @@ +/* EXE_INC = */ +/* EXE_LIBS = */ diff --git a/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C b/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C new file mode 100644 index 0000000000..8deaf6b5d0 --- /dev/null +++ b/applications/test/splitFunctionArgs/Test-splitFunctionArgs.C @@ -0,0 +1,155 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2021 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 . + +Application + Test-splitFunctionArgs + +Description + Test splitting of function name args + +\*---------------------------------------------------------------------------*/ + +#include "argList.H" +#include "IOstreams.H" +#include "IOobject.H" +#include "IFstream.H" +#include "dictionary.H" +#include "stringOps.H" +#include "Tuple2.H" + +using namespace Foam; + +// Split out function name and any arguments +// - use as per functionObjectList +void testFunctionNameAndArgsSplit(const std::string& line) +{ + word funcName; + wordRes args; + List> namedArgs; + + const auto lbracket = line.find('('); + if (lbracket == std::string::npos) + { + funcName = word::validate(line); + // No args + } + else + { + funcName = word::validate(line.substr(0, lbracket)); + std::string params; + + const auto rbracket = line.rfind(')'); + if (rbracket != std::string::npos && lbracket < rbracket) + { + params = line.substr(lbracket+1, (rbracket - lbracket - 1)); + } + else + { + params = line.substr(lbracket+1); + } + + Info<<"parsing: " << params << nl; + + stringOps::splitFunctionArgs(params, args, namedArgs); + } + + Info<< nl + << line << nl + << "function: <" << funcName << '>' << nl + << " args: " << args << nl + << " named: " << namedArgs << nl; +} + + +// Split out any arguments +void testArgsSplit(const std::string& line) +{ + wordRes args; + List> namedArgs; + stringOps::splitFunctionArgs(line, args, namedArgs); + + Info<< nl + << line << nl + << " args: " << args << nl + << " named: " << namedArgs << nl; +} + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +// Main program: + +int main(int argc, char *argv[]) +{ + argList::noBanner(); + argList::noParallel(); + argList::addArgument("file1 .. fileN"); + argList args(argc, argv, false, true); + + if (args.size() <= 1) + { + InfoErr<< "Provide a file or files to test" << nl; + } + else + { + for (label argi=1; argi < args.size(); ++argi) + { + IOobject::writeDivider(Info); + + const auto inputFile = args.get(argi); + IFstream is(inputFile); + + string line; + while (is.getLine(line)) + { + if (line.empty() || line[0] == '#') + { + continue; + } + + if (line.starts_with("function:")) + { + auto trim = line.find(':'); + ++trim; + while (isspace(line[trim])) + { + ++trim; + } + + line.erase(0, trim); + testFunctionNameAndArgsSplit(line); + } + else + { + testArgsSplit(line); + } + } + } + } + + return 0; +} + + +// ************************************************************************* // diff --git a/applications/test/splitFunctionArgs/testNames1 b/applications/test/splitFunctionArgs/testNames1 new file mode 100644 index 0000000000..8d771f5e5d --- /dev/null +++ b/applications/test/splitFunctionArgs/testNames1 @@ -0,0 +1,47 @@ +# ----------------------------------------------------------------------------- +# Test names for splitting. Comment character as per -*- sh -*- mode +# +# Prefix with "function: " to test with function name splitting + +function: basic + +function: func1( a , b ); + +function: func2(a, value=10); + +# discard or flag bad/missing parameters? +function: func3(a , , , value=10); + +function: func4(); + +function: func5( abc ); + +start=1, end=2 + +start=1, end=2, + +start=100, end= 200, abc + +value=100 + +start=1, end=2 + +origin=(0 0 0), scale=2, normal=(0 0 1) + + +# Canonical with named args +function: patchAverage(patch=inlet, p) + +# Canonical with unnamed and named args +function: patchAverage(other, patch=inlet, pval) + + +function: patchAverage(patch=(inlet|outlet), p) + +# General +(a, b) + +allow=(inlet|outlet), deny=false, regular(value) + + +# ----------------------------------------------------------------------------- diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files index 2bfb23509a..beeaf6afb4 100644 --- a/src/OpenFOAM/Make/files +++ b/src/OpenFOAM/Make/files @@ -145,6 +145,7 @@ $(strings)/parsing/genericRagelLemonDriver.C $(strings)/stringOps/stringOps.C $(strings)/stringOps/stringOpsEvaluate.C $(strings)/stringOps/stringOpsSort.C +$(strings)/stringOps/stringOpsSplit.C expr = expressions $(expr)/exprEntry/expressionEntry.C diff --git a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C index 837402f0fa..1c7f5a9186 100644 --- a/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C +++ b/src/OpenFOAM/db/functionObjects/functionObjectList/functionObjectList.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2017 OpenFOAM Foundation - Copyright (C) 2015-2020 OpenCFD Ltd. + Copyright (C) 2015-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -243,91 +243,42 @@ bool Foam::functionObjectList::readFunctionObject // 'patchAverage(patch=inlet, p)' -> funcName = patchAverage; // args = (patch=inlet, p); field = p - word funcName(funcNameArgs); - - int argLevel = 0; + word funcName; wordRes args; - List> namedArgs; - bool hasNamedArg = false; - word argName; - word::size_type start = 0; - word::size_type i = 0; - - for - ( - word::const_iterator iter = funcNameArgs.begin(); - iter != funcNameArgs.end(); - ++iter - ) { - char c = *iter; - - if (c == '(') + const auto argsBeg = funcNameArgs.find('('); + if (argsBeg == std::string::npos) { - if (argLevel == 0) - { - funcName = funcNameArgs.substr(start, i - start); - start = i+1; - } - ++argLevel; + // Function name only, no args + funcName = word::validate(funcNameArgs); } - else if (c == ',' || c == ')') + else { - if (argLevel == 1) - { - if (hasNamedArg) - { - namedArgs.append - ( - Tuple2 - ( - argName, - funcNameArgs.substr(start, i - start) - ) - ); - hasNamedArg = false; - } - else - { - args.append - ( - wordRe - ( - word::validate - ( - funcNameArgs.substr(start, i - start) - ) - ) - ); - } - start = i+1; - } + // Leading function name + funcName = word::validate(funcNameArgs.substr(0, argsBeg)); - if (c == ')') - { - if (argLevel == 1) - { - break; - } - --argLevel; - } - } - else if (c == '=') - { - argName = word::validate + const auto argsEnd = funcNameArgs.rfind(')'); + + stringOps::splitFunctionArgs ( - funcNameArgs.substr(start, i - start) + funcNameArgs.substr + ( + (argsBeg + 1), + ( + (argsEnd != std::string::npos && argsBeg < argsEnd) + ? (argsEnd - argsBeg - 1) + : std::string::npos + ) + ), + args, + namedArgs ); - - start = i+1; - hasNamedArg = true; } - - ++i; } + // Search for the functionObject dictionary fileName path = functionObjectList::findDict(funcName); diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C index be512d45d6..63a7a5a60c 100644 --- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.C +++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.C @@ -693,7 +693,7 @@ static void expandString } // End namespace Foam -// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * // std::string::size_type Foam::stringOps::count ( diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H index 33424ea873..29d26b2bee 100644 --- a/src/OpenFOAM/primitives/strings/stringOps/stringOps.H +++ b/src/OpenFOAM/primitives/strings/stringOps/stringOps.H @@ -57,6 +57,7 @@ namespace Foam // Forward Declarations class OSstream; +template class Tuple2; /*---------------------------------------------------------------------------*\ Namespace stringOps Declaration @@ -282,6 +283,14 @@ namespace stringOps // Return true if a replacement was successful. bool inplaceReplaceVar(std::string& s, const word& varName); + //- Return a copy of the input string with validated characters + template + StringType validate + ( + const std::string& str, + const UnaryPredicate& accept, + const bool invert=false //!< Invert the test logic + ); //- Find (first, last) non-space locations in string or sub-string. // This may change to std::string_view in the future. @@ -334,6 +343,30 @@ namespace stringOps //- Inplace transform string with std::toupper on each character void inplaceUpper(std::string& s); + //- Split out arguments (named or unnamed) from an input string. + // + // For example, + // \verbatim + // (U) + // -> named = () + // -> unnamed = (U) + // + // (patch=inlet, p) + // -> named = ((patch inlet)) + // -> unnamed = (p) + // + // testing, start=100, stop=200 + // -> named = ((start 100)(stop 200)) + // -> unnamed = (testing) + // \endverbatim + // + // \return total number of arguments + label splitFunctionArgs + ( + const std::string& str, + wordRes& args, + List>& namedArgs + ); //- Split string into sub-strings at the delimiter character. // Empty sub-strings are normally suppressed. diff --git a/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C b/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C new file mode 100644 index 0000000000..05de8a2d6a --- /dev/null +++ b/src/OpenFOAM/primitives/strings/stringOps/stringOpsSplit.C @@ -0,0 +1,221 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2016 OpenFOAM Foundation + Copyright (C) 2021 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 . + +\*---------------------------------------------------------------------------*/ + +#include "stringOps.H" +#include "Pair.H" +#include "Tuple2.H" +#include + +// * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * // + +namespace +{ + +inline Foam::word validateVariableName(const std::string& str) +{ + return Foam::stringOps::validate + ( + str, + [](char c) + { + return (Foam::word::valid(c) || c == '/' || c == '{' || c == '}'); + } + ); +} + +} // End anonymous namespace + + +// * * * * * * * * * * * * * * * Global Functions * * * * * * * * * * * * * // + +Foam::label Foam::stringOps::splitFunctionArgs +( + const std::string& str, + wordRes& args, + List>& namedArgs +) +{ + args.clear(); + namedArgs.clear(); + + // Similar to code originally in functionObjectList (v2012 and earlier) + // except that the function-name handling is now done prior to calling + + // (U) + // -> named = () + // -> unnamed = (U) + // + // (patch=inlet, p) + // -> named = ((patch inlet)) + // -> unnamed = (p) + // + // start=100, stop=200 + // -> named = ((start 100) (stop 200) ) + // -> unnamed = () + // + // origin=(0 0 0) , scale=2 , normal=(0 0 1) + + + // Use begin/end parse positions + typedef Pair rangeType; + + // For unnamed: beg/end range of each arg + std::vector unnamed; + + // For named: list of beg/end ranges for (name, arg) + std::vector named; + + // The beg/end range of the argument name + rangeType argName(0, 0); + + // If argName is valid + bool isNamed = false; + + // The depth of the argument parsing + int argLevel = 0; + + const auto strLen = str.length(); + + // Pass 1: parsing begin/end parse positions. + + for (std::string::size_type pos = 0, beg = 0; pos < strLen; ++pos) + { + const bool penultimate = ((pos + 1) == strLen); + const char c = str[pos]; + + if (c == ')') + { + --argLevel; + } + + if (c == '=') + { + // Introducer for named argument + argName = rangeType(beg, pos); + isNamed = true; + beg = pos + 1; + } + else if (c == '(') + { + ++argLevel; + } + else if (penultimate || (c == ',')) // OR: (c == ',' || c == ';') + { + if (penultimate && (c != ',')) // OR: (c != ',' && c != ';') + { + ++pos; // Until the end, but do not include comma + } + + if (argLevel == 0) + { + if (isNamed) + { + named.push_back(argName); + named.push_back(rangeType(beg, pos)); + } + else + { + unnamed.push_back(rangeType(beg, pos)); + } + isNamed = false; + beg = pos + 1; + } + } + } + + + // Stage 2: Convert to concrete string and store + + + // unnamed + { + const label nInputArgs = static_cast