nitdoc: Extend the model to help the importation from Doxygen.
[nit.git] / src / neo.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Save and load a `Model` to/from a Neo4j graph.
16 #
17 # Nit models are composed by MEntities.
18 # This module creates NeoNode for each MEntity found in a `Model` and save them
19 # into Neo4j database.
20 #
21 # SEE: `Neo4jClient`
22 #
23 # NeoNodes can also be translated back to MEntities to rebuild a Nit `Model`.
24 #
25 # Structure of the nit `Model` in the graph:
26 #
27 # Note : Any null or empty attribute will not be saved in the database.
28 #
29 # For any `MEntity` (in addition to specific data):
30 #
31 # * labels: model name (`model_name`) and `MEntity`.
32 # * `name`: short (unqualified) name.
33 # * `mdoc`: JSON array representing the associated Markdown documentation
34 # (one item by line).
35 #
36 # Note : All nodes described here are MEntities.
37 #
38 # `MProject`
39 #
40 # * labels: `MProject`, `model_name` and `MEntity`.
41 # * `(:MProject)-[:ROOT]->(:MGroup)`: root of the group tree.
42 #
43 # `MGroup`
44 #
45 # * labels: `MGroup`, `model_name` and `MEntity`.
46 # * `full_name`: fully qualified name.
47 # * `(:MGroup)-[:PROJECT]->(:MProject)`: associated project.
48 # * `(:MGroup)-[:PARENT]->(:MGroup)`: parent group. Does not exist for the root
49 # group.
50 # * `(:MGroup)-[:DECLARES]->(:MModule)`: modules that are direct children of
51 # this group.
52 # * `(:MGroup)-[:NESTS]->(:MGroup)`: nested groups that are direct children of
53 # this group.
54 #
55 # `MModule`
56 #
57 # * labels: `MModule`, `model_name` and `MEntity`.
58 # * `full_name`: fully qualified name.
59 # * `location`: origin of the definition. SEE: `Location.to_s`
60 # * `(:MModule)-[:IMPORTS]->(:MModule)`: modules that are imported directly.
61 # * `(:MModule)-[:INTRODUCES]->(:MClass)`: all by classes introduced by this
62 # module.
63 # * `(:MModule)-[:DEFINES]->(:MClassDef)`: all class definitons contained in
64 # this module.
65 #
66 # `MClass`
67 #
68 # * labels: `MClass`, `model_name` and `MEntity`.
69 # * `full_name`: fully qualified name.
70 # * `kind`: kind of the class (`interface`, `abstract class`, etc.)
71 # * `visibility`: visibility of the class.
72 # * `parameter_names`: JSON array listing the name of each formal generic
73 # parameter (in order of declaration).
74 # * `(:MClass)-[:CLASSTYPE]->(:MClassType)`: SEE: `MClass.mclass_type`
75 #
76 # Arguments in the `CLASSTYPE` are named following the `parameter_names`
77 # attribute of the `MClassDef` that introduces the class. A class definition
78 # introduces a class if and only if it has this class as `MCLASS` and
79 # has `is_intro` set to `true`.
80 #
81 # `MClassDef`
82 #
83 # * labels: `MClassDef`, `model_name` and `MEntity`.
84 # * `location`: origin of the definition. SEE: `Location.to_s`
85 # * `(:MClassDef)-[:BOUNDTYPE]->(:MClassType)`: bounded type associated to the
86 # classdef.
87 # * `(:MClassDef)-[:MCLASS]->(:MClass)`: associated `MClass`.
88 # * `(:MClassDef)-[:INTRODUCES]->(:MProperty)`: all properties introduced by
89 # the classdef.
90 # * `(:MClassDef)-[:DECLARES]->(:MPropDef)`: all property definitions in the
91 # classdef (introductions and redefinitions).
92 # * `(:MClassDef)-[:INHERITS]->(:MClassType)`: all declared super-types
93 #
94 # `MProperty`
95 #
96 # * labels: `MProperty`, `model_name` and `MEntity`. Must also have `MMethod`,
97 # `MAttribute` or `MVirtualTypeProp`, depending on the class of the represented
98 # entity.
99 # * `full_name`: fully qualified name.
100 # * `visibility`: visibility of the property.
101 # * `is_init`: Indicates if the property is a constructor. Exists only if the
102 # node is a `MMethod`.
103 # * `(:MProperty)-[:INTRO_CLASSDEF]->(:MClassDef)`: classdef that introduces
104 # the property.
105 #
106 # `MPropDef`
107 #
108 # * labels: `MPropDef`, `model_name` and `MEntity`. Must also have `MMethodDef`,
109 # `MAttributeDef` or `MVirtualTypeDef`, depending on the class of the
110 # represented entity.
111 # * `location`: origin of the definition. SEE: `Location.to_s`.
112 # * `(:MPropDef)-[:DEFINES]->(:MProperty)`: associated property.
113 #
114 # Additional attributes and relationship for `MMethodDef`:
115 #
116 # * `is_abstract`: Is the method definition abstract?
117 # * `is_intern`: Is the method definition intern?
118 # * `is_extern`: Is the method definition extern?
119 # * `(:MMethodDef)-[:SIGNATURE]->(:MSignature)`: signature attached to the
120 # property definition.
121 #
122 # Additional relationship for `MAttributeDef`:
123 #
124 # * `(:MAttributeDef)-[:TYPE]->(:MType)`: static type of the attribute,
125 # if specified.
126 #
127 # Additional relationship for `MVirtualTypeDef`:
128 #
129 # * `(:MVirtualTypeDef)-[:BOUND]->(:MType)`: type to which the virtual type
130 # is bound in this definition. Exists only if this definition bound the virtual
131 # type to an effective type.
132 #
133 # `MType`
134 #
135 # * labels: `MType`, `model_name` and `MEntity`. Must also have `MClassType`,
136 # `MNullableType`, `MVirtualType`, `MRawType` or `MSignature`, depending on the
137 # class of the represented entity.
138 #
139 # Additional label and relationships for `MClassType`:
140 #
141 # * If it is a `MGenericType`, also has the `MGenericType` label.
142 # * `(:MClassType)-[:CLASS]->(:MClass)`: SEE: `MClassType.mclass`
143 # * `(:MClassType)-[:ARGUMENT]->(:MType)`: type arguments.
144 #
145 # Arguments are named following the `parameter_names` attribute of the
146 # `MClass` referred by `CLASS`.
147 #
148 # Additional relationship for `MVirtualType`:
149 #
150 # * `(:MVirtualType)-[:PROPERTY]->(:MProperty)`: associated property that
151 # determines the type (usually a `MVirtualTypeProp`).
152 #
153 # Additional attribute and relationship for `MParameterType`:
154 #
155 # * `rank`: position of the parameter (0 for the first parameter).
156 # * `(:MParameterType)-[:CLASS]->(:MClass)`: generic class where the parameter
157 # belong.
158 #
159 # Additional relationship for `MNullableType`:
160 #
161 # * `(:MNullableType)-[:TYPE]->(:MType)`: base type of the nullable type.
162 #
163 # Additional attribute and relationship for `MRawType`:
164 #
165 # * `text`: JSON array of the parts’ text.
166 # * `(:MRawType)-[:LINK]->(:MTypePart)`: the parts that link to another entity.
167 #
168 # Additional attribute and relationships for `MSignature`:
169 #
170 # * `parameter_names`: JSON array representing the list of the parameter names.
171 # * `(:MSignature)-[:PARAMETER]->(:MParameter)`: parameters.
172 # * `(:MSignature)-[:RETURNTYPE]->(:MType)`: return type. Does not exist for
173 # procedures.
174 #
175 # In order to maintain the correct parameters order, each `MSignature` node
176 # contains an array of parameter names corresponding to the parameter order in
177 # the signature.
178 #
179 # For example, if the source code contains:
180 #
181 # fun foo(a: A, b: B, c: C)
182 #
183 # The `MSignature` node will contain a property
184 # `parameter_names = ["a", "b", "c"]` so the MSignature can be reconstructed
185 # with the parameters in the correct order.
186 #
187 # `MParameter`
188 #
189 # * labels: `MParameter`, `model_name` and `MEntity`.
190 # * `is_vararg`: Is the parameter a vararg?
191 # * `rank`: position of the parameter (0 for the first parameter).
192 # * `(:MParameter)-[:TYPE]->(:MType)`: static type of the parameter.
193 #
194 # MParameters are also ranked by their position in the corresponding signature.
195 # Rank 0 for the first parameter, 1 for the next one and etc.
196 #
197 # `MTypePart`
198 #
199 # * labels: `MTypePart`, `model_name` and `MEntity`.
200 # * `rank`: position in the `MRawType`.
201 # * `(:MTypePart)-[:TARGET]->(:MEntity)`: the target of the link.
202 module neo
203
204 import doc::model_ext
205 import neo4j
206 import toolcontext
207
208 # Helper class that can save and load a `Model` into a Neo4j database.
209 class NeoModel
210
211 # The model name.
212 #
213 # Because we use only one Neo4j instance to store all the models,
214 # we need to mark their appartenance to a particular model and avoid loading all models.
215 #
216 # The name is used as a Neo label on each created nodes and used to load nodes from base.
217 var model_name: String
218
219 # The toolcontext used to init the `NeoModel` tool.
220 var toolcontext: ToolContext
221
222 # The Neo4j `client` used to communicate with the Neo4j instance.
223 var client: Neo4jClient
224
225 # Fill `model` using base pointed by `client`.
226 fun load(model: Model): Model do
227 toolcontext.info("Locate all mentities...", 1)
228 var nodes = client.nodes_with_label(model_name)
229
230 toolcontext.info("Preload nodes...", 1)
231 pull_all_nodes(nodes)
232 toolcontext.info("Preload edges...", 1)
233 pull_all_edges(nodes)
234
235 toolcontext.info("Build model...", 1)
236 nodes = client.nodes_with_labels([model_name, "MProject"])
237 for node in nodes do to_mproject(model, node)
238 nodes = client.nodes_with_labels([model_name, "MGroup"])
239 for node in nodes do to_mgroup(model, node)
240 nodes = client.nodes_with_labels([model_name, "MModule"])
241 for node in nodes do to_mmodule(model, node)
242 nodes = client.nodes_with_labels([model_name, "MClass"])
243 for node in nodes do to_mclass(model, node)
244 nodes = client.nodes_with_labels([model_name, "MClassDef"])
245 for node in nodes do to_mclassdef(model, node)
246 nodes = client.nodes_with_labels([model_name, "MProperty"])
247 for node in nodes do to_mproperty(model, node)
248 nodes = client.nodes_with_labels([model_name, "MPropDef"])
249 for node in nodes do to_mpropdef(model, node)
250 return model
251 end
252
253 # Save `model` in the base pointed by `client`.
254 fun save(model: Model) do
255 var nodes = collect_model_nodes(model)
256 toolcontext.info("Save {nodes.length} nodes...", 1)
257 push_all(nodes)
258 var edges = collect_model_edges(model)
259 toolcontext.info("Save {edges.length} edges...", 1)
260 push_all(edges)
261 end
262
263 # Save `neo_entities` in base using batch mode.
264 private fun push_all(neo_entities: Collection[NeoEntity]) do
265 var batch = new NeoBatch(client)
266 var len = neo_entities.length
267 var sum = 0
268 var i = 1
269 for nentity in neo_entities do
270 batch.save_entity(nentity)
271 if i == batch_max_size then
272 do_batch(batch)
273 sum += batch_max_size
274 toolcontext.info(" {sum * 100 / len}% done", 1)
275 batch = new NeoBatch(client)
276 i = 1
277 else
278 i += 1
279 end
280 end
281 do_batch(batch)
282 end
283
284 # Load content for all `nodes` from base.
285 #
286 # Content corresponds to properties and labels that are loaded in batch mode.
287 private fun pull_all_nodes(nodes: Collection[NeoNode]) do
288 var batch = new NeoBatch(client)
289 var len = nodes.length
290 var sum = 0
291 var i = 1
292 for node in nodes do
293 batch.load_node(node)
294 if i == batch_max_size then
295 do_batch(batch)
296 sum += batch_max_size
297 toolcontext.info(" {sum * 100 / len}% done", 1)
298 batch = new NeoBatch(client)
299 i = 1
300 else
301 i += 1
302 end
303 end
304 do_batch(batch)
305 end
306
307 # Load all edges from base linked to `nodes`.
308 #
309 # Edges are loaded in batch mode.
310 private fun pull_all_edges(nodes: Collection[NeoNode]) do
311 var batch = new NeoBatch(client)
312 var len = nodes.length
313 var sum = 0
314 var i = 1
315 for node in nodes do
316 batch.load_node_edges(node)
317 if i == batch_max_size then
318 do_batch(batch)
319 sum += batch_max_size
320 toolcontext.info(" {sum * 100 / len}% done", 1)
321 batch = new NeoBatch(client)
322 i = 1
323 else
324 i += 1
325 end
326 end
327 do_batch(batch)
328 end
329
330 # How many operation can be executed in one batch?
331 private var batch_max_size = 1000
332
333 # Execute `batch` and check for errors.
334 #
335 # Abort if `batch.execute` returns errors.
336 private fun do_batch(batch: NeoBatch) do
337 var errors = batch.execute
338 if not errors.is_empty then
339 print errors
340 exit(1)
341 end
342 end
343
344 # Collect all nodes from the current `model`.
345 private fun collect_model_nodes(model: Model): Collection[NeoNode] do
346 for mproject in model.mprojects do
347 to_node(mproject)
348 for mgroup in mproject.mgroups do to_node(mgroup)
349 end
350 return nodes.values
351 end
352
353 # Collect all edges from the current `model`.
354 #
355 # Actually collect all out_edges from all nodes.
356 private fun collect_model_edges(model: Model): Collection[NeoEdge] do
357 var edges = new HashSet[NeoEdge]
358 for node in nodes.values do edges.add_all(node.out_edges)
359 return edges
360 end
361
362 # Mentities associated to nodes.
363 private var mentities = new HashMap[NeoNode, MEntity]
364
365 # Nodes associated with MEntities.
366 private var nodes = new HashMap[MEntity, NeoNode]
367
368 # Get the `NeoNode` associated with `mentity`.
369 # `mentities` are stored locally to avoid duplication.
370 fun to_node(mentity: MEntity): NeoNode do
371 if nodes.has_key(mentity) then return nodes[mentity]
372 if mentity isa MProject then return mproject_node(mentity)
373 if mentity isa MGroup then return mgroup_node(mentity)
374 if mentity isa MModule then return mmodule_node(mentity)
375 if mentity isa MClass then return mclass_node(mentity)
376 if mentity isa MClassDef then return mclassdef_node(mentity)
377 if mentity isa MProperty then return mproperty_node(mentity)
378 if mentity isa MPropDef then return mpropdef_node(mentity)
379 if mentity isa MType then return mtype_node(mentity)
380 if mentity isa MParameter then return mparameter_node(mentity)
381 abort
382 end
383
384 # Get the `MEntity` associated with `node`.
385 fun to_mentity(model: Model, node: NeoNode): MEntity do
386 if node.labels.has("MProject") then return to_mproject(model, node)
387 if node.labels.has("MGroup") then return to_mgroup(model, node)
388 if node.labels.has("MModule") then return to_mmodule(model, node)
389 if node.labels.has("MClass") then return to_mclass(model, node)
390 if node.labels.has("MClassDef") then return to_mclassdef(model, node)
391 if node.labels.has("MProperty") then return to_mproperty(model, node)
392 if node.labels.has("MPropDef") then return to_mpropdef(model, node)
393 if node.labels.has("MType") then return to_mtype(model, node)
394 if node.labels.has("MParameter") then return to_mparameter(model, node)
395 abort
396 end
397
398 # Make a new `NeoNode` based on `mentity`.
399 private fun make_node(mentity: MEntity): NeoNode do
400 var node = new NeoNode
401 nodes[mentity] = node
402 node.labels.add "MEntity"
403 node.labels.add model_name
404 node["name"] = mentity.name
405 if mentity.mdoc != null then node["mdoc"] = new JsonArray.from(mentity.mdoc.content)
406 return node
407 end
408
409 # Build a `NeoNode` representing `mproject`.
410 private fun mproject_node(mproject: MProject): NeoNode do
411 var node = make_node(mproject)
412 node.labels.add "MProject"
413 var root = mproject.root
414 if root != null then
415 node.out_edges.add(new NeoEdge(node, "ROOT", to_node(root)))
416 end
417 return node
418 end
419
420 # Build a new `MProject` from a `node`.
421 #
422 # REQUIRE `node.labels.has("MProject")`
423 private fun to_mproject(model: Model, node: NeoNode): MProject do
424 if mentities.has_key(node) then return mentities[node].as(MProject)
425 assert node.labels.has("MProject")
426 var mproject = new MProject(node["name"].to_s, model)
427 mentities[node] = mproject
428 set_doc(node, mproject)
429 mproject.root = to_mgroup(model, node.out_nodes("ROOT").first)
430 return mproject
431 end
432
433 # Build a `NeoNode` representing `mgroup`.
434 private fun mgroup_node(mgroup: MGroup): NeoNode do
435 var node = make_node(mgroup)
436 node.labels.add "MGroup"
437 node["full_name"] = mgroup.full_name
438 var parent = mgroup.parent
439 node.out_edges.add(new NeoEdge(node, "PROJECT", to_node(mgroup.mproject)))
440 if parent != null then
441 node.out_edges.add(new NeoEdge(node, "PARENT", to_node(parent)))
442 end
443 for mmodule in mgroup.mmodules do
444 node.out_edges.add(new NeoEdge(node, "DECLARES", to_node(mmodule)))
445 end
446 for subgroup in mgroup.in_nesting.direct_smallers do
447 node.in_edges.add(new NeoEdge(node, "NESTS", to_node(subgroup)))
448 end
449 return node
450 end
451
452 # Build a new `MGroup` from a `node`.
453 #
454 # REQUIRE `node.labels.has("MGroup")`
455 private fun to_mgroup(model: Model, node: NeoNode): MGroup do
456 if mentities.has_key(node) then return mentities[node].as(MGroup)
457 assert node.labels.has("MGroup")
458 var mproject = to_mproject(model, node.out_nodes("PROJECT").first)
459 var parent: nullable MGroup = null
460 var out = node.out_nodes("PARENT")
461 if not out.is_empty then
462 parent = to_mgroup(model, out.first)
463 end
464 var mgroup = new MGroup(node["name"].to_s, mproject, parent)
465 mentities[node] = mgroup
466 set_doc(node, mgroup)
467 return mgroup
468 end
469
470 # Build a `NeoNode` representing `mmodule`.
471 private fun mmodule_node(mmodule: MModule): NeoNode do
472 var node = make_node(mmodule)
473 node.labels.add "MModule"
474 node["full_name"] = mmodule.full_name
475 node["location"] = mmodule.location.to_s
476 for parent in mmodule.in_importation.direct_greaters do
477 node.out_edges.add(new NeoEdge(node, "IMPORTS", to_node(parent)))
478 end
479 for mclass in mmodule.intro_mclasses do
480 node.out_edges.add(new NeoEdge(node, "INTRODUCES", to_node(mclass)))
481 end
482 for mclassdef in mmodule.mclassdefs do
483 node.out_edges.add(new NeoEdge(node, "DEFINES", to_node(mclassdef)))
484 end
485 return node
486 end
487
488 # Build a new `MModule` from a `node`.
489 #
490 # REQUIRE `node.labels.has("MModule")`
491 private fun to_mmodule(model: Model, node: NeoNode): MModule do
492 if mentities.has_key(node) then return mentities[node].as(MModule)
493 assert node.labels.has("MModule")
494 var ins = node.in_nodes("DECLARES")
495 var mgroup: nullable MGroup = null
496 if not ins.is_empty then
497 mgroup = to_mgroup(model, ins.first)
498 end
499 var name = node["name"].to_s
500 var location = to_location(node["location"].to_s)
501 var mmodule = new MModule(model, mgroup, name, location)
502 mentities[node] = mmodule
503 set_doc(node, mmodule)
504 var imported_mmodules = new Array[MModule]
505 for smod in node.out_nodes("IMPORTS") do
506 imported_mmodules.add to_mmodule(model, smod)
507 end
508 mmodule.set_imported_mmodules(imported_mmodules)
509 return mmodule
510 end
511
512 # Build a `NeoNode` representing `mclass`.
513 private fun mclass_node(mclass: MClass): NeoNode do
514 var node = make_node(mclass)
515 node.labels.add "MClass"
516 node["full_name"] = mclass.full_name
517 node["kind"] = mclass.kind.to_s
518 node["visibility"] = mclass.visibility.to_s
519 if not mclass.mparameters.is_empty then
520 var parameter_names = new Array[String]
521 for p in mclass.mparameters do parameter_names.add(p.name)
522 node["parameter_names"] = new JsonArray.from(parameter_names)
523 end
524 node.out_edges.add(new NeoEdge(node, "CLASSTYPE", to_node(mclass.mclass_type)))
525 return node
526 end
527
528 # Build a new `MClass` from a `node`.
529 #
530 # REQUIRE `node.labels.has("MClass")`
531 private fun to_mclass(model: Model, node: NeoNode): MClass do
532 if mentities.has_key(node) then return mentities[node].as(MClass)
533 assert node.labels.has("MClass")
534 var mmodule = to_mmodule(model, node.in_nodes("INTRODUCES").first)
535 var name = node["name"].to_s
536 var kind = to_kind(node["kind"].to_s)
537 var visibility = to_visibility(node["visibility"].to_s)
538 var parameter_names = new Array[String]
539 if node.has_key("parameter_names") then
540 for e in node["parameter_names"].as(JsonArray) do
541 parameter_names.add e.to_s
542 end
543 end
544 var mclass = new MClass(mmodule, name, parameter_names, kind, visibility)
545 mentities[node] = mclass
546 set_doc(node, mclass)
547 return mclass
548 end
549
550 # Build a `NeoNode` representing `mclassdef`.
551 private fun mclassdef_node(mclassdef: MClassDef): NeoNode do
552 var node = make_node(mclassdef)
553 node.labels.add "MClassDef"
554 node["location"] = mclassdef.location.to_s
555 node.out_edges.add(new NeoEdge(node, "BOUNDTYPE", to_node(mclassdef.bound_mtype)))
556 node.out_edges.add(new NeoEdge(node, "MCLASS", to_node(mclassdef.mclass)))
557 for mproperty in mclassdef.intro_mproperties do
558 node.out_edges.add(new NeoEdge(node, "INTRODUCES", to_node(mproperty)))
559 end
560 for mpropdef in mclassdef.mpropdefs do
561 node.out_edges.add(new NeoEdge(node, "DECLARES", to_node(mpropdef)))
562 end
563 for sup in mclassdef.supertypes do
564 node.out_edges.add(new NeoEdge(node, "INHERITS", to_node(sup)))
565 end
566 return node
567 end
568
569 # Build a new `MClassDef` from a `node`.
570 #
571 # REQUIRE `node.labels.has("MClassDef")`
572 private fun to_mclassdef(model: Model, node: NeoNode): MClassDef do
573 if mentities.has_key(node) then return mentities[node].as(MClassDef)
574 assert node.labels.has("MClassDef")
575 var mmodule = to_mmodule(model, node.in_nodes("DEFINES").first)
576 var mtype = to_mtype(model, node.out_nodes("BOUNDTYPE").first).as(MClassType)
577 var location = to_location(node["location"].to_s)
578 var mclassdef = new MClassDef(mmodule, mtype, location)
579 mentities[node] = mclassdef
580 set_doc(node, mclassdef)
581 var supertypes = new Array[MClassType]
582 for sup in node.out_nodes("INHERITS") do
583 supertypes.add to_mtype(model, sup).as(MClassType)
584 end
585 mclassdef.set_supertypes(supertypes)
586 mclassdef.add_in_hierarchy
587 return mclassdef
588 end
589
590 # Build a `NeoNode` representing `mproperty`.
591 private fun mproperty_node(mproperty: MProperty): NeoNode do
592 var node = make_node(mproperty)
593 node.labels.add "MProperty"
594 node["full_name"] = mproperty.full_name
595 node["visibility"] = mproperty.visibility.to_s
596 if mproperty isa MMethod then
597 node.labels.add "MMethod"
598 node["is_init"] = mproperty.is_init
599 else if mproperty isa MAttribute then
600 node.labels.add "MAttribute"
601 else if mproperty isa MVirtualTypeProp then
602 node.labels.add "MVirtualTypeProp"
603 end
604 node.out_edges.add(new NeoEdge(node, "INTRO_CLASSDEF", to_node(mproperty.intro_mclassdef)))
605 return node
606 end
607
608 # Build a new `MProperty` from a `node`.
609 #
610 # REQUIRE `node.labels.has("MProperty")`
611 private fun to_mproperty(model: Model, node: NeoNode): MProperty do
612 if mentities.has_key(node) then return mentities[node].as(MProperty)
613 assert node.labels.has("MProperty")
614 var intro_mclassdef = to_mclassdef(model, node.out_nodes("INTRO_CLASSDEF").first)
615 var name = node["name"].to_s
616 var visibility = to_visibility(node["visibility"].to_s)
617 var mprop: nullable MProperty = null
618 if node.labels.has("MMethod") then
619 mprop = new MMethod(intro_mclassdef, name, visibility)
620 mprop.is_init = node["is_init"].as(Bool)
621 else if node.labels.has("MAttribute") then
622 mprop = new MAttribute(intro_mclassdef, name, visibility)
623 else if node.labels.has("MVirtualTypeProp") then
624 mprop = new MVirtualTypeProp(intro_mclassdef, name, visibility)
625 end
626 if mprop == null then
627 print "not yet implemented to_mproperty for {node.labels.join(",")}"
628 abort
629 end
630 mentities[node] = mprop
631 set_doc(node, mprop)
632 return mprop
633 end
634
635 # Build a `NeoNode` representing `mpropdef`.
636 private fun mpropdef_node(mpropdef: MPropDef): NeoNode do
637 var node = make_node(mpropdef)
638 node.labels.add "MPropDef"
639 node["location"] = mpropdef.location.to_s
640 node.out_edges.add(new NeoEdge(node, "DEFINES", to_node(mpropdef.mproperty)))
641 if mpropdef isa MMethodDef then
642 node.labels.add "MMethodDef"
643 node["is_abstract"] = mpropdef.is_abstract
644 node["is_intern"] = mpropdef.is_intern
645 node["is_extern"] = mpropdef.is_extern
646 var msignature = mpropdef.msignature
647 if msignature != null then
648 node.out_edges.add(new NeoEdge(node, "SIGNATURE", to_node(msignature)))
649 end
650 else if mpropdef isa MAttributeDef then
651 node.labels.add "MAttributeDef"
652 var static_mtype = mpropdef.static_mtype
653 if static_mtype != null then
654 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(static_mtype)))
655 end
656 else if mpropdef isa MVirtualTypeDef then
657 node.labels.add "MVirtualTypeDef"
658 var bound = mpropdef.bound
659 if bound != null then
660 node.out_edges.add(new NeoEdge(node, "BOUND", to_node(bound)))
661 end
662 end
663 return node
664 end
665
666 # Build a new `MPropDef` from a `node`.
667 #
668 # REQUIRE `node.labels.has("MPropDef")`
669 private fun to_mpropdef(model: Model, node: NeoNode): MPropDef do
670 if mentities.has_key(node) then return mentities[node].as(MPropDef)
671 assert node.labels.has("MPropDef")
672 var mclassdef = to_mclassdef(model, node.in_nodes("DECLARES").first)
673 var mproperty = to_mproperty(model, node.out_nodes("DEFINES").first)
674 var location = to_location(node["location"].to_s)
675 var mpropdef: nullable MPropDef = null
676 if node.labels.has("MMethodDef") then
677 mpropdef = new MMethodDef(mclassdef, mproperty.as(MMethod), location)
678 mpropdef.is_abstract = node["is_abstract"].as(Bool)
679 mpropdef.is_intern = node["is_intern"].as(Bool)
680 mpropdef.is_extern = node["is_extern"].as(Bool)
681 mentities[node] = mpropdef
682 mpropdef.msignature = to_mtype(model, node.out_nodes("SIGNATURE").first).as(MSignature)
683 else if node.labels.has("MAttributeDef") then
684 mpropdef = new MAttributeDef(mclassdef, mproperty.as(MAttribute), location)
685 mentities[node] = mpropdef
686 var static_mtype = node.out_nodes("TYPE")
687 if not static_mtype.is_empty then mpropdef.static_mtype = to_mtype(model, static_mtype.first)
688 else if node.labels.has("MVirtualTypeDef") then
689 mpropdef = new MVirtualTypeDef(mclassdef, mproperty.as(MVirtualTypeProp), location)
690 mentities[node] = mpropdef
691 var bound = node.out_nodes("BOUND")
692 if not bound.is_empty then mpropdef.bound = to_mtype(model, bound.first)
693 end
694 if mpropdef == null then
695 print "not yet implemented to_mpropdef for {node.labels.join(",")}"
696 abort
697 end
698 set_doc(node, mpropdef)
699 return mpropdef
700 end
701
702 # Build a `NeoNode` representing `mtype`.
703 private fun mtype_node(mtype: MType): NeoNode do
704 var node = make_node(mtype)
705 node.labels.add "MType"
706 if mtype isa MClassType then
707 node.labels.add "MClassType"
708 node.out_edges.add(new NeoEdge(node, "CLASS", to_node(mtype.mclass)))
709 for arg in mtype.arguments do
710 node.out_edges.add(new NeoEdge(node, "ARGUMENT", to_node(arg)))
711 end
712 if mtype isa MGenericType then
713 node.labels.add "MGenericType"
714 end
715 else if mtype isa MVirtualType then
716 node.labels.add "MVirtualType"
717 node.out_edges.add(new NeoEdge(node, "PROPERTY", to_node(mtype.mproperty)))
718 else if mtype isa MParameterType then
719 node.labels.add "MParameterType"
720 node["rank"] = mtype.rank
721 node.out_edges.add(new NeoEdge(node, "CLASS", to_node(mtype.mclass)))
722 else if mtype isa MNullableType then
723 node.labels.add "MNullableType"
724 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mtype.mtype)))
725 else if mtype isa MSignature then
726 node.labels.add "MSignature"
727 var names = new JsonArray
728 var rank = 0
729 for mparameter in mtype.mparameters do
730 names.add mparameter.name
731 var pnode = mparameter_node(mparameter)
732 pnode["rank"] = rank
733 node.out_edges.add(new NeoEdge(node, "PARAMETER", pnode))
734 rank += 1
735 end
736 if not names.is_empty then node["parameter_names"] = names
737 var return_mtype = mtype.return_mtype
738 if return_mtype != null then
739 node.out_edges.add(new NeoEdge(node, "RETURNTYPE", to_node(return_mtype)))
740 end
741 else if mtype isa MRawType then
742 node.labels.add "MRawType"
743 var text = new JsonArray
744 var rank = 0
745 for part in mtype.parts do
746 text.add part.text
747 if part.target != null then
748 var pnode = mtypepart_node(part)
749 pnode["rank"] = rank
750 node.out_edges.add(new NeoEdge(node, "LINK", pnode))
751 end
752 rank += 1
753 end
754 if not text.is_empty then node["text"] = text
755 end
756 return node
757 end
758
759 # Build a `NeoNode` representing `mtypepart`.
760 private fun mtypepart_node(mtypepart: MTypePart): NeoNode do
761 var node = make_node(mtypepart)
762 node.labels.add "MTypePart"
763 if mtypepart.target != null then
764 var target_node = to_node(mtypepart.target.as(not null))
765 node.out_edges.add(new NeoEdge(node, "TARGET", target_node))
766 end
767 return node
768 end
769
770 # Build a new `MType` from a `node`.
771 #
772 # REQUIRE `node.labels.has("MType")`
773 private fun to_mtype(model: Model, node: NeoNode): MType do
774 if mentities.has_key(node) then return mentities[node].as(MType)
775 assert node.labels.has("MType")
776 if node.labels.has("MClassType") then
777 var mclass = to_mclass(model, node.out_nodes("CLASS").first)
778 var args = new Array[MType]
779 for narg in node.out_nodes("ARGUMENT") do
780 args.add to_mtype(model, narg)
781 end
782 var mtype = mclass.get_mtype(args)
783 mentities[node] = mtype
784 return mtype
785 else if node.labels.has("MParameterType") then
786 var mclass = to_mclass(model, node.out_nodes("CLASS").first)
787 var rank = node["rank"].to_s.to_i
788 var mtype = mclass.mparameters[rank]
789 mentities[node] = mtype
790 return mtype
791 else if node.labels.has("MNullableType") then
792 var intype = to_mtype(model, node.out_nodes("TYPE").first)
793 var mtype = intype.as_nullable
794 mentities[node] = mtype
795 return mtype
796 else if node.labels.has("MVirtualType") then
797 var mproperty = to_mproperty(model, node.out_nodes("PROPERTY").first)
798 assert mproperty isa MVirtualTypeProp
799 var mtype = mproperty.mvirtualtype
800 mentities[node] = mtype
801 return mtype
802 else if node.labels.has("MSignature") then
803 # Get all param nodes
804 var mparam_nodes = new HashMap[String, MParameter]
805 for pnode in node.out_nodes("PARAMETER") do
806 var mparam = to_mparameter(model, pnode)
807 mparam_nodes[mparam.name] = mparam
808 end
809 # Load params in the good order
810 var mparam_names = node["parameter_names"]
811 var mparameters = new Array[MParameter]
812 if mparam_names isa JsonArray then
813 for mparam_name in mparam_names do
814 var mparam = mparam_nodes[mparam_name.to_s]
815 mparameters.add mparam
816 end
817 end
818 var return_mtype: nullable MType = null
819 var ret_nodes = node.out_nodes("RETURNTYPE")
820 if not ret_nodes.is_empty then
821 return_mtype = to_mtype(model, ret_nodes.first)
822 end
823 var mtype = new MSignature(mparameters, return_mtype)
824 mentities[node] = mtype
825 return mtype
826 else if node.labels.has("MRawType") then
827 var mtype = new MRawType(model)
828 var parts = node["text"]
829 if parts isa JsonArray then
830 for p in parts do
831 mtype.parts.add(new MTypePart(model, p.to_s, null))
832 end
833 for pnode in node.out_nodes("LINK") do
834 assert pnode.labels.has("MTypePart")
835 if not pnode.out_nodes("TARGET").is_empty then
836 var rank = pnode["rank"]
837 var target = to_mentity(model, pnode.out_nodes("TARGET").first)
838 assert rank isa Int
839 mtype.parts[rank] = mtype.parts[rank].link_to(target)
840 end
841 end
842 end
843 mentities[node] = mtype
844 return mtype
845 end
846 print "not yet implemented to_mtype for {node.labels.join(",")}"
847 abort
848 end
849
850 # Build a `NeoNode` representing `mparameter`.
851 private fun mparameter_node(mparameter: MParameter): NeoNode do
852 var node = make_node(mparameter)
853 node.labels.add "MParameter"
854 node["name"] = mparameter.name
855 node["is_vararg"] = mparameter.is_vararg
856 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mparameter.mtype)))
857 return node
858 end
859
860 # Build a new `MParameter` from `node`.
861 #
862 # REQUIRE `node.labels.has("MParameter")`
863 private fun to_mparameter(model: Model, node: NeoNode): MParameter do
864 if mentities.has_key(node) then return mentities[node].as(MParameter)
865 assert node.labels.has("MParameter")
866 var name = node["name"].to_s
867 var mtype = to_mtype(model, node.out_nodes("TYPE").first)
868 var is_vararg = node["is_vararg"].as(Bool)
869 var mparameter = new MParameter(name, mtype, is_vararg)
870 mentities[node] = mparameter
871 return mparameter
872 end
873
874 # Get a `Location` from its string representation.
875 private fun to_location(loc: String): Location do
876 #TODO filepath
877 var parts = loc.split_with(":")
878 var file = new SourceFile.from_string(parts[0], "")
879 var pos = parts[1].split_with("--")
880 var pos1 = pos[0].split_with(",")
881 var pos2 = pos[1].split_with(",")
882 var line_s = pos1[0].to_i
883 var line_e = pos2[0].to_i
884 var column_s = pos1[1].to_i
885 var column_e = 0
886 if pos2.length == 2 then pos2[1].to_i
887 return new Location(file, line_s, line_e, column_s, column_e)
888 end
889
890 # Get a `MVisibility` from its string representation.
891 private fun to_visibility(vis: String): MVisibility do
892 if vis == intrude_visibility.to_s then
893 return intrude_visibility
894 else if vis == public_visibility.to_s then
895 return public_visibility
896 else if vis == protected_visibility.to_s then
897 return protected_visibility
898 else if vis == private_visibility.to_s then
899 return private_visibility
900 else if vis == package_visibility.to_s then
901 return package_visibility
902 else
903 return none_visibility
904 end
905 end
906
907 # Get a `MKind` from its string representation.
908 private fun to_kind(kind: String): MClassKind do
909 if kind == abstract_kind.to_s then
910 return abstract_kind
911 else if kind == concrete_kind.to_s then
912 return concrete_kind
913 else if kind == interface_kind.to_s then
914 return interface_kind
915 else if kind == enum_kind.to_s then
916 return enum_kind
917 else if kind == extern_kind.to_s then
918 return extern_kind
919 else
920 return raw_kind(kind)
921 end
922 end
923
924 # Extract the `MDoc` from `node` and link it to `mentity`.
925 private fun set_doc(node: NeoNode, mentity: MEntity) do
926 if node.has_key("mdoc") then
927 var lines = new Array[String]
928 for e in node["mdoc"].as(JsonArray) do
929 lines.add e.to_s#.replace("\n", "\\n")
930 end
931 var mdoc = new MDoc
932 mdoc.content.add_all(lines)
933 mdoc.original_mentity = mentity
934 mentity.mdoc = mdoc
935 end
936 end
937 end