/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 this program; if not, see . =========================================================================== */ // Filename:- genericparser2.cpp #include "common_headers.h" #include "genericparser2.h" #ifdef _JK2EXE #include "../qcommon/qcommon.h" #endif #include #include static void skipWhitespace( gsl::cstring_view& text, const bool allowLineBreaks ) { gsl::cstring_view::iterator whitespaceEnd = text.begin(); while( whitespaceEnd != text.end() // No EOF && std::isspace( *whitespaceEnd ) // No End of Whitespace && ( allowLineBreaks || *whitespaceEnd != '\n' ) ) // No unwanted newline { ++whitespaceEnd; } text = { whitespaceEnd, text.end() }; } static void skipWhitespaceAndComments( gsl::cstring_view& text, const bool allowLineBreaks ) { skipWhitespace( text, allowLineBreaks ); // skip single line comment if( text.size() >= 2 && text[ 0 ] == '/' && text[ 1 ] == '/' ) { auto commentEnd = std::find( text.begin() + 2, text.end(), '\n' ); if( commentEnd == text.end() ) { text = { text.end(), text.end() }; return; } else { text = { commentEnd, text.end() }; skipWhitespaceAndComments( text, allowLineBreaks ); return; } } // skip multi line comments if( text.size() >= 2 && text[ 0 ] == '/' && text[ 1 ] == '*' ) { static const std::array< char, 2 > endStr{ '*', '/' }; auto commentEnd = std::search( text.begin(), text.end(), endStr.begin(), endStr.end() ); if( commentEnd == text.end() ) { text = { text.end(), text.end() }; return; } else { text = { commentEnd + endStr.size(), text.end() }; skipWhitespace( text, allowLineBreaks ); return; } } // found start of token return; } static gsl::cstring_view removeTrailingWhitespace( const gsl::cstring_view& text ) { return{ text.begin(), std::find_if_not( std::reverse_iterator< const char *>( text.end() ), std::reverse_iterator< const char* >( text.begin() ), static_cast< int( *)( int ) >( std::isspace ) ).base() }; } /** Skips whitespace (including lineBreaks, if desired) & comments, then reads one token. A token can be: - a string ("" delimited; ignores readToEOL) - whitespace-delimited (if readToEOL == false) - EOL- or comment-delimited (if readToEOL == true); i.e. reads to end of line or the first // or /* @param text adjusted to start beyond the read token */ static gsl::cstring_view GetToken( gsl::cstring_view& text, bool allowLineBreaks, bool readToEOL = false ) { skipWhitespaceAndComments( text, allowLineBreaks ); // EOF if( text.empty() ) { return{}; } // string. ignores readToEOL. if( text[ 0 ] == '"' ) { // there are no escapes, string just ends at the next " auto tokenEnd = std::find( text.begin() + 1, text.end(), '"' ); if( tokenEnd == text.end() ) { gsl::cstring_view token = { text.begin() + 1, text.end() }; text = { text.end(), text.end() }; return token; } else { gsl::cstring_view token = { text.begin() + 1, tokenEnd }; text = { tokenEnd + 1, text.end() }; return token; } } else if( readToEOL ) { // find the first of '\n', "//" or "/*"; that's end of token auto tokenEnd = std::find( text.begin(), text.end(), '\n' ); static const std::array< char, 2 > commentPatterns[]{ { { '/', '*' } }, { { '/', '/' } } }; for( auto& pattern : commentPatterns ) { tokenEnd = std::min( tokenEnd, std::search( text.begin(), tokenEnd, pattern.begin(), pattern.end() ) ); } gsl::cstring_view token{ text.begin(), tokenEnd }; text = { tokenEnd, text.end() }; return removeTrailingWhitespace( token ); } else { // consume until first whitespace (if allowLineBreaks == false, that may be text.begin(); in that case token is empty.) auto tokenEnd = std::find_if( text.begin(), text.end(), static_cast< int( *)( int ) >( std::isspace ) ); gsl::cstring_view token{ text.begin(), tokenEnd }; text = { tokenEnd, text.end() }; return token; } } CGPProperty::CGPProperty( gsl::cstring_view initKey, gsl::cstring_view initValue ) : mKey( initKey ) { if( !initValue.empty() ) { mValues.push_back( initValue ); } } void CGPProperty::AddValue( gsl::cstring_view newValue ) { mValues.push_back( newValue ); } CGPGroup::CGPGroup( const gsl::cstring_view& initName ) : mName( initName ) { } bool CGPGroup::Parse( gsl::cstring_view& data, const bool topLevel ) { while( true ) { gsl::cstring_view token = GetToken( data, true ); if( token.empty() ) { if ( topLevel ) { // top level parse; there was no opening "{", so there should be no closing one either. return true; } else { // end of data - error! return false; } } else if( token == CSTRING_VIEW( "}" ) ) { if( topLevel ) { // top-level group; there was no opening "{" so there should be no closing one, either. return false; } else { // ending brace for this group return true; } } gsl::cstring_view lastToken = token; // read ahead to see what we are doing token = GetToken( data, true, true ); if( token == CSTRING_VIEW( "{" ) ) { // new sub group mSubGroups.emplace_back( lastToken ); if( !mSubGroups.back().Parse( data, false ) ) { return false; } } else if( token == CSTRING_VIEW( "[" ) ) { // new list mProperties.emplace_back( lastToken ); CGPProperty& list = mProperties.back(); while( true ) { token = GetToken( data, true, true ); if( token.empty() ) { return false; } if( token == CSTRING_VIEW( "]" ) ) { break; } list.AddValue( token ); } } else { // new value mProperties.emplace_back( lastToken, token ); } } } bool CGenericParser2::Parse( gsl::czstring filename ) { Clear(); mFileContent = FS::ReadFile( filename ); if( !mFileContent.valid() ) { return false; } auto view = mFileContent.view(); return mTopLevel.Parse( view ); } void CGenericParser2::Clear() NOEXCEPT { mTopLevel.Clear(); } //////////////////// eof /////////////////////