Use of stat instead of d_type from dirent struct.
[ohcount] / src / detector.c
1 // detector.c written by Mitchell Foral. mitchell<att>caladbolg.net.
2 // See COPYING for license information.
3
4 #include <ctype.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <unistd.h>
9
10 #include "detector.h"
11 #include "languages.h"
12 #include "log.h"
13
14 #include "hash/cppheader_hash.h"
15 #include "hash/disambiguatefunc_hash.h"
16 #include "hash/extension_hash.h"
17 #include "hash/filename_hash.h"
18
19 #define ISBINARY(x) (x[0] == '\1')
20 #define ISAMBIGUOUS(x) (x[0] == '\2')
21 #define DISAMBIGUATEWHAT(x) &x[1]
22
23 const char *ohcount_detect_language(SourceFile *sourcefile) {
24   const char *language = NULL;
25   char *p, *pe;
26   int length;
27
28   // Attempt to detect using Emacs mode line (/^-\*-\s*mode[\s:]*\w/i).
29   char line[81] = { '\0' }, buf[81];
30   p = ohcount_sourcefile_get_contents(sourcefile);
31   pe = p;
32   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
33   while (pe < eof) {
34     // Get the contents of the first line.
35     while (pe < eof && *pe != '\r' && *pe != '\n') pe++;
36     length = (pe - p <= sizeof(line)) ? pe - p : sizeof(line);
37     strncpy(line, p, length);
38     line[length] = '\0';
39     if (*line == '#' && *(line + 1) == '!') {
40       // First line was sh-bang; loop to get contents of second line.
41       while (*pe == '\r' || *pe == '\n') pe++;
42       p = pe;
43     } else break;
44   }
45   p = strstr(line, "-*-");
46   if (p) {
47     p += 3;
48     while (*p == ' ' || *p == '\t') p++;
49     // detect "mode" (any capitalization)
50     if (strncasecmp(p, "mode", 4) == 0) {
51       p += 4;
52       while (*p == ' ' || *p == '\t' || *p == ':') p++;
53     }
54     pe = p;
55     while (!isspace(*pe) && *pe != ';' && pe != strstr(pe, "-*-")) pe++;
56     length = pe - p;
57     strncpy(buf, p, length);
58     buf[length] = '\0';
59     // First try it with the language name.
60     struct LanguageMap *rl = ohcount_hash_language_from_name(buf, length);
61     if (rl) language = rl->name;
62     if(!language) {
63       // Then try it with the extension table.
64       struct ExtensionMap *re = ohcount_hash_language_from_ext(buf, length);
65       if (re) language = re->value;
66     }
67     if (!language) {
68       // Try the lower-case version of this modeline.
69       for (pe = buf; pe < buf+length; pe++) *pe = tolower(*pe);
70       // First try it with the language name.
71       rl = ohcount_hash_language_from_name(buf, length);
72       if (rl) language = rl->name;
73     }
74     if (!language) {
75       // Then try it with the extension table.
76       struct ExtensionMap *re = ohcount_hash_language_from_ext(buf, length);
77       if (re) language = re->value;
78     }
79   }
80
81   // Attempt to detect based on file extension.
82   if(!language) {
83       length = strlen(sourcefile->ext);
84       struct ExtensionMap *re = ohcount_hash_language_from_ext(sourcefile->ext,
85                                                                length);
86       if (re) language = re->value;
87     if (!language) {
88       // Try the lower-case version of this extension.
89       char lowerext[length + 1];
90       strncpy(lowerext, sourcefile->ext, length);
91       lowerext[length] = '\0';
92       for (p = lowerext; p < lowerext + length; p++) *p = tolower(*p);
93       struct ExtensionMap *re = ohcount_hash_language_from_ext(lowerext, length);
94       if (re) language = re->value;
95     }
96   }
97
98   // Attempt to detect based on filename.
99   if(!language) {
100     length = strlen(sourcefile->filename);
101     struct FilenameMap *rf =
102       ohcount_hash_language_from_filename(sourcefile->filename, length);
103     if (rf) language = rf->value;
104   }
105
106   // Attempt to detect based on Unix 'file' command.
107   if(!language) {
108     int tmpfile = 0;
109     char *path = sourcefile->filepath;
110     if (sourcefile->diskpath)
111       path = sourcefile->diskpath;
112     if (access(path, F_OK) != 0) { // create temporary file
113       path = malloc(21);
114       strncpy(path, "/tmp/ohcount_XXXXXXX", 20);
115       *(path + 21) = '\0';
116       int fd = mkstemp(path);
117       char *contents = ohcount_sourcefile_get_contents(sourcefile);
118       log_it("contents:");
119       log_it(contents);
120       length = contents ? strlen(contents) : 0;
121       write(fd, contents, length);
122       close(fd);
123       tmpfile = 1;
124     }
125     char command[strlen(path) + 11];
126     sprintf(command, "file -b '%s'", path);
127     FILE *f = popen(command, "r");
128     if (f) {
129       fgets(line, sizeof(line), f);
130       char *eol = line + strlen(line);
131       for (p = line; p < eol; p++) *p = tolower(*p);
132       p = strstr(line, "script text");
133       if (p && p == line) { // /^script text(?: executable)? for \w/
134         p = strstr(line, "for ");
135         if (p) {
136           p += 4;
137           pe = p;
138           while (isalnum(*pe)) pe++;
139           length = pe - p;
140           strncpy(buf, p, length);
141           buf[length] = '\0';
142           struct LanguageMap *rl = ohcount_hash_language_from_name(buf, length);
143           if (rl) language = rl->name;
144         }
145       } else if (p) { // /(\w+)(?: -\w+)* script text/
146         do {
147           p--;
148           pe = p;
149           while (*p == ' ') p--;
150           while (p != line && isalnum(*(p - 1))) p--;
151           if (p != line && *(p - 1) == '-') p--;
152         } while (*p == '-'); // Skip over any switches.
153         length = pe - p;
154         strncpy(buf, p, length);
155         buf[length] = '\0';
156         struct LanguageMap *rl = ohcount_hash_language_from_name(buf, length);
157         if (rl) language = rl->name;
158       } else if (strstr(line, "xml")) language = LANG_XML;
159       pclose(f);
160       if (tmpfile) {
161         remove(path);
162         free(path);
163       }
164     }
165   }
166   if (language) {
167     if (ISAMBIGUOUS(language)) {
168       // Call the appropriate function for disambiguation.
169       length = strlen(DISAMBIGUATEWHAT(language));
170       struct DisambiguateFuncsMap *rd =
171         ohcount_hash_disambiguate_func_from_id(DISAMBIGUATEWHAT(language),
172                                                length);
173       if (rd) language = rd->value(sourcefile);
174     } else language = ISBINARY(language) ? NULL : language;
175   }
176   return language;
177 }
178
179 const char *disambiguate_aspx(SourceFile *sourcefile) {
180   char *p = ohcount_sourcefile_get_contents(sourcefile);
181   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
182   for (; p < eof; p++) {
183     // /<%@\s*Page[^>]+Language="VB"[^>]+%>/
184     p = strstr(p, "<%@");
185     if (!p)
186                         break;
187     char *pe = strstr(p, "%>");
188     if (p && pe) {
189       p += 3;
190       const int length = pe - p;
191       char buf[length];
192       strncpy(buf, p, length);
193       buf[length] = '\0';
194       char *eol = buf + strlen(buf);
195       for (p = buf; p < eol; p++) *p = tolower(*p);
196       p = buf;
197       while (*p == ' ' || *p == '\t') p++;
198       if (strncmp(p, "page", 4) == 0) {
199         p += 4;
200         if (strstr(p, "language=\"vb\""))
201           return LANG_VB_ASPX;
202       }
203     }
204   }
205   return LANG_CS_ASPX;
206 }
207
208 const char *disambiguate_b(SourceFile *sourcefile) {
209   char *p = ohcount_sourcefile_get_contents(sourcefile);
210   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
211   while (p < eof) {
212     // /(implement[ \t])|(include[ \t]+"[^"]*";)|
213     //  ((return|break|continue).*;|(pick|case).*\{)/
214     if (strncmp(p, "implement", 9) == 0 &&
215         (*(p + 9) == ' ' || *(p + 9) == '\t'))
216       return LANG_LIMBO;
217     else if (strncmp(p, "include", 7) == 0 &&
218         (*(p + 7) == ' ' || *(p + 7) == '\t')) {
219       p += 7;
220       while (*p == ' ' || *p == '\t') p++;
221       if (*p == '"') {
222         while (*p != '"' && p < eof) p++;
223         if (*p == '"' && *(p + 1) == ';')
224           return LANG_LIMBO;
225       }
226     } else if (strncmp(p, "return", 6) == 0 ||
227                strncmp(p, "break", 5) == 0 ||
228                strncmp(p, "continue", 8) == 0) {
229       if (strstr(p, ";"))
230         return LANG_LIMBO;
231     } else if (strncmp(p, "pick", 4) == 0 ||
232                strncmp(p, "case", 4) == 0) {
233       if (strstr(p, "{"))
234         return LANG_LIMBO;
235     }
236     p++;
237   }
238   return disambiguate_basic(sourcefile);
239 }
240
241 const char *disambiguate_basic(SourceFile *sourcefile) {
242   char *p, *pe;
243   int length;
244
245   // Attempt to detect based on file contents.
246   char line[81];
247   p = ohcount_sourcefile_get_contents(sourcefile);
248   pe = p;
249   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
250   while (pe < eof) {
251     // Get a line at a time.
252     while (pe < eof && *pe != '\r' && *pe != '\n') pe++;
253     length = (pe - p <= sizeof(line)) ? pe - p : sizeof(line);
254     strncpy(line, p, length);
255     line[length] = '\0';
256     char *line_end = pe;
257
258     p = line;
259     if (isdigit(*p)) {
260       // /^\d+\s+\w/
261       p++;
262       while (isdigit(*p)) p++;
263       if (*p == ' ' || *p == '\t') {
264         p++;
265         while (*p == ' ' || *p == '\t') p++;
266         if (isalnum(*p))
267           return LANG_CLASSIC_BASIC;
268       }
269     }
270
271     // Next line.
272     pe = line_end;
273     while (*pe == '\r' || *pe == '\n') pe++;
274     p = pe;
275   }
276
277   // Attempt to detect from associated VB files in file context.
278   char **filenames = sourcefile->filenames;
279   if (filenames) {
280     int i;
281     for (i = 0; filenames[i] != NULL; i++) {
282       pe = filenames[i] + strlen(filenames[i]);
283       p = pe;
284       while (p > filenames[i] && *(p - 1) != '.') p--;
285       length = pe - p;
286       if (length == 3 &&
287           (strncmp(p, "frm", length) == 0 ||
288            strncmp(p, "frx", length) == 0 ||
289            strncmp(p, "vba", length) == 0 ||
290            strncmp(p, "vbp", length) == 0 ||
291            strncmp(p, "vbs", length) == 0)) {
292         return LANG_VISUALBASIC;
293       }
294     }
295   }
296
297   return LANG_STRUCTURED_BASIC;
298 }
299
300 const char *disambiguate_cs(SourceFile *sourcefile) {
301   // Attempt to detect based on file contents.
302         char *contents = ohcount_sourcefile_get_contents(sourcefile);
303   if (contents && strstr(contents, "<?cs"))
304     return LANG_CLEARSILVER_TEMPLATE;
305   else
306     return LANG_CSHARP;
307 }
308
309 const char *disambiguate_fortran(SourceFile *sourcefile) {
310   char *p, *pe;
311
312   p = ohcount_sourcefile_get_contents(sourcefile);
313   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
314   while (p < eof) {
315     if (*p == ' ' && p + 5 < eof) {
316       int i;
317       for (i = 1; i <= 5; i++)
318         if (!isdigit(*(p + i)) && *(p + i) != ' ')
319           return LANG_FORTRANFIXED; // definately not f77
320       // Possibly fixed (doesn't match /^\s*\d+\s*$/).
321       pe = p;
322       while (*pe == ' ' || *pe == '\t') pe++;
323       if (pe - p <= 5) {
324         if (!isdigit(*pe))
325           return LANG_FORTRANFIXED;
326         while (isdigit(*pe)) pe++;
327         while (*pe == ' ' || *pe == '\t') pe++;
328         if (*pe != '\r' && *pe != '\n' && pe - p == 5)
329           return LANG_FORTRANFIXED;
330       }
331     }
332     while (*p != '\r' && *p != '\n' && *p != '&' && p < eof) p++;
333     if (*p == '&') {
334       p++;
335       // Look for free-form continuation.
336       while (*p == ' ' || *p == '\t') p++;
337       if (*p == '\r' || *p == '\n') {
338         pe = p;
339         while (*pe == '\r' || *pe == '\n' || *pe == ' ' || *pe == '\t') pe++;
340         if (*pe == '&')
341           return LANG_FORTRANFREE;
342       }
343     }
344     while (*p == '\r' || *p == '\n') p++;
345   }
346   return LANG_FORTRANFREE; // might as well be free-form
347 }
348
349 const char *disambiguate_h(SourceFile *sourcefile) {
350   char *p, *pe, *bof;
351   int length;
352
353   // If the directory contains a matching *.m file, likely Objective C.
354   length = strlen(sourcefile->filename);
355   if (strcmp(sourcefile->ext, "h") == 0) {
356     char path[length];
357     strncpy(path, sourcefile->filename, length);
358     path[length] = '\0';
359     *(path + length - 1) = 'm';
360     char **filenames = sourcefile->filenames;
361     if (filenames) {
362       int i;
363       for (i = 0; filenames[i] != NULL; i++)
364         if (strcmp(path, filenames[i]) == 0)
365           return LANG_OBJECTIVE_C;
366     }
367   }
368
369   // Attempt to detect based on file contents.
370   char line[81], buf[81];
371   bof = ohcount_sourcefile_get_contents(sourcefile);
372   p = bof;
373   pe = p;
374   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
375   while (pe < eof) {
376     // Get a line at a time.
377     while (pe < eof && *pe != '\r' && *pe != '\n') pe++;
378     length = (pe - p <= sizeof(line)) ? pe - p : sizeof(line);
379     strncpy(line, p, length);
380     line[length] = '\0';
381     char *eol = line + strlen(line);
382     char *line_end = pe;
383
384     // Look for C++ headers.
385     if (*line == '#') {
386       p = line + 1;
387       while (*p == ' ' || *p == '\t') p++;
388       if (strncmp(p, "include", 7) == 0 &&
389           (*(p + 7) == ' ' || *(p + 7) == '\t')) {
390         // /^#\s*include\s+[<"][^>"]+[>"]/
391         p += 8;
392         while (*p == ' ' || *p == '\t') p++;
393         if (*p == '<' || *p == '"') {
394           // Is the header file a C++ header file?
395           p++;
396           pe = p;
397           while (pe < eol && *pe != '>' && *pe != '"') pe++;
398           length = pe - p;
399           strncpy(buf, p, length);
400           buf[length] = '\0';
401           if (ohcount_hash_is_cppheader(buf, length))
402             return LANG_CPP;
403           // Is the extension for the header file a C++ file?
404           p = pe;
405           while (p > line && *(p - 1) != '.') p--;
406           length = pe - p;
407           strncpy(buf, p, length);
408           buf[length] = '\0';
409           struct ExtensionMap *re = ohcount_hash_language_from_ext(buf, length);
410           if (re && strcmp(re->value, LANG_CPP) == 0)
411             return LANG_CPP;
412         }
413       }
414     }
415
416     // Look for C++ keywords.
417     p = line;
418     while (p < eol) {
419       if (islower(*p) && p != bof && !isalnum(*(p - 1)) && *(p - 1) != '_') {
420         pe = p;
421         while (islower(*pe)) pe++;
422         if (!isalnum(*pe) && *pe != '_') {
423           length = pe - p;
424           strncpy(buf, p, length);
425           buf[length] = '\0';
426           if (strcmp(buf, "class") == 0 ||
427               strcmp(buf, "namespace") == 0 ||
428               strcmp(buf, "template") == 0 ||
429               strcmp(buf, "typename") == 0)
430             return LANG_CPP;
431         }
432         p = pe + 1;
433       } else p++;
434     }
435
436     // Next line.
437     pe = line_end;
438     while (*pe == '\r' || *pe == '\n') pe++;
439     p = pe;
440   }
441
442   // Nothing to suggest C++.
443   return LANG_C;
444 }
445
446 const char *disambiguate_in(SourceFile *sourcefile) {
447   char *p, *pe;
448   int length;
449   const char *language = NULL;
450
451   p = sourcefile->filepath;
452   pe = p + strlen(p) - 3;
453   if (strstr(p, ".") <= pe) {
454     // Only if the filename has an extension prior to the .in
455     length = pe - p;
456     char buf[length];
457     strncpy(buf, p, length);
458     buf[length] = '\0';
459     SourceFile *undecorated = ohcount_sourcefile_new(buf);
460     p = ohcount_sourcefile_get_contents(sourcefile);
461                 if (!p) {
462                         return NULL;
463                 }
464     // The filepath without the '.in' extension does not exist on disk. The
465     // sourcefile->diskpath field must be set incase the detector needs to run
466     // 'file -b' on the file.
467     ohcount_sourcefile_set_diskpath(undecorated, sourcefile->filepath);
468     ohcount_sourcefile_set_contents(undecorated, p);
469                 undecorated->filenames = sourcefile->filenames;
470     language = ohcount_sourcefile_get_language(undecorated);
471     ohcount_sourcefile_free(undecorated);
472   }
473   return language;
474 }
475
476 const char *disambiguate_inc(SourceFile *sourcefile) {
477   char *p = ohcount_sourcefile_get_contents(sourcefile);
478         if (p) {
479                 char *eof = p + strlen(p);
480                 while (p < eof) {
481                         if (*p == '\0')
482                                 return BINARY;
483                         else if (*p == '?' && strncmp(p + 1, "php", 3) == 0)
484                                 return LANG_PHP;
485                         p++;
486                 }
487         }
488   return NULL;
489 }
490
491 const char *disambiguate_m(SourceFile *sourcefile) {
492   char *p, *pe;
493   int length;
494
495   // Attempt to detect based on a weighted heuristic of file contents.
496   int matlab_score = 0;
497   int objective_c_score = 0;
498   int limbo_score = 0;
499   int octave_syntax_detected = 0;
500
501   int i, has_h_headers = 0, has_c_files = 0;
502   char **filenames = sourcefile->filenames;
503   if (filenames) {
504     for (i = 0; filenames[i] != NULL; i++) {
505       p = filenames[i];
506       pe = p + strlen(p);
507       if (pe - p >= 4) {
508         if (*(pe - 4) == '.' && *(pe - 3) == 'c' &&
509             ((*(pe - 2) == 'p' && *(pe - 1) == 'p') ||
510              (*(pe - 2) == '+' && *(pe - 1) == '+') ||
511              (*(pe - 2) == 'x' && *(pe - 1) == 'x'))) {
512           has_c_files = 1;
513           break; // short circuit
514         }
515       } else if (pe - p >= 3) {
516         if (*(pe - 3) == '.' && *(pe - 2) == 'c' && *(pe - 1) == 'c') {
517           has_c_files = 1;
518           break; // short circuit
519         }
520       } else if (pe - p >= 2) {
521         if (*(pe - 2) == '.') {
522           if (*(pe - 1) == 'h')
523             has_h_headers = 1;
524           else if (*(pe - 1) == 'c' || *(pe - 1) == 'C') {
525             has_c_files = 1;
526             break; // short circuit
527           }
528         }
529       }
530     }
531   }
532   if (has_h_headers && !has_c_files)
533     objective_c_score += 5;
534
535   char line[81], buf[81];
536   p = ohcount_sourcefile_get_contents(sourcefile);
537   pe = p;
538   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
539   while (pe < eof) {
540     // Get a line at a time.
541     while (pe < eof && *pe != '\r' && *pe != '\n') pe++;
542     length = (pe - p <= sizeof(line)) ? pe - p : sizeof(line);
543     strncpy(line, p, length);
544     line[length] = '\0';
545     char *eol = line + strlen(line);
546     char *line_end = pe;
547
548     // Look for tell-tale lines.
549     p = line;
550     while (*p == ' ' || *p == '\t') p++;
551     if (*p == '%') { // Matlab comment
552       matlab_score++;
553                 } else if (*p == '#' && strncmp(p, "#import", 7) == 0) { // Objective C
554                         objective_c_score++;
555     } else if (*p == '#') { // Limbo or Octave comment
556       while (*p == '#') p++;
557       if (*p == ' ' || *p == '\t') {
558         limbo_score++;
559         matlab_score++;
560         octave_syntax_detected = 1;
561       }
562     } else if (*p == '/' && *(p + 1) == '/' || *(p + 1) == '*') {
563       objective_c_score++; // Objective C comment
564     } else if (*p == '+' || *p == '-') { // Objective C method signature
565       objective_c_score++;
566     } else if (*p == '@' || *p == '#') { // Objective C method signature
567       if (strncmp(p, "@implementation", 15) == 0 ||
568           strncmp(p, "@interface", 10) == 0)
569         objective_c_score++;
570     } else if (strncmp(p, "function", 8) == 0) { // Matlab or Octave function
571       p += 8;
572       while (*p == ' ' || *p == '\t') p++;
573       if (*p == '(')
574         matlab_score++;
575     } else if (strncmp(p, "include", 7) == 0) { // Limbo include
576       // /^include[ \t]+"[^"]+\.m";/
577       p += 7;
578       if (*p == ' ' || *p == '\t') {
579         while (*p == ' ' || *p == '\t') p++;
580         if (*p == '"') {
581           while (*p != '"' && p < eol) p++;
582           if (*p == '"' && *(p - 2) == '.' && *(p - 1) == 'm')
583             limbo_score++;
584         }
585       }
586     }
587
588     // Look for Octave keywords.
589     p = line;
590     while (p < eol) {
591       if (islower(*p) && p != line && !isalnum(*(p - 1))) {
592         pe = p;
593         while (islower(*pe) || *pe == '_') pe++;
594         if (!isalnum(*pe)) {
595           length = pe - p;
596           strncpy(buf, p, length);
597           buf[length] = '\0';
598           if (strcmp(buf, "end_try_catch") == 0 ||
599               strcmp(buf, "end_unwind_protect") == 0 ||
600               strcmp(buf, "endfunction") == 0 ||
601               strcmp(buf, "endwhile") == 0)
602             octave_syntax_detected = 1;
603         }
604         p = pe + 1;
605       } else p++;
606     }
607
608     // Look for Limbo declarations
609     p = line;
610     while (p < eol) {
611       if (*p == ':' && (*(p + 1) == ' ' || *(p + 1) == '\t')) {
612         // /:[ \t]+(module|adt|fn ?\(|con[ \t])/
613         p += 2;
614         if (strncmp(p, "module", 6) == 0 && !isalnum(*(p + 6)) ||
615             strncmp(p, "adt", 3) == 0 && !isalnum(*(p + 3)) ||
616             strncmp(p, "fn", 2) == 0 &&
617               (*(p + 2) == ' ' && *(p + 3) == '(' || *(p + 2) == '(') ||
618             strncmp(p, "con", 3) == 0 &&
619               (*(p + 3) == ' ' || *(p + 3) == '\t'))
620           limbo_score++;
621       } else p++;
622     }
623
624     // Next line.
625     pe = line_end;
626     while (*pe == '\r' || *pe == '\n') pe++;
627     p = pe;
628   }
629
630   if (limbo_score > objective_c_score && limbo_score > matlab_score)
631     return LANG_LIMBO;
632   else if (objective_c_score > matlab_score)
633     return LANG_OBJECTIVE_C;
634   else
635     return octave_syntax_detected ? LANG_OCTAVE : LANG_MATLAB;
636 }
637
638 #define QMAKE_SOURCES_SPACE "SOURCES +="
639 #define QMAKE_SOURCES "SOURCES+="
640 #define QMAKE_CONFIG_SPACE "CONFIG +="
641 #define QMAKE_CONFIG "CONFIG+="
642
643 const char *disambiguate_pro(SourceFile *sourcefile) {
644         char *p = ohcount_sourcefile_get_contents(sourcefile);
645   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
646         for (; p < eof; p++) {
647                 if (strncmp(p, QMAKE_SOURCES_SPACE, strlen(QMAKE_SOURCES_SPACE)) == 0 ||
648                                 strncmp(p, QMAKE_SOURCES, strlen(QMAKE_SOURCES)) == 0 ||
649                                 strncmp(p, QMAKE_CONFIG_SPACE, strlen(QMAKE_CONFIG_SPACE)) == 0 ||
650                                 strncmp(p, QMAKE_CONFIG, strlen(QMAKE_CONFIG)) == 0)
651                         return LANG_MAKE; // really QMAKE
652         }
653         return LANG_IDL_PVWAVE;
654 }
655
656 const char *disambiguate_st(SourceFile *sourcefile) {
657   char *p, *pe;
658   int length;
659
660   // Attempt to detect based on file contents.
661   int found_assignment = 0, found_block_start = 0, found_block_end = 0;
662
663   char line[81];
664   p = ohcount_sourcefile_get_contents(sourcefile);
665   pe = p;
666   char *eof = p + ohcount_sourcefile_get_contents_size(sourcefile);
667   while (pe < eof) {
668     // Get a line at a time.
669     while (p < eof && *pe != '\r' && *pe != '\n') pe++;
670     length = (pe - p <= sizeof(line)) ? pe - p : sizeof(line);
671     strncpy(line, p, length);
672     line[length] = '\0';
673     char *eol = line + strlen(line);
674     char *line_end = pe;
675
676     for (p = line; p < eol; p++) {
677       if (*p == ':') {
678         p++;
679         while (p < eol && (*p == ' ' || *p == '\t')) p++;
680         if (*p == '=')
681           found_assignment = 1;
682         else if (*p == '[')
683           found_block_start = 1;
684       } else if (*p == ']' && *(p + 1) == '.') found_block_end = 1;
685       if (found_assignment && found_block_start && found_block_end)
686         return LANG_SMALLTALK;
687     }
688
689     // Next line.
690     pe = line_end;
691     while (*pe == '\r' || *pe == '\n') pe++;
692     p = pe;
693   }
694
695   return NULL;
696 }
697
698 int ohcount_is_binary_filename(const char *filename) {
699   char *p = (char *)filename + strlen(filename);
700   while (p > filename && *(p - 1) != '.') p--;
701   if (p > filename) {
702     struct ExtensionMap *re;
703     int length = strlen(p);
704     re = ohcount_hash_language_from_ext(p, length);
705     if (re) return ISBINARY(re->value);
706     // Try the lower-case version of this extension.
707     char lowerext[length];
708     strncpy(lowerext, p, length);
709     lowerext[length] = '\0';
710     for (p = lowerext; p < lowerext + length; p++) *p = tolower(*p);
711     re = ohcount_hash_language_from_ext(lowerext, length);
712     if (re) return ISBINARY(re->value);
713   }
714   return 0;
715 }