/* * c++check - c/c++ syntax checking * Copyright (C) 2007 Daniel Marjamäki * * This program 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. * * 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 #include #include #ifdef __BORLANDC__ #include #endif /** * Get all possible configurations. By looking at the ifdefs and ifndefs in filedata */ static std::list getcfgs( const std::string &filedata ); /** * Get preprocessed code for a given configuration */ static std::string getcode(const std::string &filedata, std::string cfg); /** * Extract the code for each configuration * \param istr The (file/string) stream to read from. * \param result The map that will get the results */ void preprocess(std::istream &istr, std::map &result, const std::string filename) { // Get filedata from stream.. bool ignoreSpace = true; std::ostringstream code; for (char ch = (char)istr.get(); istr.good(); ch = (char)istr.get()) { if ( ch < 0 ) { // Bad content.. errout << "[" << filename << "] Bad character found: " << int((unsigned char)ch) << std::endl; result.clear(); return; } // Replace assorted special chars with spaces.. if ( ch < 0 ) ch = ' '; if ( (ch != '\n') && (isspace(ch) || iscntrl(ch)) ) ch = ' '; // Skip spaces after ' ' and after '#' if ( ch == ' ' && ignoreSpace ) continue; ignoreSpace = bool(ch == ' ' || ch == '#' || ch == '/'); // Remove comments.. if ( ch == '/' ) { char chNext = (char)istr.get(); if ( chNext == '/' ) { while (istr.good() && ch!='\n') ch = (char)istr.get(); code << "\n"; } else if ( chNext == '*' ) { char chPrev = 0; while (istr.good() && (chPrev!='*' || ch!='/')) { chPrev = ch; ch = (char)istr.get(); if (ch == '\n') code << "\n"; } } else { code << std::string(1,ch) << std::string(1,chNext); } } // String constants.. else if ( ch == '\"' ) { code << "\""; do { ch = (char)istr.get(); code << std::string(1,ch); if ( ch == '\\' ) { ch = (char)istr.get(); code << std::string(1,ch); } } while ( istr.good() && ch != '\"' ); } // char constants.. else if ( ch == '\'' ) { code << "\'"; ch = (char)istr.get(); code << std::string(1,ch); if ( ch == '\\' ) { ch = (char)istr.get(); code << std::string(1,ch); } ch = (char)istr.get(); code << "\'"; } // Just some code.. else { code << std::string(1, ch); } } std::string codestr( code.str() ); // Remove all indentation.. while ( ! codestr.empty() && codestr[0] == ' ' ) codestr.erase(0, 1); while ( codestr.find("\n ") != std::string::npos ) codestr.erase( 1 + codestr.find("\n "), 1 ); // Remove all trailing spaces.. while ( codestr.find(" \n") != std::string::npos ) codestr.erase( codestr.find(" \n"), 1 ); // Get all possible configurations.. std::list cfgs = getcfgs( codestr ); // Extract the code for each possible configuration.. result.clear(); for ( std::list::const_iterator it = cfgs.begin(); it != cfgs.end(); ++it ) { result[ *it ] = getcode( codestr, *it ); } } // Get the DEF in this line: "#ifdef DEF" static std::string getdef(std::string line, bool def) { // If def is true, the line must start with "#ifdef" if ( def && line.find("#ifdef ") != 0 && line.find("#if ") != 0 && line.find("#elif ") != 0 ) { return ""; } // If def is false, the line must start with "#ifndef" if ( !def && line.find("#ifndef ") != 0 ) { return ""; } // Remove the "#ifdef" or "#ifndef" line.erase( 0, line.find(" ") ); // Remove all spaces. while ( line.find(" ") != std::string::npos ) line.erase( line.find(" "), 1 ); // The remaining string is our result. return line; } static std::list getcfgs( const std::string &filedata ) { std::list ret; ret.push_back(""); std::list deflist; std::istringstream istr(filedata); std::string line; while ( getline(istr, line) ) { std::string def = getdef(line, true) + getdef(line, false); if (!def.empty()) { if ( ! deflist.empty() && line.find("#elif ") == 0 ) deflist.pop_back(); deflist.push_back(def); def = ""; for ( std::list::const_iterator it = deflist.begin(); it != deflist.end(); ++it) { if ( *it == "0" ) break; if ( *it == "1" ) continue; if ( ! def.empty() ) def += ";"; def += *it; } if (std::find(ret.begin(), ret.end(), def) == ret.end()) ret.push_back( def ); } if ( line.find("#else") == 0 && ! deflist.empty() ) { std::string def( ( deflist.back() == "1" ) ? "0" : "1" ); deflist.pop_back(); deflist.push_back( def ); } if ( line.find("#endif") == 0 && ! deflist.empty() ) deflist.pop_back(); } return ret; } static bool match_cfg_def( std::string cfg, const std::string &def ) { if ( def == "0" ) return false; if ( def == "1" ) return true; if ( cfg.empty() ) return false; while ( ! cfg.empty() ) { if ( cfg.find(";") == std::string::npos ) return bool(cfg == def); std::string _cfg = cfg.substr( 0, cfg.find(";") ); if ( _cfg == def ) return true; cfg.erase( 0, cfg.find(";") + 1 ); } return false; } static std::string getcode(const std::string &filedata, std::string cfg) { std::ostringstream ret; std::list matching_ifdef; std::list matched_ifdef; std::istringstream istr(filedata); std::string line; while ( getline(istr, line) ) { std::string def = getdef( line, true ); std::string ndef = getdef( line, false ); if ( line.find("#elif ") == 0 ) { if ( matched_ifdef.back() ) { matching_ifdef.back() = false; } else { if ( match_cfg_def(cfg, def) ) { matching_ifdef.back() = true; matched_ifdef.back() = true; } } } else if ( ! def.empty() ) { matching_ifdef.push_back( match_cfg_def(cfg, def) ); matched_ifdef.push_back( matching_ifdef.back() ); } else if ( ! ndef.empty() ) { matching_ifdef.push_back( ! match_cfg_def(cfg, ndef) ); matched_ifdef.push_back( matching_ifdef.back() ); } else if ( line == "#else" ) { if ( ! matched_ifdef.empty() ) matching_ifdef.back() = ! matched_ifdef.back(); } else if ( line == "#endif" ) { if ( ! matched_ifdef.empty() ) matched_ifdef.pop_back(); if ( ! matching_ifdef.empty() ) matching_ifdef.pop_back(); } bool match = true; for ( std::list::const_iterator it = matching_ifdef.begin(); it != matching_ifdef.end(); ++it ) match &= bool(*it); if ( ! match ) line = ""; if ( line.find("#if") == 0 || line.find("#else") == 0 || line.find("#elif") == 0 || line.find("#endif") == 0 ) line = ""; ret << line << "\n"; } return ret.str(); }