parser.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // SuperTux - Scripting reference generator
  2. // Copyright (C) 2023 Vankata453
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  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. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #include "parser.hpp"
  17. #include <string>
  18. #include <iostream>
  19. #include <unordered_map>
  20. #include "util.hpp"
  21. namespace Parser {
  22. // Simplified versions of various C++ types for easier understanding
  23. static const std::unordered_map<std::string, std::string> s_simplified_types = {
  24. { "SQInteger", "ANY" }, // SQInteger is used internally with Squirrel, so we don't know the exact return type
  25. { "char", "int" },
  26. { "signed char", "int" },
  27. { "short", "int" },
  28. { "long", "int" },
  29. { "unsigned char", "int" },
  30. { "unsigned short", "int" },
  31. { "unsigned int", "int" },
  32. { "unsigned long", "int" },
  33. { "uint8_t", "int" },
  34. { "uint16_t", "int" },
  35. { "uint32_t", "int" },
  36. { "long long", "int" },
  37. { "unsigned long long", "int" },
  38. { "double", "float" },
  39. { "std::string", "string" },
  40. { "std::wstring", "string" },
  41. { "const std::string &", "string" },
  42. { "const std::wstring &", "string" }
  43. };
  44. static void parse_base_classes(tinyxml2::XMLElement* p_inheritancenode, tinyxml2::XMLElement* p_inheritancegraph, Class::BaseClasses& list)
  45. {
  46. list[std::stoi(p_inheritancenode->FindAttribute("id")->Value())] = p_inheritancenode->FirstChildElement("label")->GetText();
  47. tinyxml2::XMLElement* p_childnode = p_inheritancenode->FirstChildElement("childnode");
  48. while (p_childnode)
  49. {
  50. tinyxml2::XMLElement* p_childinheritancenode = first_child_with_attribute(p_inheritancegraph, "node", "id", p_childnode->FindAttribute("refid")->Value());
  51. if (p_childinheritancenode)
  52. parse_base_classes(p_childinheritancenode, p_inheritancegraph, list);
  53. p_childnode = p_childnode->NextSiblingElement("childnode");
  54. }
  55. }
  56. void parse_compounddef(tinyxml2::XMLElement* p_root, Class& cls)
  57. {
  58. tinyxml2::XMLElement* p_compounddef = p_root->FirstChildElement("compounddef");
  59. // Get general class info
  60. const char* name = p_compounddef->FirstChildElement("compoundname")->GetText();
  61. if (!name) return; // Some namespaces may not have a name. Don't include those.
  62. cls.name = name;
  63. // Leave only the class name
  64. const size_t pos = cls.name.find_last_of("::");
  65. if (pos != std::string::npos)
  66. cls.name.erase(0, pos + 1);
  67. // Get additional info
  68. tinyxml2::XMLElement* p_detaileddescpara = p_compounddef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  69. if (p_detaileddescpara) // Detailed description (possibly containing additional info) is available
  70. {
  71. // Check whether the class is marked with "@scripting". Otherwise, do not include.
  72. bool include = false;
  73. tinyxml2::XMLElement* p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  74. while (p_xrefsect)
  75. {
  76. if (el_equal(p_xrefsect, "xreftitle", "Scripting"))
  77. {
  78. include = true;
  79. break;
  80. }
  81. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  82. }
  83. if (!include) return;
  84. p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  85. while (p_xrefsect)
  86. {
  87. // Check if the "xrefsect" title matches any of the additional info titles
  88. const std::string title = p_xrefsect->FirstChildElement("xreftitle")->GetText();
  89. if (title == "Summary")
  90. parse_xrefsect_desc(p_xrefsect, cls.summary);
  91. else if (title == "Instances")
  92. parse_xrefsect_desc(p_xrefsect, cls.instances);
  93. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  94. }
  95. }
  96. parse_sectiondef(p_compounddef, cls);
  97. tinyxml2::XMLElement* p_inheritancegraph = p_compounddef->FirstChildElement("inheritancegraph");
  98. if (p_inheritancegraph)
  99. {
  100. // Get base classes
  101. tinyxml2::XMLElement* p_inheritancenode = first_child_with_attribute(p_inheritancegraph, "node", "id", "1");
  102. if (p_inheritancenode)
  103. parse_base_classes(p_inheritancenode, p_inheritancegraph, cls.base_classes);
  104. cls.base_classes.erase(1); // The node with an ID of "1" is this class
  105. // Get derived (child) classes
  106. p_inheritancenode = p_inheritancegraph->FirstChildElement("node");
  107. while (p_inheritancenode)
  108. {
  109. const int id = std::stoi(p_inheritancenode->FindAttribute("id")->Value());
  110. if (id != 1 && cls.base_classes.find(id) == cls.base_classes.end()) // Make sure this is not this class, or a base class
  111. {
  112. std::string name = p_inheritancenode->FirstChildElement("label")->GetText();
  113. // Leave only the class name
  114. const size_t pos = name.find_last_of("::");
  115. if (pos != std::string::npos)
  116. name.erase(0, pos + 1);
  117. cls.derived_classes.push_back(name);
  118. }
  119. p_inheritancenode = p_inheritancenode->NextSiblingElement("node");
  120. }
  121. std::sort(cls.derived_classes.begin(), cls.derived_classes.end()); // Sort A-Z
  122. cls.derived_classes.erase(std::unique(cls.derived_classes.begin(), cls.derived_classes.end()), cls.derived_classes.end()); // Remove duplicates
  123. }
  124. }
  125. void parse_sectiondef(tinyxml2::XMLElement* p_compounddef, Class& cls)
  126. {
  127. tinyxml2::XMLElement* p_sectiondef = p_compounddef->FirstChildElement("sectiondef");
  128. while (p_sectiondef)
  129. {
  130. parse_memberdef(p_sectiondef, cls);
  131. p_sectiondef = p_sectiondef->NextSiblingElement("sectiondef");
  132. }
  133. }
  134. void parse_memberdef(tinyxml2::XMLElement* p_sectiondef, Class& cls)
  135. {
  136. tinyxml2::XMLElement* p_memberdef = p_sectiondef->FirstChildElement("memberdef");
  137. while (p_memberdef)
  138. {
  139. if (attr_equal(p_memberdef, "kind", "function") &&
  140. //!el_equal(p_memberdef, "type", "") &&
  141. !p_memberdef->FirstChildElement("reimplements")) // Look for non-derived typed functions
  142. {
  143. parse_function(p_memberdef, cls);
  144. }
  145. else if (attr_equal(p_memberdef, "kind", "enum")) // Look for enumerators
  146. {
  147. tinyxml2::XMLElement* p_detaileddescpara = p_memberdef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  148. if (p_detaileddescpara)
  149. {
  150. bool include = false;
  151. std::string prefix;
  152. tinyxml2::XMLElement* p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  153. while (p_xrefsect)
  154. {
  155. if (el_equal(p_xrefsect, "xreftitle", "Scripting")) // Make sure the enum is marked with "@scripting". Otherwise, do not include.
  156. include = true;
  157. else if (el_equal(p_xrefsect, "xreftitle", "Prefix"))
  158. parse_xrefsect_desc(p_xrefsect, prefix);
  159. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  160. }
  161. if (include)
  162. {
  163. tinyxml2::XMLElement* p_enumvalue = p_memberdef->FirstChildElement("enumvalue");
  164. while (p_enumvalue)
  165. {
  166. parse_constant(p_enumvalue, cls, true, prefix); // Parse enumerators as constants
  167. p_enumvalue = p_enumvalue->NextSiblingElement("enumvalue");
  168. }
  169. }
  170. }
  171. }
  172. else if (attr_equal(p_memberdef, "kind", "variable") &&
  173. !el_equal(p_memberdef, "type", "")) // Look for variables
  174. {
  175. if (starts_with(p_memberdef->FirstChildElement("type")->GetText(), "const ")) // Constant variable
  176. parse_constant(p_memberdef, cls);
  177. else // Non-constant variable
  178. parse_variable(p_memberdef, cls);
  179. }
  180. p_memberdef = p_memberdef->NextSiblingElement("memberdef");
  181. }
  182. }
  183. void parse_constant(tinyxml2::XMLElement* p_memberdef, Class& cls, bool include, const std::string& prefix)
  184. {
  185. /** Parse the constant **/
  186. Constant con;
  187. // Get general info
  188. tinyxml2::XMLElement* p_type = p_memberdef->FirstChildElement("type");
  189. if (p_type)
  190. {
  191. con.type = p_type->GetText();
  192. con.type.erase(0, 6); // Remove the "const " part from the type string
  193. }
  194. else // Would indicate this is an enumerator
  195. {
  196. con.type = "int";
  197. }
  198. con.name = p_memberdef->FirstChildElement("name")->GetText();
  199. if (starts_with(con.name, "s_"))
  200. con.name.erase(0, 2); // Remove the "s_" prefix
  201. con.name = prefix + con.name;
  202. tinyxml2::XMLElement* p_initializer = p_memberdef->FirstChildElement("initializer");
  203. if (p_initializer)
  204. con.initializer = p_initializer->GetText();
  205. tinyxml2::XMLElement* p_descpara = p_memberdef->FirstChildElement("briefdescription")->FirstChildElement("para");
  206. tinyxml2::XMLElement* p_detaileddescpara = p_memberdef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  207. if (p_detaileddescpara)
  208. {
  209. tinyxml2::XMLElement* p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  210. while (p_xrefsect)
  211. {
  212. if (el_equal(p_xrefsect, "xreftitle", "Scripting")) // Make sure the constant is marked with "@scripting". Otherwise, do not include.
  213. include = true;
  214. else if (el_equal(p_xrefsect, "xreftitle", "Description"))
  215. parse_xrefsect_desc(p_xrefsect, con.description);
  216. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  217. }
  218. if (!include) return;
  219. if (p_descpara) // Brief description has been provided
  220. {
  221. XMLTextReader read(con.description);
  222. p_descpara->Accept(&read);
  223. }
  224. // Add to constants list
  225. cls.constants.push_back(std::move(con));
  226. }
  227. }
  228. void parse_variable(tinyxml2::XMLElement* p_memberdef, Class& cls)
  229. {
  230. /** Parse the variable **/
  231. Variable var;
  232. // Get general info
  233. var.type = p_memberdef->FirstChildElement("type")->GetText();
  234. var.name = p_memberdef->FirstChildElement("name")->GetText();
  235. if (starts_with(var.name, "m_"))
  236. var.name.erase(0, 2); // Remove the "m_" prefix
  237. tinyxml2::XMLElement* p_descpara = p_memberdef->FirstChildElement("briefdescription")->FirstChildElement("para");
  238. tinyxml2::XMLElement* p_detaileddescpara = p_memberdef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  239. if (p_detaileddescpara)
  240. {
  241. bool include = false;
  242. tinyxml2::XMLElement* p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  243. while (p_xrefsect)
  244. {
  245. if (el_equal(p_xrefsect, "xreftitle", "Scripting")) // Make sure the variable is marked with "@scripting". Otherwise, do not include.
  246. include = true;
  247. else if (el_equal(p_xrefsect, "xreftitle", "Description"))
  248. parse_xrefsect_desc(p_xrefsect, var.description);
  249. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  250. }
  251. if (!include) return;
  252. if (p_descpara) // Brief description has been provided
  253. {
  254. XMLTextReader read(var.description);
  255. p_descpara->Accept(&read);
  256. }
  257. // Add to variables list
  258. cls.variables.push_back(std::move(var));
  259. }
  260. }
  261. void parse_function(tinyxml2::XMLElement* p_memberdef, Class& cls)
  262. {
  263. /** Parse the function **/
  264. Function func;
  265. // Get general info
  266. const char* type = p_memberdef->FirstChildElement("type")->GetText();
  267. if (type)
  268. {
  269. func.type = type;
  270. // Replace type with simplified version, if available
  271. const auto it = s_simplified_types.find(func.type);
  272. if (it != s_simplified_types.end())
  273. func.type = it->second;
  274. }
  275. else
  276. {
  277. func.type = "void";
  278. }
  279. func.name = p_memberdef->FirstChildElement("name")->GetText();
  280. tinyxml2::XMLElement* p_descpara = p_memberdef->FirstChildElement("briefdescription")->FirstChildElement("para");
  281. tinyxml2::XMLElement* p_detaileddescpara = p_memberdef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  282. if (p_detaileddescpara)
  283. {
  284. bool include = false;
  285. tinyxml2::XMLElement* p_xrefsect = p_detaileddescpara->FirstChildElement("xrefsect");
  286. while (p_xrefsect)
  287. {
  288. if (el_equal(p_xrefsect, "xreftitle", "Scripting")) // Make sure the function is marked with "@scripting". Otherwise, do not include.
  289. include = true;
  290. else if (el_equal(p_xrefsect, "xreftitle", "Deprecated"))
  291. {
  292. func.deprecated = true;
  293. parse_xrefsect_desc(p_xrefsect, func.deprecation_msg);
  294. }
  295. else if (el_equal(p_xrefsect, "xreftitle", "Description"))
  296. parse_xrefsect_desc(p_xrefsect, func.description);
  297. p_xrefsect = p_xrefsect->NextSiblingElement("xrefsect");
  298. }
  299. if (include)
  300. {
  301. tinyxml2::XMLElement* p_simplesect = p_detaileddescpara->FirstChildElement("simplesect");
  302. while (p_simplesect)
  303. {
  304. if (attr_equal(p_simplesect, "kind", "return")) // Custom return type specified
  305. {
  306. func.type = p_simplesect->FirstChildElement("para")->GetText();
  307. func.type.pop_back(); // Remove space at the end
  308. break;
  309. }
  310. p_simplesect = p_simplesect->NextSiblingElement("simplesect");
  311. }
  312. if (p_descpara) // Brief description has been provided
  313. {
  314. XMLTextReader read(func.description);
  315. p_descpara->Accept(&read);
  316. }
  317. /** Parse function parameters **/
  318. parse_parameterlist(p_memberdef, func);
  319. // Add to function list
  320. cls.functions.push_back(std::move(func));
  321. }
  322. }
  323. }
  324. void parse_parameterlist(tinyxml2::XMLElement* p_memberdef, Function& func)
  325. {
  326. tinyxml2::XMLElement* p_detaileddescpara = p_memberdef->FirstChildElement("detaileddescription")->FirstChildElement("para");
  327. if (!p_detaileddescpara) return; // There is no detailed description, hence no parameter list
  328. tinyxml2::XMLElement* p_parameterlist = p_detaileddescpara->FirstChildElement("parameterlist");
  329. if (!p_parameterlist) return; // No parameter list
  330. tinyxml2::XMLElement* p_parameteritem = p_parameterlist->FirstChildElement("parameteritem");
  331. while (p_parameteritem)
  332. {
  333. /** Parse the parameter **/
  334. Parameter param;
  335. tinyxml2::XMLElement* p_parameternamelist = p_parameteritem->FirstChildElement("parameternamelist");
  336. tinyxml2::XMLElement* p_parametertype = p_parameternamelist->FirstChildElement("parametertype");
  337. if (p_parametertype) // Type has been provided
  338. param.type = p_parametertype->GetText();
  339. tinyxml2::XMLElement* p_parametername = p_parameternamelist->FirstChildElement("parametername");
  340. if (p_parametername) // Name has been provided
  341. param.name = p_parametername->GetText();
  342. if (starts_with(param.name, "$")) // Parameter names may start with "$" so they can have types assigned. Remove the "$" if so
  343. param.name.erase(0, 1);
  344. tinyxml2::XMLElement* p_parameterdescpara = p_parameteritem->FirstChildElement("parameterdescription")->FirstChildElement("para");
  345. if (p_parameterdescpara) // Description has been provided
  346. {
  347. XMLTextReader read(param.description);
  348. p_parameterdescpara->Accept(&read);
  349. }
  350. // Add to parameter list
  351. func.parameters.push_back(std::move(param));
  352. p_parameteritem = p_parameteritem->NextSiblingElement("parameteritem");
  353. }
  354. }
  355. void parse_xrefsect_desc(tinyxml2::XMLElement* p_xrefsect, std::string& dest)
  356. {
  357. tinyxml2::XMLElement* p_xrefsectdescpara = p_xrefsect->FirstChildElement("xrefdescription")->FirstChildElement("para");
  358. if (p_xrefsectdescpara) // Description has been provided
  359. {
  360. XMLTextReader read(dest);
  361. p_xrefsectdescpara->Accept(&read);
  362. }
  363. }
  364. } // namespace Parser