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/applications/test/namedDictionary/Make/files b/applications/test/namedDictionary/Make/files new file mode 100644 index 0000000000..7877feb85b --- /dev/null +++ b/applications/test/namedDictionary/Make/files @@ -0,0 +1,3 @@ +Test-namedDictionary.C + +EXE = $(FOAM_USER_APPBIN)/Test-namedDictionary diff --git a/applications/test/namedDictionary/Make/options b/applications/test/namedDictionary/Make/options new file mode 100644 index 0000000000..18e6fe47af --- /dev/null +++ b/applications/test/namedDictionary/Make/options @@ -0,0 +1,2 @@ +/* EXE_INC = */ +/* EXE_LIBS = */ diff --git a/applications/test/namedDictionary/Test-namedDictionary.C b/applications/test/namedDictionary/Test-namedDictionary.C new file mode 100644 index 0000000000..fc7e418547 --- /dev/null +++ b/applications/test/namedDictionary/Test-namedDictionary.C @@ -0,0 +1,84 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / 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-namedDictionary + +Description + Test handling of keyType/dictionary + +\*---------------------------------------------------------------------------*/ + +#include "argList.H" +#include "IOstreams.H" +#include "IOobject.H" +#include "IFstream.H" +#include "namedDictionary.H" + +using namespace Foam; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // +// 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) + { + const auto dictFile = args.get(argi); + IFstream ifs(dictFile); + + dictionary dict(ifs); + + IOobject::writeDivider(Info) << nl; + + for (const entry& dEntry : dict) + { + if (!dEntry.isStream()) + { + continue; + } + Info<< "input: " << dEntry << nl; + List list(dEntry.stream()); + Info<< "list: " << list << nl; + } + } + } + + return 0; +} + + +// ************************************************************************* // diff --git a/applications/test/namedDictionary/testDict1 b/applications/test/namedDictionary/testDict1 new file mode 100644 index 0000000000..99c1960109 --- /dev/null +++ b/applications/test/namedDictionary/testDict1 @@ -0,0 +1,83 @@ +/*--------------------------------*- 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; +} +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +entry1 +( + value1 + value2 ; // spurious trailing ';' is removed + this { correct true; } + + { } // Empty everything == ignore + + { anonymous true; } +); + + +actions1 +( + starting + { + name self; + type faceSet; + action new; + source something; + } + + { + name self; + type faceSet; + action subset; + source something; + } +); + + +actions2 +( + { + name self; + type faceSet; + action new; + source something; + } + + { + name self; + type faceSet; + action subset; + source something; + } +); + +actions3 +( + { + name self; + type faceSet; + action new; + source something; + } + + subset + { + name self; + type faceSet; + action subset; + source something; + } +); + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 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/applications/utilities/mesh/manipulation/topoSet/topoSet.C b/applications/utilities/mesh/manipulation/topoSet/topoSet.C index 162fd3fd10..6e590a82bb 100644 --- a/applications/utilities/mesh/manipulation/topoSet/topoSet.C +++ b/applications/utilities/mesh/manipulation/topoSet/topoSet.C @@ -6,7 +6,7 @@ \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2011-2017 OpenFOAM Foundation - Copyright (C) 2018-2020 OpenCFD Ltd. + Copyright (C) 2018-2021 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. @@ -47,6 +47,7 @@ Description #include "faceZoneSet.H" #include "pointZoneSet.H" #include "IOdictionary.H" +#include "namedDictionary.H" using namespace Foam; @@ -237,7 +238,7 @@ int main(int argc, char *argv[]) IOdictionary topoSetDict(dictIO); // Read set construct info from dictionary - PtrList actions(topoSetDict.lookup("actions")); + List actionEntries(topoSetDict.lookup("actions")); forAll(timeDirs, timeI) { @@ -248,8 +249,13 @@ int main(int argc, char *argv[]) meshReadUpdate(mesh); // Execute all actions - for (const dictionary& dict : actions) + for (const namedDictionary& actionEntry : actionEntries) { + const dictionary& dict = actionEntry.dict(); + if (dict.empty()) + { + continue; + } const word setName(dict.get("name")); const word setType(dict.get("type")); diff --git a/src/OpenFOAM/Make/files b/src/OpenFOAM/Make/files index 2bfb23509a..cd25b075df 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 @@ -275,7 +276,9 @@ $(dictionary)/dictionaryIO.C $(dictionary)/dictionarySearch.C $(dictionary)/dictionaryCompat.C +/* Additional helpers */ $(dictionary)/dictionaryContent/dictionaryContent.C +$(dictionary)/namedDictionary/namedDictionary.C entry = $(dictionary)/entry $(entry)/entry.C 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..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)) @@ -199,8 +439,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 : { @@ -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(); @@ -368,7 +623,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)) { @@ -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 bf7e25c88a..78644b4e89 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; } @@ -127,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) @@ -144,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 } @@ -161,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/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/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/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/db/dictionary/namedDictionary/namedDictionary.C b/src/OpenFOAM/db/dictionary/namedDictionary/namedDictionary.C new file mode 100644 index 0000000000..3bbd6608fc --- /dev/null +++ b/src/OpenFOAM/db/dictionary/namedDictionary/namedDictionary.C @@ -0,0 +1,122 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020 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 "namedDictionary.H" + +// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // + +Foam::namedDictionary::namedDictionary() +: + Tuple2() +{} + + +Foam::namedDictionary::namedDictionary(Istream& is) +{ + is >> *this; +} + + +// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // + +void Foam::namedDictionary::clear() +{ + first().clear(); + second().clear(); +} + + +bool Foam::namedDictionary::empty() const noexcept +{ + return (first().empty() && second().empty()); +} + + +// * * * * * * * * * * * * * * Friend Operators * * * * * * * * * * * * * * // + +Foam::Istream& Foam::operator>>(Istream& is, namedDictionary& obj) +{ + obj.clear(); + + // Three possible inputs: + // - key + // - key { ... } + // - { ... } + + // Minor consistency with primitiveEntry, also accept the following: + // - key ; + + token tok(is); + is.putBack(tok); + + if (!tok.isPunctuation(token::BEGIN_BLOCK)) + { + is >> obj.keyword(); + is >> tok; + + // Discards possible trailing ';' + if (!tok.isPunctuation(token::END_STATEMENT)) + { + is.putBack(tok); + } + } + + if (tok.isPunctuation(token::BEGIN_BLOCK)) + { + obj.dict().read(is); + } + + is.check(FUNCTION_NAME); + return is; +} + + +Foam::Ostream& Foam::operator<<(Ostream& os, const namedDictionary& obj) +{ + // Three possible outputs: + // - key + // - key { ... } + // - { ... } + // No distinction between a missing and an empty dictionary + + if (obj.keyword().empty() || !obj.dict().empty()) + { + // Never allow empty output. + // Otherwise cannot re-read for streaming + obj.dict().writeEntry(obj.keyword(), os); + } + else + { + os << obj.keyword(); + } + + return os; +} + + +// ************************************************************************* // diff --git a/src/OpenFOAM/db/dictionary/namedDictionary/namedDictionary.H b/src/OpenFOAM/db/dictionary/namedDictionary/namedDictionary.H new file mode 100644 index 0000000000..eb6c18b2c5 --- /dev/null +++ b/src/OpenFOAM/db/dictionary/namedDictionary/namedDictionary.H @@ -0,0 +1,154 @@ +/*---------------------------------------------------------------------------*\ + ========= | + \\ / F ield | OpenFOAM: The Open Source CFD Toolbox + \\ / O peration | + \\ / A nd | www.openfoam.com + \\/ M anipulation | +------------------------------------------------------------------------------- + Copyright (C) 2020 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 . + +Class + Foam::namedDictionary + +Description + A tuple of keyType and dictionary, which can be used when reading + named or unnamed dictionary entries or simply a name. + + For example, + \verbatim + fields + ( + U + T { relax false; } + ); + \endverbatim + + In can also be used in situations where an individual dictionary entry + should be read. + \verbatim + actions + ( + testing { action new; ... } // An action with a name + { action subset; } // Unnamed action + ); + \endverbatim + Normal dictionary reading would fail for this type of input since the + leading 'testing' keyword would cause the entire content to be considered + a single dictionary. + +Note + No distinction currently made between a missing and an empty dictionary. + +SourceFiles + namedDictionary.C + +\*---------------------------------------------------------------------------*/ + +#ifndef namedDictionary_H +#define namedDictionary_H + +#include "dictionary.H" +#include "Tuple2.H" + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +namespace Foam +{ + +// Forward Declarations +class namedDictionary; +Istream& operator>>(Istream&, namedDictionary&); +Ostream& operator<<(Ostream&, const namedDictionary&); + +/*---------------------------------------------------------------------------*\ + Class namedDictionary Declaration +\*---------------------------------------------------------------------------*/ + +class namedDictionary +: + public Tuple2 +{ +public: + + // Constructors + + //- Inherit constructors + using Tuple2::Tuple2; + + //- Default construct + namedDictionary(); + + //- Construct from Istream + explicit namedDictionary(Istream& is); + + + //- Destructor + ~namedDictionary() = default; + + + // Member Functions + + //- Clear keyword and dictionary + void clear(); + + //- Empty if both keyword and dictionary are empty + bool empty() const noexcept; + + //- Return keyword + const keyType& keyword() const noexcept + { + return first(); + } + + //- Return non-const access to keyword + keyType& keyword() noexcept + { + return first(); + } + + //- Read-access to the dictionay + const dictionary& dict() const noexcept + { + return second(); + } + + //- Write access to the dictionay + dictionary& dict() noexcept + { + return second(); + } + + + // IOstream Operators + + friend Istream& operator>>(Istream&, namedDictionary&); + friend Ostream& operator<<(Ostream&, const namedDictionary&); +}; + + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +} // End namespace Foam + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // + +#endif + +// ************************************************************************* // 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/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/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; } 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