PageRenderTime 29ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/typemapgenerator/mpiparser.rb

https://bitbucket.org/hkaiser/libgeodecomp
Ruby | 501 lines | 331 code | 81 blank | 89 comment | 29 complexity | b16a5eb92bdb30751b3bfeec2a4ea1a5 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1
  1. # -*- coding: utf-8 -*-
  2. # Copyright (C) 2006,2007 Andreas Schaefer <gentryx@gmx.de>
  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 2 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, but
  10. # WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # 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, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. # 02110-1301 USA.
  18. require 'rexml/document'
  19. require 'set'
  20. require 'datatype'
  21. require 'pp'
  22. # This class is responsible for extracting all the information we need
  23. # from Doxygen's XML output.
  24. class MPIParser
  25. attr_accessor :datatype_map
  26. attr_accessor :type_hierarchy_closure
  27. # All doxygen xml files are expected in path, setting sloppy to true
  28. # will allow you to create partial typemaps (which simply ignore
  29. # members for which no MPI datatype could be generated). Be aware
  30. # that this won't work if the to be excluded members are of a
  31. # template type for whitch typemaps will be generated using other
  32. # parameters. Yeah, it's complicated.
  33. def initialize(path="../../../trunk/doc/xml", sloppy=false, namespace="")
  34. @path, @sloppy, @namespace = path, sloppy, namespace
  35. class_files = Dir.glob("#{@path}/*.xml")
  36. @xml_docs = { }
  37. class_files.each do |filename|
  38. doc = REXML::Document.new File.new(filename)
  39. @xml_docs[filename] = doc
  40. end
  41. @filename_cache = { }
  42. @xml_docs.each do |filename, doc|
  43. next if !is_class_declaration(filename)
  44. xpath = "doxygen/compounddef/compoundname"
  45. klass = parse_class_name(doc.elements[xpath].text)
  46. @filename_cache[klass] = filename
  47. end
  48. @datatype_map = Datatype.new
  49. @datatype_map.merge!(map_enums)
  50. classes_to_be_serialized = find_classes_to_be_serialized
  51. @type_hierarchy_closure = @datatype_map.keys.to_set +
  52. classes_to_be_serialized
  53. @all_classes = classes_to_be_serialized
  54. end
  55. # tries to resolve all datatypes given in classes to MPI type. For
  56. # those classes, whose MPI type could not be found in @datatype_map,
  57. # it'll try to create a new MPI type map specification.
  58. def resolve_forest(classes)
  59. classes = classes.sort
  60. resolved_classes = { }
  61. resolved_parents = { }
  62. topological_class_sortation = []
  63. @type_hierarchy_closure = @type_hierarchy_closure.union(classes)
  64. while classes.any?
  65. # puts " classes:"
  66. # print " "
  67. # pp classes
  68. num_unresolved = classes.size
  69. classes.each do |klass|
  70. resolve_class(klass, classes,
  71. resolved_classes, resolved_parents,
  72. topological_class_sortation)
  73. end
  74. # fail if no class could be resolved in the last iteration
  75. if num_unresolved == classes.size
  76. raise "incomplete type hierarchy: could not resolve any in " +
  77. classes.inspect
  78. end
  79. end
  80. headers = topological_class_sortation.map { |klass| find_header(klass) }
  81. return [resolved_classes, resolved_parents,
  82. @datatype_map, topological_class_sortation, headers]
  83. end
  84. def template_parameters(klass)
  85. xpath = "doxygen/compounddef/templateparamlist/param/declname"
  86. doc = @xml_docs[@filename_cache[klass]]
  87. template_params = []
  88. doc.elements.each(xpath) do |spec|
  89. template_params.push spec.text
  90. end
  91. return template_params
  92. end
  93. def used_template_parameters(klass)
  94. # puts "used_template_parameters(#{klass})"
  95. params = []
  96. klass =~ /^(#@namespace::|)(.+)/
  97. class_name = $2
  98. @all_classes.each do |c|
  99. c_template_params = template_parameters(c)
  100. members = get_members(c)
  101. members.each do |name, spec|
  102. if spec[:type] =~ /^(#@namespace::|)#{class_name}<(.+)>/
  103. # this will fail for constructs like Foo<Bar<int,int>,int>
  104. values = $2.split(",")
  105. values.map! { |v| v.strip }
  106. res = values.any? do |v|
  107. c_template_params.include?(v)
  108. end
  109. params.push values if !res
  110. end
  111. end
  112. end
  113. return params.sort.uniq
  114. end
  115. def map_template_parameters(members, template_params, values)
  116. param_map = { }
  117. values.size.times do |i|
  118. param_map[template_params[i]] = values[i]
  119. end
  120. new_members = { }
  121. members.each do |name, spec|
  122. new_spec = spec.clone
  123. param_map.each do |param, val|
  124. new_spec[:type] = new_spec[:type].gsub(/#{param}/, val)
  125. end
  126. new_members[name] = new_spec
  127. end
  128. return new_members
  129. end
  130. # wraps the resolution process (mapping of members) for a single class.
  131. def resolve_class(klass, classes,
  132. resolved_classes, resolved_parents,
  133. topological_class_sortation)
  134. begin
  135. members = get_members(klass)
  136. parents = get_parents(klass)
  137. template_params = template_parameters(klass)
  138. # puts "----------------------------------"
  139. # puts "resolve_class(#{klass})"
  140. # puts "members"
  141. # pp members
  142. # puts "parents"
  143. # pp parents
  144. # puts "resolved_classes"
  145. # pp resolved_classes
  146. # puts "template_params"
  147. # pp template_params
  148. # puts "----------------------------------"
  149. # puts
  150. if template_params.empty?
  151. resolve_class_simple(klass, members, parents,
  152. classes,
  153. resolved_classes, resolved_parents,
  154. topological_class_sortation)
  155. else
  156. used_params = used_template_parameters(klass)
  157. # puts "used_params"
  158. # pp used_params
  159. # puts
  160. used_params.each do |values|
  161. new_members =
  162. map_template_parameters(members, template_params, values)
  163. new_class = "#{klass}<#{values.join(",")} >"
  164. resolve_class_simple(new_class, new_members, parents,
  165. classes,
  166. resolved_classes, resolved_parents,
  167. topological_class_sortation)
  168. end
  169. end
  170. classes.delete(klass)
  171. rescue Exception => e
  172. # puts "failed with"
  173. # pp e
  174. # puts e.backtrace
  175. end
  176. # puts
  177. end
  178. def prune_unresolvable_members(members)
  179. ret = {}
  180. members.each do |klass, spec|
  181. next if @sloppy && exclude?(spec[:type])
  182. ret[klass] = spec
  183. end
  184. return ret
  185. end
  186. # fixme: refactor this shitty interface
  187. def resolve_class_simple(klass, members, parents, classes,
  188. resolved_classes, resolved_parents,
  189. topological_class_sortation)
  190. actual_members = prune_unresolvable_members(members)
  191. member_map = map_types_to_MPI_Datatypes(actual_members)
  192. parents_map = map_parent_types_to_MPI_Datatypes(parents)
  193. classes.delete klass
  194. topological_class_sortation.push klass
  195. @datatype_map[klass] = Datatype.cpp_to_mpi(klass, partial?(members))
  196. resolved_classes[klass] = member_map
  197. resolved_parents[klass] = parents_map
  198. end
  199. # checks if some class members will be excluded from serialization.
  200. def partial?(members)
  201. members.each do |klass, spec|
  202. return true if exclude?(spec[:type])
  203. end
  204. return false
  205. end
  206. # returns a map consisting of all member variables listed in the
  207. # class' doxygen .xml file.
  208. def get_members(klass)
  209. members = { }
  210. sweep_all_members(klass) do |member|
  211. klass, spec = parse_member(member)
  212. members[klass] = spec
  213. end
  214. return members
  215. end
  216. # returns an array containing all parent classes.
  217. def get_parents(klass)
  218. filename = class_to_filename(klass)
  219. doc = @xml_docs[filename]
  220. xpath = "doxygen/compounddef/basecompoundref"
  221. parents = []
  222. doc.elements.each(xpath) do |member|
  223. parents.push member.text
  224. end
  225. return parents
  226. end
  227. def lookup_type(type)
  228. return @datatype_map[type] || @datatype_map["#{@namespace}::#{type}"]
  229. end
  230. # tries to map all members to mpi datatypes (using datatype_map as a
  231. # dictionary). Returns nil if a type could not be found.
  232. def map_types_to_MPI_Datatypes(members)
  233. resolved = { }
  234. members.each do |name, map_orig|
  235. lookup = lookup_type(map_orig[:type])
  236. unless lookup
  237. name1 = map_orig[:type]
  238. name2 = "#{@namespace}::#{map_orig[:type]}"
  239. raise "could not resolve member #{name1} or #{name2}"
  240. end
  241. map = map_orig.clone
  242. map[:type] = lookup
  243. resolved[name] = map
  244. end
  245. return resolved
  246. end
  247. # tries to map all parent types to mpi datatypes. Returns nil if a
  248. # type could not be found.
  249. def map_parent_types_to_MPI_Datatypes(parents)
  250. resolved = { }
  251. parents.each do |name|
  252. lookup = lookup_type(name)
  253. unless lookup
  254. raise "could not resolve parent #{name} or #{@namespace + "::" + name}"
  255. end
  256. resolved[name] = lookup
  257. end
  258. return resolved
  259. end
  260. def template_basename(klass)
  261. klass =~ /([^<]+)(<.+>)*/
  262. $1
  263. end
  264. # Determine if a member variable may be excluded since we're
  265. # performing sloppy parsing and we're not able to create a datatype map ourselves.
  266. def exclude?(klass)
  267. # for exclusion, look for template name (without parameter list), too
  268. stripped = template_basename(klass)
  269. return @sloppy &&
  270. !@type_hierarchy_closure.include?(klass) &&
  271. !@type_hierarchy_closure.include?(stripped)
  272. end
  273. # is liable for extracting member information from the belonging XML node.
  274. def parse_member(member)
  275. name = member.elements["name"].text
  276. spec = {
  277. :type => extract_type(member),
  278. :cardinality => resolve_cardinality(member)
  279. }
  280. return [name, spec]
  281. end
  282. def extract_type(member)
  283. type_elem = member.elements["type"]
  284. raw_type =
  285. (type_elem.elements["ref"].nil? ? "" : type_elem.elements["ref"].text) +
  286. (type_elem.has_text? ? type_elem.text : "")
  287. return parse_class_name(raw_type)
  288. end
  289. # gathers the cardinality for a member. It distinguishes simple
  290. # members (e.g. "int foo") from arrays (e.g. "int bar[Doedel]").
  291. # Cannot handle arrays with non-symbolic width (e.g. "int bar[69]"),
  292. # and I refuse an implementation until we have a use case for that.
  293. def resolve_cardinality(member)
  294. argsString = member.elements["argsstring"]
  295. # easy case: non-array member
  296. return 1 unless argsString.has_text?
  297. # more difficult: array members...
  298. raise "illegal cardinality" unless argsString.text =~ /\[(.+)\]/
  299. # numeric constant as array size:
  300. return $1.to_i if argsString.text =~ /\[(\d+)\]/
  301. # gotta search a little longer for symbolic sizes:
  302. member_id = member.attributes["id"]
  303. cardinality_id = resolve_cardinality_id(member_id)
  304. return resolve_cardinality_declaration(cardinality_id)
  305. end
  306. # Doxygen assigns items an ID. The cardinality of an array member is
  307. # an item. Unfortunately Doxygen's XML output in files named
  308. # "classXXX.xml" lacks such a reference ID. Nevertheless, we can dig
  309. # for it in the files "XXX_8h.xml".
  310. def resolve_cardinality_id(member_id)
  311. unless member_id =~ /class(.+)_.+/
  312. raise "illegal member ID"
  313. end
  314. klass = $1.downcase
  315. filename = "#{@path}/#{klass}_8h.xml"
  316. doc = @xml_docs[filename]
  317. codeline = nil
  318. doc.elements.each("doxygen/compounddef/programlisting/codeline") do |line|
  319. codeline = line if line.attributes["refid"] == member_id
  320. end
  321. cardinality = nil
  322. codeline.elements.each("highlight") do |elem|
  323. node = elem.find { |elem| elem == "[" }
  324. next unless node
  325. cardinality = node.next_sibling_node.attributes["refid"]
  326. end
  327. throw "failed to find cardinality for member_id #{member_id}" unless cardinality
  328. return cardinality
  329. end
  330. # uses the ID to identify the bit of code that makes up the
  331. # cardinality of an array.
  332. def resolve_cardinality_declaration(cardinality_id)
  333. sweep_all_classes do |klass, member|
  334. if member.attributes["id"] == cardinality_id
  335. name = member.elements["name"].text
  336. return "#{klass}::#{name}"
  337. end
  338. end
  339. raise "cardinality declaration not found"
  340. end
  341. # locates all enumeration types contained in classes.
  342. def find_enums
  343. enums = []
  344. sweep_all_classes do |klass, member|
  345. if member.attributes["kind"] == "enum"
  346. enums.push member.elements["name"].text
  347. end
  348. end
  349. return enums
  350. end
  351. # creates a mapping of all enumeration types to the corresponding
  352. # int type for MPI. C++ handles enumeration variables like integers,
  353. # and so does MPI.
  354. def map_enums
  355. ret = { }
  356. int_MPI_type = lookup_type("int")
  357. find_enums.each { |enum| ret[enum] = int_MPI_type }
  358. return ret
  359. end
  360. # wraps the iteration though all XML class definitions and their
  361. # contained class members.
  362. def sweep_all_classes
  363. @xml_docs.each do |filename, doc|
  364. next if !is_class_declaration(filename)
  365. raw_name = doc.elements["doxygen/compounddef/compoundname"].text
  366. klass = parse_class_name(raw_name)
  367. doc.elements.each("doxygen/compounddef/sectiondef/memberdef") do |member|
  368. yield(klass, member)
  369. end
  370. end
  371. end
  372. # iterates though all members of klass class that are instance specific variables.
  373. def sweep_all_members(klass, kind="variable")
  374. filename = class_to_filename(klass)
  375. doc = @xml_docs[filename]
  376. xpath = "doxygen/compounddef/sectiondef/memberdef[@kind='#{kind}'][@static='no']"
  377. doc.elements.each(xpath) do |member|
  378. yield member
  379. end
  380. end
  381. # locate the header filename
  382. def find_header(klass)
  383. begin
  384. find_header_simple(klass)
  385. rescue Exception => e
  386. if klass =~ /<.+>/
  387. find_header_simple(template_basename(klass))
  388. else
  389. raise e
  390. end
  391. end
  392. end
  393. def find_header_simple(klass)
  394. sweep_all_members(klass) do |member|
  395. header = member.elements["location"].attributes["file"]
  396. return header if header
  397. end
  398. raise "no header found for class #{klass}"
  399. end
  400. def find_classes_to_be_serialized
  401. ret = Set.new
  402. sweep_all_classes do |klass, member|
  403. if member.attributes["kind"] == "friend" &&
  404. member.elements["name"].text == "Typemaps"
  405. ret.add klass
  406. end
  407. end
  408. return ret
  409. end
  410. def is_class_declaration(filename)
  411. (filename =~ /\/class/)
  412. end
  413. def class_to_filename(klass)
  414. @filename_cache[klass]
  415. end
  416. def parse_class_name(klass)
  417. ret = klass.gsub(/ /, "")
  418. return ret.gsub(/>/, " >")
  419. end
  420. end