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