- stricter checking for common dictionary input errors such as a missing ';' for primitive entries, extra or missing closing '}' etc.
500 lines
14 KiB
C
500 lines
14 KiB
C
/*---------------------------------------------------------------------------*\
|
|
========= |
|
|
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
|
|
\\ / O peration |
|
|
\\ / A nd | Copyright (C) 2011-2016 OpenFOAM Foundation
|
|
\\/ M anipulation | Copyright (C) 2018 OpenCFD Ltd.
|
|
-------------------------------------------------------------------------------
|
|
License
|
|
This file is part of OpenFOAM.
|
|
|
|
OpenFOAM is free software: you can redistribute it and/or modify it
|
|
under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
\*---------------------------------------------------------------------------*/
|
|
|
|
#include "entry.H"
|
|
#include "primitiveEntry.H"
|
|
#include "dictionaryEntry.H"
|
|
#include "functionEntry.H"
|
|
#include "includeEntry.H"
|
|
#include "stringOps.H"
|
|
#include "dictionaryListEntry.H"
|
|
|
|
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
|
|
|
|
bool Foam::entry::getKeyword(keyType& keyword, token& keyToken, Istream& is)
|
|
{
|
|
// Read the next valid token discarding spurious ';'s
|
|
do
|
|
{
|
|
if
|
|
(
|
|
is.read(keyToken).bad()
|
|
|| is.eof()
|
|
|| !keyToken.good()
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
while (keyToken == token::END_STATEMENT);
|
|
|
|
// If the token is a valid keyword set 'keyword' return true...
|
|
if (keyToken.isWord())
|
|
{
|
|
keyword = keyToken.wordToken();
|
|
return true;
|
|
}
|
|
|
|
if (keyToken.isString())
|
|
{
|
|
// Enable wildcards
|
|
keyword = keyToken.stringToken();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Foam::entry::getKeyword(keyType& keyword, Istream& is)
|
|
{
|
|
token keyToken;
|
|
const bool valid = getKeyword(keyword, keyToken, is);
|
|
|
|
if (valid)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Mark as invalid, but allow for some more checking
|
|
if (keyToken == token::END_BLOCK || is.eof())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Otherwise the token is invalid
|
|
std::cerr
|
|
<< "--> FOAM Warning :" << nl
|
|
<< " From function " << FUNCTION_NAME << nl
|
|
<< " in file " << __FILE__ << " at line " << __LINE__ << nl
|
|
<< " Reading " << is.name() << nl
|
|
<< " found " << keyToken << nl
|
|
<< " expected either " << token::END_BLOCK << " or EOF"
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Foam::entry::New
|
|
(
|
|
dictionary& parentDict,
|
|
Istream& is,
|
|
const entry::inputMode inpMode,
|
|
const int endChar
|
|
)
|
|
{
|
|
// The inputMode for dealing with duplicate entries
|
|
const entry::inputMode mode =
|
|
(
|
|
inpMode == inputMode::GLOBAL
|
|
? globalInputMode
|
|
: inpMode
|
|
);
|
|
|
|
// If somehow the global itself is 'global' - this is a severe logic error.
|
|
if (mode == inputMode::GLOBAL)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Cannot use 'GLOBAL' as an inputMode"
|
|
<< exit(FatalIOError);
|
|
}
|
|
|
|
is.fatalCheck(FUNCTION_NAME);
|
|
|
|
keyType keyword;
|
|
token keyToken;
|
|
|
|
// Get the next keyword and if a valid keyword return true
|
|
const bool valid = getKeyword(keyword, keyToken, is);
|
|
|
|
// Can accept a list of entries too
|
|
if
|
|
(
|
|
keyToken.isLabel()
|
|
|| (keyToken.isPunctuation() && keyToken.pToken() == token::BEGIN_LIST)
|
|
)
|
|
{
|
|
is.putBack(keyToken);
|
|
return parentDict.add
|
|
(
|
|
new dictionaryListEntry(parentDict, is),
|
|
false
|
|
);
|
|
}
|
|
|
|
if (!valid)
|
|
{
|
|
// Error processing for invalid or unexpected input
|
|
|
|
// Do some more checking
|
|
if (keyToken == token::END_BLOCK)
|
|
{
|
|
if (token::END_BLOCK != endChar)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Unexpected '}' while reading dictionary entry"
|
|
<< exit(FatalIOError);
|
|
}
|
|
return false;
|
|
}
|
|
if (is.eof())
|
|
{
|
|
if (endChar)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Unexpected EOF while reading dictionary entry"
|
|
<< exit(FatalIOError);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
if (endChar)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Found " << keyToken
|
|
<< " but expected " << char(endChar)
|
|
<< exit(FatalIOError);
|
|
}
|
|
else
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Found " << keyToken
|
|
<< " but expected EOF, or perhaps a '}' char"
|
|
<< exit(FatalIOError);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
if (keyword[0] == '#')
|
|
{
|
|
// Function entry - #function
|
|
|
|
if (disableFunctionEntries)
|
|
{
|
|
return parentDict.add
|
|
(
|
|
new functionEntry
|
|
(
|
|
keyword,
|
|
parentDict,
|
|
is
|
|
),
|
|
false
|
|
);
|
|
}
|
|
|
|
const word functionName(keyword.substr(1), false);
|
|
return functionEntry::execute(functionName, parentDict, is);
|
|
}
|
|
|
|
|
|
if (!disableFunctionEntries && keyword[0] == '$')
|
|
{
|
|
// Substitution entry - $variable
|
|
|
|
token nextToken(is);
|
|
is.putBack(nextToken);
|
|
|
|
if (keyword.size() > 2 && keyword[1] == token::BEGIN_BLOCK)
|
|
{
|
|
// Recursive substitution mode.
|
|
// Content between {} is replaced with expansion.
|
|
// Then let standard variable expansion deal with rest.
|
|
string expanded = keyword.substr(2, keyword.size()-3);
|
|
|
|
// Substitute dictionary and environment variables.
|
|
// Do not allow empty substitutions.
|
|
stringOps::inplaceExpand(expanded, parentDict, true, false);
|
|
|
|
// Restore the '$' prefix.
|
|
// Use replace since operator= is private
|
|
keyword.std::string::replace(1, keyword.size()-1, expanded);
|
|
}
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
const word varName = keyword.substr(1);
|
|
|
|
// Lookup the variable name in the given dictionary
|
|
const auto finder = parentDict.csearchScoped(varName, true, true);
|
|
|
|
if (finder.found())
|
|
{
|
|
// Read as primitiveEntry
|
|
const keyType newKeyword(finder.ptr()->stream());
|
|
|
|
return parentDict.add
|
|
(
|
|
new dictionaryEntry(newKeyword, parentDict, is),
|
|
false
|
|
);
|
|
}
|
|
|
|
FatalIOErrorInFunction(is)
|
|
<< "Attempt to use undefined variable " << varName
|
|
<< " as keyword"
|
|
<< exit(FatalIOError);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Deal with duplicate entries (at least partially)
|
|
const bool mergeEntry =
|
|
(
|
|
mode == inputMode::MERGE
|
|
|| mode == inputMode::OVERWRITE
|
|
);
|
|
|
|
parentDict.substituteScopedKeyword(keyword, mergeEntry);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// Normal or scoped entry
|
|
{
|
|
token nextToken(is);
|
|
is.putBack(nextToken);
|
|
|
|
if (nextToken == token::END_LIST)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "Unexpected token encountered for "
|
|
<< keyword << " - " << nextToken.info()
|
|
<< exit(FatalIOError);
|
|
return false;
|
|
}
|
|
|
|
const bool scoped =
|
|
(
|
|
!disableFunctionEntries
|
|
&& (keyword.find('/') != string::npos)
|
|
);
|
|
|
|
// See (using exact match) if entry already present
|
|
auto finder =
|
|
(
|
|
scoped
|
|
? parentDict.searchScoped(keyword, false, false)
|
|
: parentDict.search(keyword, false, false)
|
|
);
|
|
|
|
// How to manage duplicate entries
|
|
bool mergeEntry = false;
|
|
|
|
if (finder.found())
|
|
{
|
|
// Use keyword from the found entry (ie, eliminate scoping chars)
|
|
const keyType key = finder.ref().keyword();
|
|
|
|
if (mode == inputMode::PROTECT || keyword == "FoamFile")
|
|
{
|
|
// Read and discard if existing element should be protected,
|
|
// or would potentially alter the "FoamFile" header.
|
|
|
|
// Disable function/variable expansion to avoid side-effects
|
|
const int oldFlag = entry::disableFunctionEntries;
|
|
entry::disableFunctionEntries = 1;
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
dictionaryEntry dummy("dummy", finder.context(), is);
|
|
}
|
|
else
|
|
{
|
|
primitiveEntry dummy("dummy", finder.context(), is);
|
|
}
|
|
|
|
entry::disableFunctionEntries = oldFlag;
|
|
return true;
|
|
}
|
|
|
|
if (mode == inputMode::ERROR)
|
|
{
|
|
FatalIOErrorInFunction(is)
|
|
<< "duplicate entry: " << key
|
|
<< exit(FatalIOError);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (mode == inputMode::MERGE)
|
|
{
|
|
mergeEntry = true;
|
|
}
|
|
else if (mode == inputMode::OVERWRITE)
|
|
{
|
|
// Clear existing dictionary so merge acts like overwrite
|
|
if (finder.isDict())
|
|
{
|
|
finder.dict().clear();
|
|
}
|
|
mergeEntry = true;
|
|
}
|
|
|
|
// Merge/overwrite data entry
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
return finder.context().add
|
|
(
|
|
new dictionaryEntry(key, finder.context(), is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return finder.context().add
|
|
(
|
|
new primitiveEntry(key, finder.context(), is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
}
|
|
else if (scoped)
|
|
{
|
|
// A slash-scoped entry - did not previously exist
|
|
|
|
string fullPath(keyword);
|
|
fileName::clean(fullPath);
|
|
|
|
// Get or create the dictionary-path.
|
|
// fileName::path == dictionary-path
|
|
dictionary* subDictPtr =
|
|
parentDict.makeScopedDictPtr
|
|
(
|
|
fileName::path(fullPath)
|
|
);
|
|
|
|
if (subDictPtr)
|
|
{
|
|
// fileName::name == keyword-name
|
|
string keyName = fileName::name(fullPath);
|
|
keyType key;
|
|
|
|
// Patterns allowed for the final element.
|
|
// - use if key name begins with a (single|double) quote
|
|
|
|
if (keyName.find_first_of("\"'") == 0)
|
|
{
|
|
// Begins with a quote - treat as pattern
|
|
key = keyType(string::validate<keyType>(keyName), true);
|
|
}
|
|
else
|
|
{
|
|
// Treat as a word
|
|
key = word::validate(keyName, false);
|
|
}
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
return subDictPtr->add
|
|
(
|
|
new dictionaryEntry(key, *subDictPtr, is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return subDictPtr->add
|
|
(
|
|
new primitiveEntry(key, *subDictPtr, is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
}
|
|
|
|
// Some error finding/creating intermediate dictionaries
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// A non-scoped entry - did not previously exist
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
return parentDict.add
|
|
(
|
|
new dictionaryEntry(keyword, parentDict, is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
else
|
|
{
|
|
return parentDict.add
|
|
(
|
|
new primitiveEntry(keyword, parentDict, is),
|
|
mergeEntry
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Foam::autoPtr<Foam::entry> Foam::entry::New(Istream& is)
|
|
{
|
|
is.fatalCheck(FUNCTION_NAME);
|
|
|
|
autoPtr<entry> ptr;
|
|
|
|
// Get the next keyword and if invalid return false
|
|
keyType keyword;
|
|
if (getKeyword(keyword, is))
|
|
{
|
|
// Keyword starts entry ...
|
|
token nextToken(is);
|
|
is.putBack(nextToken);
|
|
|
|
if (nextToken == token::BEGIN_BLOCK)
|
|
{
|
|
// A sub-dictionary
|
|
ptr.reset(new dictionaryEntry(keyword, dictionary::null, is));
|
|
}
|
|
else
|
|
{
|
|
ptr.reset(new primitiveEntry(keyword, is));
|
|
}
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
|
|
// * * * * * * * * * * * * * Ostream operator * * * * * * * * * * * * * * * //
|
|
|
|
Foam::Ostream& Foam::operator<<(Ostream& os, const entry& e)
|
|
{
|
|
e.write(os);
|
|
return os;
|
|
}
|
|
|
|
|
|
// ************************************************************************* //
|