2020#undef __STRICT_ANSI__
2121#endif
2222
23+ #define LOG_EMACS_MARKER
24+
2325#include " path.h"
2426#include " utils.h"
2527
2628#include < algorithm>
29+ #include < cstdio>
2730#include < cstdlib>
31+ #ifdef LOG_EMACS_MARKER
32+ #include < iostream>
33+ #endif
2834#include < sys/stat.h>
2935#include < unordered_set>
3036#include < utility>
@@ -235,7 +241,7 @@ bool Path::isCPP(const std::string &path)
235241bool Path::acceptFile (const std::string &path, const std::set<std::string> &extra)
236242{
237243 bool header = false ;
238- return (identify (path, &header) != Standards::Language::None && !header) || extra.find (getFilenameExtension (path)) != extra.end ();
244+ return (identify (path, false , &header) != Standards::Language::None && !header) || extra.find (getFilenameExtension (path)) != extra.end ();
239245}
240246
241247// cppcheck-suppress unusedFunction
@@ -245,13 +251,98 @@ bool Path::isHeader(const std::string &path)
245251 return startsWith (extension, " .h" );
246252}
247253
248- Standards::Language Path::identify (const std::string &path, bool *header)
254+ static bool hasEmacsCppMarker (const char * path)
255+ {
256+ // TODO: identify is called three times for each file
257+ // Preprocessor::loadFiles() -> createDUI()
258+ // Preprocessor::preprocess() -> createDUI()
259+ // TokenList::createTokens() -> TokenList::determineCppC()
260+ #ifdef LOG_EMACS_MARKER
261+ std::cout << path << ' \n ' ;
262+ #endif
263+
264+ FILE *fp = fopen (path, " rt" );
265+ if (!fp)
266+ return false ;
267+ std::string buf (128 , ' \0 ' );
268+ {
269+ // TODO: read the whole first line only
270+ const char * const res = fgets (const_cast <char *>(buf.data ()), buf.size (), fp);
271+ fclose (fp);
272+ fp = nullptr ;
273+ if (!res)
274+ return false ; // failed to read file
275+ }
276+ // TODO: replace with regular expression
277+ const auto pos1 = buf.find (" -*-" );
278+ if (pos1 == std::string::npos)
279+ return false ; // no start marker
280+ const auto pos_nl = buf.find_first_of (" \r\n " );
281+ if (pos_nl != std::string::npos && (pos_nl < pos1)) {
282+ #ifdef LOG_EMACS_MARKER
283+ std::cout << path << " - Emacs marker not on the first line" << ' \n ' ;
284+ #endif
285+ return false ; // not on first line
286+ }
287+ const auto pos2 = buf.find (" -*-" , pos1 + 3 );
288+ // TODO: make sure we have read the whole line before bailing out
289+ if (pos2 == std::string::npos) {
290+ #ifdef LOG_EMACS_MARKER
291+ std::cout << path << " - Emacs marker not terminated" << ' \n ' ;
292+ #endif
293+ return false ; // no end marker
294+ }
295+ #ifdef LOG_EMACS_MARKER
296+ std::cout << " Emacs marker: '" << buf.substr (pos1, (pos2 + 3 ) - pos1) << " '" << ' \n ' ;
297+ #endif
298+ const std::string buf_trim = trim (buf); // trim whitespaces
299+ if (buf_trim[0 ] != ' /' || buf_trim[1 ] != ' /' ) {
300+ #ifdef LOG_EMACS_MARKER
301+ std::cout << path << " - Emacs marker not in a comment: '" << buf.substr (pos1, (pos2 + 3 ) - pos1) << " '" << ' \n ' ;
302+ #endif
303+ return false ; // not a comment
304+ }
305+
306+ // there are more variations with lowercase and no whitespaces
307+ // -*- C++ -*-
308+ // -*- Mode: C++; -*-
309+ // -*- Mode: C++; c-basic-offset: 8 -*-
310+ std::string marker = trim (buf.substr (pos1 + 3 , pos2 - pos1 - 3 ), " ;" );
311+ // cut off additional attributes
312+ const auto pos_semi = marker.find (' ;' );
313+ if (pos_semi != std::string::npos)
314+ marker.resize (pos_semi);
315+ findAndReplace (marker, " mode:" , " " );
316+ findAndReplace (marker, " Mode:" , " " );
317+ marker = trim (marker);
318+ if (marker == " C++" || marker == " c++" )
319+ return true ; // C++ marker found
320+
321+ // if (marker == "C" || marker == "c")
322+ // return false;
323+ #ifdef LOG_EMACS_MARKER
324+ std::cout << path << " - unmatched Emacs marker: '" << marker << " '" << ' \n ' ;
325+ #endif
326+
327+ return false ; // marker is not a C++ one
328+ }
329+
330+ Standards::Language Path::identify (const std::string &path, bool cppHeaderProbe, bool *header)
249331{
250332 // cppcheck-suppress uninitvar - TODO: FP
251333 if (header)
252334 *header = false ;
253335
254336 std::string ext = getFilenameExtension (path);
337+ // standard library headers have no extension
338+ if (cppHeaderProbe && ext.empty ()) {
339+ if (hasEmacsCppMarker (path.c_str ())) {
340+ if (header)
341+ *header = true ;
342+ return Standards::Language::CPP;
343+ }
344+ return Standards::Language::None;
345+ }
255346 if (ext == " .C" )
256347 return Standards::Language::CPP;
257348 if (c_src_exts.find (ext) != c_src_exts.end ())
@@ -262,7 +353,9 @@ Standards::Language Path::identify(const std::string &path, bool *header)
262353 if (ext == " .h" ) {
263354 if (header)
264355 *header = true ;
265- return Standards::Language::C; // treat as C for now
356+ if (cppHeaderProbe && hasEmacsCppMarker (path.c_str ()))
357+ return Standards::Language::CPP;
358+ return Standards::Language::C;
266359 }
267360 if (cpp_src_exts.find (ext) != cpp_src_exts.end ())
268361 return Standards::Language::CPP;
@@ -277,7 +370,7 @@ Standards::Language Path::identify(const std::string &path, bool *header)
277370bool Path::isHeader2 (const std::string &path)
278371{
279372 bool header;
280- (void )Path:: identify (path, &header);
373+ (void )identify (path, false , &header);
281374 return header;
282375}
283376
0 commit comments