parsing.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. ktigcc - TIGCC IDE for KDE
  3. Copyright (C) 2006-2007 Kevin Kofler
  4. Copyright (C) 2007 Konrad Meyer
  5. This program is free software; you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation; either version 2, or (at your option)
  8. any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program; if not, write to the Free Software Foundation,
  15. Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  16. */
  17. // This file handles parsing of source files for the function list and for
  18. // completion purposes.
  19. #include "parsing.h"
  20. #include "ktigcc.h"
  21. #include <QString>
  22. #include <QStringList>
  23. #include <QRegExp>
  24. #include <QTextCodec>
  25. #include <QApplication>
  26. #include <QEventLoop>
  27. #include <QDir>
  28. #include <QLinkedList>
  29. #include <kprocess.h>
  30. #include <kmessagebox.h>
  31. #include <unistd.h>
  32. SourceFileFunctions getCFunctions(const QString &text)
  33. {
  34. // Parse C using Exuberant Ctags (http://ctags.sourceforge.net).
  35. SourceFileFunctions result;
  36. write_temp_file("parser_temp_source.c",text,0);
  37. {
  38. KProcess process;
  39. process.setOutputChannelMode(KProcess::MergedChannels);
  40. process.setWorkingDirectory(tempdir);
  41. process<<"ctags"<<"-f"<<"-"<<"-n"<<"-u"<<"-h"<<".h"<<"--language-force=C"
  42. <<"--C-kinds=pf"<<"--fields=k"<<"-I"<<"CALLBACK,__ATTR_TIOS__,"
  43. "__ATTR_TIOS_NORETURN__,__ATTR_TIOS_CALLBACK__,__ATTR_GCC__,"
  44. "__ATTR_LIB_C__,__ATTR_LIB_ASM__,__ATTR_LIB_ASM_NORETURN__,"
  45. "__ATTR_LIB_CALLBACK_C__,__ATTR_LIB_CALLBACK_ASM__"
  46. <<"parser_temp_source.c";
  47. process.start();
  48. if (!process.waitForStarted()) {
  49. delete_temp_file("parser_temp_source.c");
  50. KMessageBox::error(0,"Could not run ctags.\nThis feature requires "
  51. "Exuberant Ctags, which can be obtained from: "
  52. "http://ctags.sourceforge.net");
  53. return result;
  54. }
  55. bool ret;
  56. while ((ret=process.canReadLine()) || process.state()!=QProcess::NotRunning) {
  57. if (ret) {
  58. QString line=process.readLine();
  59. line.chop(1); // zap newline
  60. QStringList columns=line.split('\t');
  61. int numColumns=columns.count();
  62. QString identifier;
  63. if (numColumns) identifier=columns[0];
  64. QString linenoString;
  65. if (numColumns>2) linenoString=columns[2];
  66. int semicolonPos=linenoString.find(';');
  67. if (semicolonPos>=0) linenoString.truncate(semicolonPos);
  68. int lineno=linenoString.toInt()-1;
  69. QString kind;
  70. if (numColumns>3) kind=columns[3];
  71. SourceFileFunctions::Iterator it=result.find(identifier);
  72. if (kind=="p") {
  73. if (it==result.end())
  74. result.append(SourceFileFunction(identifier,lineno,-1));
  75. } else if (kind=="f") {
  76. if (it==result.end())
  77. result.append(SourceFileFunction(identifier,-1,lineno));
  78. else
  79. (*it).implementationLine=lineno;
  80. } else qWarning("Invalid result from ctags.");
  81. } else {
  82. usleep(10000);
  83. QCoreApplication::processEvents(QEventLoop::ExcludeUserInput,10);
  84. }
  85. }
  86. }
  87. delete_temp_file("parser_temp_source.c");
  88. return result;
  89. }
  90. SourceFileFunctions getASMFunctions(const QString &text)
  91. {
  92. // Parse ASM by hand.
  93. QStringList lines=text.split('\n');
  94. unsigned lineno=0;
  95. SourceFileFunctions result;
  96. foreach (const QString &line, lines) {
  97. if (line.isEmpty()) {lineno++; continue;}
  98. QString identifier;
  99. unsigned col=0, l=line.length();
  100. while (col<l) {
  101. QChar c=line[col++];
  102. if ((c>='A'&&c<='Z')||(c>='a'&&c<='z')||(c>='0'&&c<='9')||c=='_'||c=='$')
  103. identifier.append(c);
  104. else
  105. break;
  106. }
  107. if (line[col-1]==':') result.append(SourceFileFunction(identifier,-1,lineno));
  108. lineno++;
  109. }
  110. return result;
  111. }
  112. CompletionInfo parseFileCompletion(const QString &fileText,
  113. const QString &pathInProject,
  114. CompletionInfo result)
  115. {
  116. // Parse included files.
  117. // Empty lines can be ignored here.
  118. QStringList lines=fileText.split('\n',QString::SkipEmptyParts);
  119. bool inComment=false;
  120. bool isSystemHeader=pathInProject.isNull();
  121. foreach (const QString &line, lines) {
  122. if (!inComment) {
  123. QString strippedLine=line.trimmed();
  124. if (strippedLine.startsWith("#include")) {
  125. QString includedName=strippedLine.mid(8).trimmed();
  126. if (!includedName.isEmpty()) {
  127. if (includedName[0]=='<') {
  128. int pos=includedName.find('>',1);
  129. if (pos>=0)
  130. result.includedSystem.append(includedName.mid(1,pos-1));
  131. } else if (includedName[0]=='\"') {
  132. int pos=includedName.find('\"',1);
  133. if (pos>=0) {
  134. if (isSystemHeader)
  135. // A system header can only include another system header.
  136. result.includedSystem.append(includedName.mid(1,pos-1));
  137. else
  138. result.included.append(QDir::cleanPath(pathInProject+"/"
  139. +includedName.mid(1,pos-1)));
  140. }
  141. } // else ignore
  142. }
  143. }
  144. }
  145. int pos=0;
  146. if (inComment) {
  147. in_comment:
  148. pos=line.find("*/",pos);
  149. if (pos<0) continue; // Comment line only, next line.
  150. pos+=2;
  151. inComment=false;
  152. }
  153. pos=line.find("/*",pos);
  154. if (pos>=0) {
  155. pos+=2;
  156. inComment=true;
  157. goto in_comment;
  158. }
  159. }
  160. // Parse for prototypes etc. using ctags.
  161. QString fileTextCleaned=fileText;
  162. // ctags doesn't like asmspecs.
  163. fileTextCleaned.remove(QRegExp("\\b(asm|_asm|__asm)\\(\"%?[adAD][0-7]\"\\)"));
  164. write_temp_file("parser_temp_source.c",fileTextCleaned,0);
  165. {
  166. KProcess process;
  167. process.setOutputChannelMode(KProcess::MergedChannels);
  168. process.setWorkingDirectory(tempdir);
  169. process<<"ctags"<<"-f"<<"-"<<"-n"<<"-u"<<"-h"<<".h"<<"--language-force=C"
  170. <<"--C-kinds=defgpstuvx"<<"--fields=kS"<<"-I"<<"CALLBACK,__ATTR_TIOS__,"
  171. "__ATTR_TIOS_NORETURN__,__ATTR_TIOS_CALLBACK__,__ATTR_GCC__,"
  172. "__ATTR_LIB_C__,__ATTR_LIB_ASM__,__ATTR_LIB_ASM_NORETURN__,"
  173. "__ATTR_LIB_CALLBACK_C__,__ATTR_LIB_CALLBACK_ASM__"
  174. <<"parser_temp_source.c";
  175. process.start();
  176. if (!process.waitForStarted()) {
  177. delete_temp_file("parser_temp_source.c");
  178. KMessageBox::error(0,"Could not run ctags.\nThis feature requires "
  179. "Exuberant Ctags, which can be obtained from: "
  180. "http://ctags.sourceforge.net");
  181. result.dirty=true;
  182. return result;
  183. }
  184. bool ret;
  185. while ((ret=process.canReadLine()) || process.state()!=QProcess::NotRunning) {
  186. if (ret) {
  187. QString line=process.readLine();
  188. line.chop(1); // zap newline
  189. QStringList columns=line.split('\t');
  190. int numColumns=columns.count();
  191. QString identifier;
  192. if (numColumns) identifier=columns[0];
  193. QString linenoString;
  194. if (numColumns>2) linenoString=columns[2];
  195. int semicolonPos=linenoString.find(';');
  196. if (semicolonPos>=0) linenoString.truncate(semicolonPos);
  197. int lineno=linenoString.toInt()-1;
  198. QString kind;
  199. if (numColumns>3) kind=columns[3];
  200. QString type=(kind=="d")?"macro"
  201. :(kind=="e")?"enum"
  202. :(kind=="f" || kind=="p")?"func"
  203. :(kind=="v" || kind=="x")?"var"
  204. :"type";
  205. QString signature;
  206. if (numColumns>4) signature=columns[4];
  207. if (signature.startsWith("signature:")) signature.remove(0,10);
  208. signature.replace(QRegExp("\\s*,"),",").replace(QRegExp("\\s*\\)"),")");
  209. bool alreadyKnown=result.lineNumbers.contains(identifier);
  210. // This has to be done for system headers because there may already be
  211. // better information extracted from the .hsf files. However, .hsf files
  212. // obviously don't contain line number information.
  213. if (isSystemHeader) {
  214. foreach (const CompletionEntry &entry, result.entries) {
  215. if (entry.text == identifier) {
  216. alreadyKnown=true;
  217. break;
  218. }
  219. }
  220. }
  221. if (lineno>=0)
  222. result.lineNumbers.insert(identifier,lineno,(kind!="p" && kind!="x"));
  223. if (!alreadyKnown) {
  224. CompletionEntry entry;
  225. entry.text=identifier;
  226. entry.prefix=type;
  227. entry.postfix=signature;
  228. result.entries.append(entry);
  229. }
  230. } else {
  231. usleep(10000);
  232. QCoreApplication::processEvents(QEventLoop::ExcludeUserInput,10);
  233. }
  234. }
  235. }
  236. delete_temp_file("parser_temp_source.c");
  237. return result;
  238. }