1// Callstacker.cpp : Defines the entry point for the console application.
2//
3
4#include "stdafx.h"
5
6#include <string>
7#include <map>
8#include <vector>
9
10using namespace std;
11
12// can't delete, only add files repository!
13class SkSourceDb {
14public:
15  SkSourceDb(const char* szBaseSrcPath, const char* szLightSymbolsDbFile) {
16    this->baseSrcPath = szBaseSrcPath;
17    this->lightSymbolsDbFile = szLightSymbolsDbFile;
18    nextId = 1;
19  }
20
21  const string& getBaseSrcPath() const {
22    return baseSrcPath;
23  }
24
25  string GetStoredFilename(const string& filename) {
26    string base = filename.substr(0, baseSrcPath.length());
27    if (base != baseSrcPath) {
28      return "";
29    }
30
31    string relative = filename.substr(baseSrcPath.length());
32    char tmp[10000];
33    strcpy(tmp, relative.c_str()); // insecure
34    char* sz = tmp;
35    while (*sz) {
36      if (*sz == '\\') *sz = '/';
37      sz++;
38    }
39    sz = tmp;
40    if (*sz == '/') sz++;
41
42    return string(sz);
43  }
44
45  int obtainFileId(const string& filename) {
46    string stored = GetStoredFilename(filename);
47    if (stored.empty()) {
48      return -1;
49    }
50
51    if (filenames.find(stored) == filenames.end()) {
52      int id = nextId;
53      nextId++;
54      filenames[stored] = id;
55      return id;
56    } else {
57      return filenames[stored];
58    }
59  }
60
61  static void Load(char* szFileName, SkSourceDb** ret, const char* whereToSave, const char* szBaseSrcPath) {
62    char szLine[10000];
63    FILE* file = fopen(szFileName, "rt");
64    if (file == NULL) {
65      *ret = NULL;
66      return;
67    }
68
69    const char* trimed;
70    SkSourceDb* db = new SkSourceDb(szBaseSrcPath, whereToSave == NULL ? szFileName : whereToSave);
71
72    map<int, string> ids;
73    int id;
74    while (true) {
75      id = -1;
76      if (fscanf(file, "%i", &id) == 0) break;
77      if (id == -1) break;
78      *szLine = '\0';
79      fgets(szLine, 10000, file);
80      trimed = trim(szLine);
81
82      if (id < 0 || ids[id] != "") {
83        printf("fatal error: duplicate value for id = %i, existing = \"%s\", new = \"%s\"\n", id, ids[id].c_str(), trimed);
84        exit(-1);
85      }
86
87      if (trimed == NULL || *trimed == '\0') {
88        printf("fatal error: no valuefor id = %i\n", id);
89        exit(-1);
90      }
91
92      if (db->filenames.find(trimed) != db->filenames.end()) {
93        printf("fatal error: duplicate id for same file: file = %s, existing id = %i, new id = %i\n", trimed, db->filenames[trimed], id);
94//        exit(-1);
95      }
96
97      string value = trimed;
98      ids[id] = value;
99      db->filenames[value] = id;
100      if (db->nextId <= id) {
101        db->nextId = id + 1;
102      }
103    }
104
105    *ret = db;
106  }
107
108  // dumb comit, smarter: use append
109  void commit() {
110    save(lightSymbolsDbFile.c_str());
111  }
112
113private:
114
115  static const char* trim(char* sz) {
116    if (sz == NULL) return NULL;
117
118    while (*sz == ' ' || *sz == '\t' || *sz == '\r' || *sz == '\n' || *sz == ',')
119      sz++;
120
121    if (*sz == '\0') return sz;
122
123    int len = strlen(sz);
124    char* start = sz;
125    sz = sz + (len - 1);
126
127    while (sz >= start && (*sz == ' ' || *sz == '\t' || *sz == '\r' || *sz == '\n' || *sz == ',')) {
128      *sz = '\0';
129      sz--;
130    }
131
132    return start;
133  }
134
135  void save(const char* szFilename) {
136    char szLine[10000];
137    FILE* file = fopen(szFilename, "wt");
138
139    map<string, int>::const_iterator itr;
140
141    for(itr = filenames.begin(); itr != filenames.end(); ++itr){
142      fprintf(file, "%i, %s\n", (*itr).second, (*itr).first.c_str());
143    }
144    fclose(file);
145  }
146
147  string baseSrcPath;
148  string lightSymbolsDbFile;
149  map<string, int> filenames;
150  int nextId;
151};
152
153SkSourceDb* source_db = NULL;
154
155bool endsWith(const char* who, const char* what) {
156    int a = strlen(who);
157    int b = strlen(what);
158    return stricmp(who + a - b, what) == 0; // insecure
159}
160
161bool sourceFile(const char* szFileName) {
162    return endsWith(szFileName, ".h") || endsWith(szFileName, ".c") || endsWith(szFileName, ".cpp") || endsWith(szFileName, ".cc");
163}
164
165// "
166// //
167// /*
168class CppState {
169public:
170
171    CppState() : line(1), inComment(false), inLineComment(false), inDoubleQuote(false), inSingleQuote(false), isEscaping(false), commentEnding(false), commentMightBeStarting(false) {
172    }
173
174    void apply(int ch) {
175        if (ch == '\n') {
176            line++;
177            if (inLineComment) inLineComment = false;
178        }
179
180        if (inLineComment) {
181            return;
182        }
183
184        if (commentMightBeStarting) {
185            commentMightBeStarting = false;
186            if (ch == '*') {
187                inComment = true;
188            } else if (ch == '/') {
189                inLineComment = true;
190            } else {
191                add('/');//previously we has / but was not pushed on tokens
192                newToken();//
193            }
194        }
195
196        if (inSingleQuote) {
197            if (isEscaping)
198                isEscaping = false;
199            else if (ch == '\\')
200                isEscaping = true;
201            else if (ch == '\'') {
202                inSingleQuote = false;
203                newToken();
204                pushToken("__SINGLE_QUOTE__");
205                newToken();
206            }
207
208            return;
209        } else if (inDoubleQuote) {
210            if (isEscaping)
211                isEscaping = false;
212            else if (ch == '\\')
213                isEscaping = true;
214            else if (ch == '\"') {
215                inDoubleQuote = false;
216                newToken();
217                pushToken("__DOUBLE_QUOTE__");
218                newToken();
219            }
220
221            return;
222        } else if (inComment) {
223            if (ch == '*') {
224                commentEnding = true;
225            } else if (ch == '/') {
226                inComment = false;
227                commentEnding = false;
228            } else {
229                commentEnding = false;
230            }
231
232            return;
233        }
234
235        switch (ch) {
236        case '\'':
237            newToken();
238            inSingleQuote = true;
239            return;
240
241        case '\"':
242            newToken();
243            inDoubleQuote = true;
244            return;
245
246        case '/':
247            newToken();
248            commentMightBeStarting = true;
249            return;
250        }
251
252        if (isspace(ch)) {
253            newToken();
254        } else if (tokenDelimiter(ch)) {
255            newToken();
256            if (isSingleCharToken(ch)) {
257                add(ch);
258                newToken();
259            }
260        } else if (isTokenable(ch)) {
261              add(ch);
262        } else {
263            printf("undexpected ... %c", (char)ch);
264        }
265    }
266
267    bool enteredFunction() {
268        if (inComment || inLineComment || inDoubleQuote || inSingleQuote || commentMightBeStarting) {
269            return false;
270        }
271
272        if (tokens.size() == 0) {
273            return false;
274        }
275
276        if (tokens[tokens.size() - 1] != "{") {
277            return false;
278        }
279
280        int i = tokens.size() - 2;
281
282        bool foundCloseBraket = false;
283        int innerBrakets = 0;
284        bool foundOpenBraket = false;
285    int iName = -1;
286
287        while (i >= 0) {
288            string t_i = tokens[i]; // debugging sucks!
289
290      if (!foundCloseBraket && (tokens[i] == "enum"
291                             || tokens[i] == "struct"
292                             || tokens[i] == "class"
293                             || tokens[i] == "namespace"
294                             || tokens[i] == "public"
295                             || tokens[i] == "private"
296                             || tokens[i] == "protected"
297                             || tokens[i] == "__asm"
298                             || tokens[i] == "catch"
299                             || tokens[i] == "__except"
300                             )) {
301        return false;
302      }
303
304            if (tokens[i] == ")") {
305                if (foundCloseBraket)
306                    innerBrakets++;
307                else if (i >= 3 && tokens[i - 1] == "." && tokens[i - 2] == "." && tokens[i - 3] == ".") {
308                    i -= 3;
309                }
310                foundCloseBraket = true;
311            } else if (tokens[i] == "(" && innerBrakets > 0) {
312                innerBrakets--;
313            } else if (tokens[i] == "(" && innerBrakets == 0) {
314                foundOpenBraket = true;
315                i--; if ( i < 0) return false;
316                string name = tokens[i];
317        iName = i;
318
319                if (name == "if" || name == "while" || name == "switch"|| name == "for") {
320                    return false;
321                }
322
323                if (!CouldBeFunctionName(name)) return false;
324                if (i >= 6 && tokens[i - 1] == ":" && tokens[i - 2] == ":" && CouldBeClassnName(tokens[i - 3]) && tokens[i - 4] == ":" && tokens[i - 5] == ":" && CouldBeClassnName(tokens[i - 6])) {
325                    name =  tokens[i - 6] + "::" + tokens[i - 3] + "::" + name;
326          iName = i - 6;
327                    if (i >= 7 && (tokens[i - 7] == ":" || tokens[i-7] == ",")) {
328                        i -= 7 + 1;
329                        name = "";
330                        foundCloseBraket = false;
331                        foundOpenBraket = false;
332                        innerBrakets = 0;
333                        continue;
334                    }
335                }
336                else if (i >= 3 && tokens[i - 1] == ":" && tokens[i - 2] == ":" && CouldBeClassnName(tokens[i - 3])) {
337                    name = tokens[i - 3] + "::" + name;
338          iName = i - 3;
339                    if (i >= 4 && (tokens[i - 4] == ":" || tokens[i-4] == ",")) {
340                        i -= 4 + 1;
341                        name = "";
342                        foundCloseBraket = false;
343                        foundOpenBraket = false;
344                        innerBrakets = 0;
345                        continue;
346                    }
347                }
348                else if (i >= 1 && (tokens[i - 1] == ":" || tokens[i-1] == ",")) {
349                    i -= 1 + 1;
350                    name = "";
351                    foundCloseBraket = false;
352                    foundOpenBraket = false;
353                    innerBrakets = 0;
354                    continue;
355                }
356
357                if (name == "") {
358                    return false;
359                }
360
361        if (iName >= 2 && tokens[iName - 2] == "#" && tokens[iName - 1] == "define") {
362          return false;
363        }
364
365        if (iName >= 1 && (tokens[i - 1] == "enum"
366                        || tokens[i - 1] == "struct"
367                        || tokens[i - 1] == "class"
368                        || tokens[i - 1] == "namespace"
369                        || tokens[i - 1] == "public"
370                        || tokens[i - 1] == "private"
371                        || tokens[i - 1] == "protected"
372                        || tokens[i - 1] == "__asm"
373                        || tokens[i - 1] == "if"
374                        || tokens[i - 1] == "while"
375                        || tokens[i - 1] == "for"
376                        || tokens[i - 1] == "switch"
377                        || tokens[i - 1] == "!"
378                        )) {
379          return false;
380        }
381
382        int k = 10;
383        i = iName - 2;
384        bool isInline = false;// heuristic for inline functions
385        while (k > 0 && i >= 0) {
386          if (tokens[i] == "inline") {
387            isInline = true;
388            break;
389          }
390          if (tokens[i] == ";" || tokens[i] == "{" || tokens[i] == "}") {
391            break;
392          }
393          i--;
394          k--;
395        }
396
397        if (isInline) return false; //do not trace inline functions
398
399                lastFunctionName = name;
400                return true;
401            } else {
402                if (!foundCloseBraket) {
403                    if (!IgnorableFunctionModifier(tokens[i])) {
404                        return false;
405                    }
406                } else {
407                    if (!IgnorableFunctionParameter(tokens[i])) {
408                        return false;
409                    }
410                }
411            }
412
413            i--;
414        }
415
416        return false;
417    }
418
419    const char* functionName() {
420        return lastFunctionName.c_str();
421    }
422
423    int lineNumber() {
424        return line;
425    }
426
427private:
428
429    bool CouldBeFunctionName(const string& str) {
430        if (str.empty()) return false;
431        if (!isalpha(str[0]) && str[0] != '_' && str[0] != '~' && str[0] != ':') return false;
432        for (int i = 1; i < str.length(); i++) {
433            if (!isalpha(str[i]) && !isdigit(str[i]) && str[i] != '_' && str[i] != ':') return false;
434        }
435
436        return true;
437    }
438
439    bool isNumber(const string& str) {
440        if (str.empty()) return false;
441        for (int i = 0; i < str.length(); i++) {
442            if (!isdigit(str[i]) && str[i] != '.' && str[i] != 'x' && str[i] != 'X' && str[i] != 'e' && str[i] != 'E') return false;
443        }
444
445        return true;
446    }
447
448
449    bool isOperator(const string& str) {
450        if (str.empty()) return false;
451        for (int i = 1; i < str.length(); i++) {
452            switch (str[i]) {
453            case '<':
454            case '>':
455            case '=':
456            case '+':
457            case '-':
458            case '*':
459            case '/':
460            case '(':
461            case ')':
462            case '[':
463            case ']':
464            case '!':
465            case '|':
466            case '&':
467            case '^':
468            case '%':
469                break;
470            default:
471                return false;
472            }
473        }
474
475        return true;
476    }
477
478    bool CouldBeClassnName(const string& str) {
479        return CouldBeFunctionName(str);
480    }
481
482    bool IgnorableFunctionModifier(const string& str) {
483        return str.empty() || CouldBeFunctionName(str);
484    }
485
486    bool IgnorableFunctionParameter(const string& str) {
487        if (str.empty()) return true;
488        if (CouldBeFunctionName(str)) return true;
489        if (str == ",") return true;
490        if (str == "*") return true;
491        if (str == "=") return true;
492        if (str == "&") return true;
493        if (str == "<") return true;
494        if (str == ">") return true;
495        if (str == ":") return true;
496        if (str == "=") return true;
497        if (isNumber(str)) return true;
498        if (str == "]") return true;
499        if (str == "[") return true;
500
501    if (str == ";") return false;
502
503        return false;
504    }
505
506
507    bool tokenDelimiter(int ch) {
508        if (isspace(ch))    return true;
509        if (isdigit(ch))    return false;
510        if (isalpha(ch))    return false;
511        if (ch == '_')        return false;
512                            return true;
513    }
514
515    bool isTokenable(int ch) {
516        if (isdigit(ch))    return true;
517        if (isalpha(ch))    return true;
518        if (ch == '_')        return true;
519                            return false;
520    }
521
522    bool isSingleCharToken(int ch) {
523        if (isspace(ch))    return false;
524        if (isdigit(ch))    return false;
525        if (isalpha(ch))    return false;
526        if (ch == '_')        return false;
527                            return true;
528    }
529
530    void add(char ch) {
531      token += ch;
532    }
533
534    void pushToken(const char* sz) {
535        newToken();
536        token = sz;
537        newToken();
538    }
539
540    void newToken() {
541        if (token.empty()) return;
542
543        if (tokens.size() > 0) {
544            string last = tokens[tokens.size() -1];
545            if (last == "operator") {
546                if (isOperator(op + token)) {
547                    op += token;
548                    token = "";
549                    return;
550                } else if (op != "" && isOperator(op)) {
551                    tokens[tokens.size() -1] = last + op;
552                    op = "";
553                    return;
554                } else if (isOperator(token)) {
555                    op = token;
556                    token = "";
557                    return;
558                } else {
559                    // compile error?
560                    op = "";
561                }
562            } else if (last == "~") {
563                tokens[tokens.size() -1] = last + token;
564                token = "";
565                return;
566            }
567        }
568
569        tokens.push_back(token);
570        token = "";
571    }
572
573    int line;
574    vector<string> tokens;
575    string token;
576    string lastFunctionName;
577
578    bool inComment;
579    bool inLineComment;
580    bool inDoubleQuote;
581    bool inSingleQuote;
582    bool isEscaping;
583    bool commentEnding;
584    bool commentMightBeStarting;
585
586    string op;
587};
588
589char output[100000000];
590char* now;
591
592
593void emit(char ch) {
594  *now = ch;
595  now++;
596}
597
598void emit(const char* szCode, const char* szFunctionName, int fileId, int line) {
599    sprintf(now, szCode, szFunctionName, fileId, line);
600    while (*now) {
601        now++;
602    }
603}
604
605void runFile(const char* szFileNameInput, const char* szFileNameOutput, const char* szInclude) {
606  printf("%s\n", szFileNameInput);
607
608
609  if (!sourceFile(szFileNameInput))
610    return;
611
612  now = output;
613  int fileId = source_db->obtainFileId(szFileNameInput);
614  FILE* file = fopen(szFileNameInput, "rt");
615  int ch;
616  CppState state;
617  while (true) {
618    int ch = getc(file);
619    if (ch == -1)
620        break;
621    state.apply(ch);
622    emit(ch);
623    if (ch == '{' && state.enteredFunction()) { // {
624      emit("LS_TRACE(\"%s\", %i, %i);", state.functionName(), fileId, state.lineNumber()); // light symbol traces, create a macro to define it
625    }
626  }
627  fclose(file);
628
629  file = fopen(szFileNameOutput, "wt");
630  // TODO: input parameter
631  fprintf(file, "#include \"%s\"\n", szInclude);
632  fwrite(output, 1, now - output, file);
633  fclose(file);
634  //source_db->commit();
635}
636
637// to create the list file:
638// dir *.cpp;*.h;*.cc /s /b
639void runAll(char* szFileHolder, const char* szInclude) {
640  FILE* file = fopen(szFileHolder, "rt");
641  if (file == NULL) {
642    return;
643  }
644
645  while (true) {
646    char szFileName[10000] = "";
647    fgets(szFileName, 10000, file);
648      char* end = szFileName + strlen(szFileName) - 1;
649      while (end > szFileName && (*end == '\n' || *end == '\r' || *end == ' ' || *end == '\t')) {
650        *end = 0;
651        end--;
652      }
653    if (strlen(szFileName) == 0)
654        break;
655
656  runFile(szFileName, szFileName, szInclude);
657  }
658  fclose(file);
659  source_db->commit();
660}
661
662int _tmain(int argc, char* argv[])
663{
664  // base path, include, list.txt, lightSymbolFile, [lightSymbolsOut]
665  SkSourceDb::Load(argv[4], &source_db, argc == 5 ? argv[4] : argv[5], argv[1]);
666  if (source_db == NULL) {
667    source_db = new SkSourceDb(argv[1], argv[4]);
668  }
669
670  runAll(argv[3], argv[2]); // e.g. foo\\src\\lightsymbols\\lightsymbols.h");
671
672  return 0;
673}
674