neo: Really load the graph only once.
[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` or `MSignature`, depending on the class of
137 # 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 relationships for `MSignature`:
164 #
165 # * `parameter_names`: JSON array representing the list of the parameter names.
166 # * `(:MSignature)-[:PARAMETER]->(:MParameter)`: parameters.
167 # * `(:MSignature)-[:RETURNTYPE]->(:MType)`: return type. Does not exist for
168 # procedures.
169 #
170 # In order to maintain the correct parameters order, each `MSignature` node
171 # contains an array of parameter names corresponding to the parameter order in
172 # the signature.
173 #
174 # For example, if the source code contains:
175 #
176 # fun foo(a: A, b: B, c: C)
177 #
178 # The `MSignature` node will contain a property
179 # `parameter_names = ["a", "b", "c"]` so the MSignature can be reconstructed
180 # with the parameters in the correct order.
181 #
182 # `MParameter`
183 #
184 # * labels: `MParameter`, `model_name` and `MEntity`.
185 # * `is_vararg`: Is the parameter a vararg?
186 # * `rank`: position of the parameter (0 for the first parameter).
187 # * `(:MParameter)-[:TYPE]->(:MType)`: static type of the parameter.
188 #
189 # MParameters are also ranked by their position in the corresponding signature.
190 # Rank 0 for the first parameter, 1 for the next one and etc.
191 module neo
192
193 import model
194 import neo4j
195 import toolcontext
196
197 # Helper class that can save and load a `Model` into a Neo4j database.
198 class NeoModel
199
200 # The model name.
201 #
202 # Because we use only one Neo4j instance to store all the models,
203 # we need to mark their appartenance to a particular model and avoid loading all models.
204 #
205 # The name is used as a Neo label on each created nodes and used to load nodes from base.
206 var model_name: String
207
208 # The toolcontext used to init the `NeoModel` tool.
209 var toolcontext: ToolContext
210
211 # The Neo4j `client` used to communicate with the Neo4j instance.
212 var client: Neo4jClient
213
214 # Fill `model` using base pointed by `client`.
215 fun load(model: Model): Model do
216 var nodes: Array[NeoNode]
217
218 toolcontext.info("Loading project node...", 1)
219 nodes = client.nodes_with_labels([model_name, "MProject"])
220 for node in nodes do to_mproject(model, node)
221 toolcontext.info("Loading groups...", 1)
222 nodes = client.nodes_with_labels([model_name, "MGroup"])
223 for node in nodes do to_mgroup(model, node)
224 toolcontext.info("Loading modules...", 1)
225 nodes = client.nodes_with_labels([model_name, "MModule"])
226 for node in nodes do to_mmodule(model, node)
227 toolcontext.info("Loading classes...", 1)
228 nodes = client.nodes_with_labels([model_name, "MClass"])
229 for node in nodes do to_mclass(model, node)
230 toolcontext.info("Loading class definitions...", 1)
231 nodes = client.nodes_with_labels([model_name, "MClassDef"])
232 for node in nodes do to_mclassdef(model, node)
233 toolcontext.info("Loading properties...", 1)
234 nodes = client.nodes_with_labels([model_name, "MProperty"])
235 for node in nodes do to_mproperty(model, node)
236 toolcontext.info("Loading property definitions...", 1)
237 nodes = client.nodes_with_labels([model_name, "MPropDef"])
238 for node in nodes do to_mpropdef(model, node)
239 return model
240 end
241
242 # Save `model` in the base pointed by `client`.
243 fun save(model: Model) do
244 var nodes = collect_model_nodes(model)
245 toolcontext.info("Save {nodes.length} nodes...", 1)
246 push_all(nodes)
247 var edges = collect_model_edges(model)
248 toolcontext.info("Save {edges.length} edges...", 1)
249 push_all(edges)
250 end
251
252 # Save `neo_entities` in base using batch mode.
253 private fun push_all(neo_entities: Collection[NeoEntity]) do
254 var batch = new NeoBatch(client)
255 var len = neo_entities.length
256 var sum = 0
257 var i = 1
258 for nentity in neo_entities do
259 batch.save_entity(nentity)
260 if i == batch_max_size then
261 do_batch(batch)
262 sum += batch_max_size
263 toolcontext.info(" {sum * 100 / len}% done", 1)
264 batch = new NeoBatch(client)
265 i = 1
266 else
267 i += 1
268 end
269 end
270 do_batch(batch)
271 end
272
273 # How many operation can be executed in one batch?
274 private var batch_max_size = 1000
275
276 # Execute `batch` and check for errors.
277 #
278 # Abort if `batch.execute` returns errors.
279 private fun do_batch(batch: NeoBatch) do
280 var errors = batch.execute
281 if not errors.is_empty then
282 print errors
283 exit(1)
284 end
285 end
286
287 # Collect all nodes from the current `model`.
288 private fun collect_model_nodes(model: Model): Collection[NeoNode] do
289 for mproject in model.mprojects do
290 to_node(mproject)
291 for mgroup in mproject.mgroups do to_node(mgroup)
292 end
293 return nodes.values
294 end
295
296 # Collect all edges from the current `model`.
297 #
298 # Actually collect all out_edges from all nodes.
299 private fun collect_model_edges(model: Model): Collection[NeoEdge] do
300 var edges = new HashSet[NeoEdge]
301 for node in nodes.values do edges.add_all(node.out_edges)
302 return edges
303 end
304
305 # Mentities associated to nodes.
306 #
307 # The key is the node’s id.
308 private var mentities = new HashMap[Int, MEntity]
309
310 # Nodes associated with MEntities.
311 private var nodes = new HashMap[MEntity, NeoNode]
312
313 # Get the `NeoNode` associated with `mentity`.
314 # `mentities` are stored locally to avoid duplication.
315 fun to_node(mentity: MEntity): NeoNode do
316 if nodes.has_key(mentity) then return nodes[mentity]
317 if mentity isa MProject then return mproject_node(mentity)
318 if mentity isa MGroup then return mgroup_node(mentity)
319 if mentity isa MModule then return mmodule_node(mentity)
320 if mentity isa MClass then return mclass_node(mentity)
321 if mentity isa MClassDef then return mclassdef_node(mentity)
322 if mentity isa MProperty then return mproperty_node(mentity)
323 if mentity isa MPropDef then return mpropdef_node(mentity)
324 if mentity isa MType then return mtype_node(mentity)
325 if mentity isa MParameter then return mparameter_node(mentity)
326 abort
327 end
328
329 # Make a new `NeoNode` based on `mentity`.
330 private fun make_node(mentity: MEntity): NeoNode do
331 var node = new NeoNode
332 nodes[mentity] = node
333 node.labels.add "MEntity"
334 node.labels.add model_name
335 node["name"] = mentity.name
336 if mentity.mdoc != null then node["mdoc"] = new JsonArray.from(mentity.mdoc.content)
337 return node
338 end
339
340 # Build a `NeoNode` representing `mproject`.
341 private fun mproject_node(mproject: MProject): NeoNode do
342 var node = make_node(mproject)
343 node.labels.add "MProject"
344 var root = mproject.root
345 if root != null then
346 node.out_edges.add(new NeoEdge(node, "ROOT", to_node(root)))
347 end
348 return node
349 end
350
351 # Build a new `MProject` from a `node`.
352 #
353 # REQUIRE `node.labels.has("MProject")`
354 private fun to_mproject(model: Model, node: NeoNode): MProject do
355 var m = mentities.get_or_null(node.id.as(Int))
356 if m isa MProject then return m
357
358 assert node.labels.has("MProject")
359 var mproject = new MProject(node["name"].to_s, model)
360 mentities[node.id.as(Int)] = mproject
361 set_doc(node, mproject)
362 mproject.root = to_mgroup(model, node.out_nodes("ROOT").first)
363 return mproject
364 end
365
366 # Build a `NeoNode` representing `mgroup`.
367 private fun mgroup_node(mgroup: MGroup): NeoNode do
368 var node = make_node(mgroup)
369 node.labels.add "MGroup"
370 node["full_name"] = mgroup.full_name
371 var parent = mgroup.parent
372 node.out_edges.add(new NeoEdge(node, "PROJECT", to_node(mgroup.mproject)))
373 if parent != null then
374 node.out_edges.add(new NeoEdge(node, "PARENT", to_node(parent)))
375 end
376 for mmodule in mgroup.mmodules do
377 node.out_edges.add(new NeoEdge(node, "DECLARES", to_node(mmodule)))
378 end
379 for subgroup in mgroup.in_nesting.direct_smallers do
380 node.in_edges.add(new NeoEdge(node, "NESTS", to_node(subgroup)))
381 end
382 return node
383 end
384
385 # Build a new `MGroup` from a `node`.
386 #
387 # REQUIRE `node.labels.has("MGroup")`
388 private fun to_mgroup(model: Model, node: NeoNode): MGroup do
389 var m = mentities.get_or_null(node.id.as(Int))
390 if m isa MGroup then return m
391
392 assert node.labels.has("MGroup")
393 var mproject = to_mproject(model, node.out_nodes("PROJECT").first)
394 var parent: nullable MGroup = null
395 var out = node.out_nodes("PARENT")
396 if not out.is_empty then
397 parent = to_mgroup(model, out.first)
398 end
399 var mgroup = new MGroup(node["name"].to_s, mproject, parent)
400 mentities[node.id.as(Int)] = mgroup
401 set_doc(node, mgroup)
402 return mgroup
403 end
404
405 # Build a `NeoNode` representing `mmodule`.
406 private fun mmodule_node(mmodule: MModule): NeoNode do
407 var node = make_node(mmodule)
408 node.labels.add "MModule"
409 node["full_name"] = mmodule.full_name
410 node["location"] = mmodule.location.to_s
411 for parent in mmodule.in_importation.direct_greaters do
412 node.out_edges.add(new NeoEdge(node, "IMPORTS", to_node(parent)))
413 end
414 for mclass in mmodule.intro_mclasses do
415 node.out_edges.add(new NeoEdge(node, "INTRODUCES", to_node(mclass)))
416 end
417 for mclassdef in mmodule.mclassdefs do
418 node.out_edges.add(new NeoEdge(node, "DEFINES", to_node(mclassdef)))
419 end
420 return node
421 end
422
423 # Build a new `MModule` from a `node`.
424 #
425 # REQUIRE `node.labels.has("MModule")`
426 private fun to_mmodule(model: Model, node: NeoNode): MModule do
427 var m = mentities.get_or_null(node.id.as(Int))
428 if m isa MModule then return m
429
430 assert node.labels.has("MModule")
431 var ins = node.in_nodes("DECLARES")
432 var mgroup: nullable MGroup = null
433 if not ins.is_empty then
434 mgroup = to_mgroup(model, ins.first)
435 end
436 var name = node["name"].to_s
437 var location = to_location(node["location"].to_s)
438 var mmodule = new MModule(model, mgroup, name, location)
439 mentities[node.id.as(Int)] = mmodule
440 set_doc(node, mmodule)
441 var imported_mmodules = new Array[MModule]
442 for smod in node.out_nodes("IMPORTS") do
443 imported_mmodules.add to_mmodule(model, smod)
444 end
445 mmodule.set_imported_mmodules(imported_mmodules)
446 return mmodule
447 end
448
449 # Build a `NeoNode` representing `mclass`.
450 private fun mclass_node(mclass: MClass): NeoNode do
451 var node = make_node(mclass)
452 node.labels.add "MClass"
453 node["full_name"] = mclass.full_name
454 node["kind"] = mclass.kind.to_s
455 node["visibility"] = mclass.visibility.to_s
456 if not mclass.mparameters.is_empty then
457 var parameter_names = new Array[String]
458 for p in mclass.mparameters do parameter_names.add(p.name)
459 node["parameter_names"] = new JsonArray.from(parameter_names)
460 end
461 node.out_edges.add(new NeoEdge(node, "CLASSTYPE", to_node(mclass.mclass_type)))
462 return node
463 end
464
465 # Build a new `MClass` from a `node`.
466 #
467 # REQUIRE `node.labels.has("MClass")`
468 private fun to_mclass(model: Model, node: NeoNode): MClass do
469 var m = mentities.get_or_null(node.id.as(Int))
470 if m isa MClass then return m
471
472 assert node.labels.has("MClass")
473 var mmodule = to_mmodule(model, node.in_nodes("INTRODUCES").first)
474 var name = node["name"].to_s
475 var kind = to_kind(node["kind"].to_s)
476 var visibility = to_visibility(node["visibility"].to_s)
477 var parameter_names = new Array[String]
478 if node.has_key("parameter_names") then
479 for e in node["parameter_names"].as(JsonArray) do
480 parameter_names.add e.to_s
481 end
482 end
483 var mclass = new MClass(mmodule, name, parameter_names, kind, visibility)
484 mentities[node.id.as(Int)] = mclass
485 set_doc(node, mclass)
486 return mclass
487 end
488
489 # Build a `NeoNode` representing `mclassdef`.
490 private fun mclassdef_node(mclassdef: MClassDef): NeoNode do
491 var node = make_node(mclassdef)
492 node.labels.add "MClassDef"
493 node["location"] = mclassdef.location.to_s
494 node.out_edges.add(new NeoEdge(node, "BOUNDTYPE", to_node(mclassdef.bound_mtype)))
495 node.out_edges.add(new NeoEdge(node, "MCLASS", to_node(mclassdef.mclass)))
496 for mproperty in mclassdef.intro_mproperties do
497 node.out_edges.add(new NeoEdge(node, "INTRODUCES", to_node(mproperty)))
498 end
499 for mpropdef in mclassdef.mpropdefs do
500 node.out_edges.add(new NeoEdge(node, "DECLARES", to_node(mpropdef)))
501 end
502 for sup in mclassdef.supertypes do
503 node.out_edges.add(new NeoEdge(node, "INHERITS", to_node(sup)))
504 end
505 return node
506 end
507
508 # Build a new `MClassDef` from a `node`.
509 #
510 # REQUIRE `node.labels.has("MClassDef")`
511 private fun to_mclassdef(model: Model, node: NeoNode): MClassDef do
512 var m = mentities.get_or_null(node.id.as(Int))
513 if m isa MClassDef then return m
514
515 assert node.labels.has("MClassDef")
516 var mmodule = to_mmodule(model, node.in_nodes("DEFINES").first)
517 var mtype = to_mtype(model, node.out_nodes("BOUNDTYPE").first).as(MClassType)
518 var location = to_location(node["location"].to_s)
519 var mclassdef = new MClassDef(mmodule, mtype, location)
520 mentities[node.id.as(Int)] = mclassdef
521 set_doc(node, mclassdef)
522 var supertypes = new Array[MClassType]
523 for sup in node.out_nodes("INHERITS") do
524 supertypes.add to_mtype(model, sup).as(MClassType)
525 end
526 mclassdef.set_supertypes(supertypes)
527 mclassdef.add_in_hierarchy
528 return mclassdef
529 end
530
531 # Build a `NeoNode` representing `mproperty`.
532 private fun mproperty_node(mproperty: MProperty): NeoNode do
533 var node = make_node(mproperty)
534 node.labels.add "MProperty"
535 node["full_name"] = mproperty.full_name
536 node["visibility"] = mproperty.visibility.to_s
537 if mproperty isa MMethod then
538 node.labels.add "MMethod"
539 node["is_init"] = mproperty.is_init
540 else if mproperty isa MAttribute then
541 node.labels.add "MAttribute"
542 else if mproperty isa MVirtualTypeProp then
543 node.labels.add "MVirtualTypeProp"
544 end
545 node.out_edges.add(new NeoEdge(node, "INTRO_CLASSDEF", to_node(mproperty.intro_mclassdef)))
546 return node
547 end
548
549 # Build a new `MProperty` from a `node`.
550 #
551 # REQUIRE `node.labels.has("MProperty")`
552 private fun to_mproperty(model: Model, node: NeoNode): MProperty do
553 var m = mentities.get_or_null(node.id.as(Int))
554 if m isa MProperty then return m
555
556 assert node.labels.has("MProperty")
557 var intro_mclassdef = to_mclassdef(model, node.out_nodes("INTRO_CLASSDEF").first)
558 var name = node["name"].to_s
559 var visibility = to_visibility(node["visibility"].to_s)
560 var mprop: nullable MProperty = null
561 if node.labels.has("MMethod") then
562 mprop = new MMethod(intro_mclassdef, name, visibility)
563 mprop.is_init = node["is_init"].as(Bool)
564 else if node.labels.has("MAttribute") then
565 mprop = new MAttribute(intro_mclassdef, name, visibility)
566 else if node.labels.has("MVirtualTypeProp") then
567 mprop = new MVirtualTypeProp(intro_mclassdef, name, visibility)
568 end
569 if mprop == null then
570 print "not yet implemented to_mproperty for {node.labels.join(",")}"
571 abort
572 end
573 mentities[node.id.as(Int)] = mprop
574 set_doc(node, mprop)
575 return mprop
576 end
577
578 # Build a `NeoNode` representing `mpropdef`.
579 private fun mpropdef_node(mpropdef: MPropDef): NeoNode do
580 var node = make_node(mpropdef)
581 node.labels.add "MPropDef"
582 node["location"] = mpropdef.location.to_s
583 node.out_edges.add(new NeoEdge(node, "DEFINES", to_node(mpropdef.mproperty)))
584 if mpropdef isa MMethodDef then
585 node.labels.add "MMethodDef"
586 node["is_abstract"] = mpropdef.is_abstract
587 node["is_intern"] = mpropdef.is_intern
588 node["is_extern"] = mpropdef.is_extern
589 var msignature = mpropdef.msignature
590 if msignature != null then
591 node.out_edges.add(new NeoEdge(node, "SIGNATURE", to_node(msignature)))
592 end
593 else if mpropdef isa MAttributeDef then
594 node.labels.add "MAttributeDef"
595 var static_mtype = mpropdef.static_mtype
596 if static_mtype != null then
597 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(static_mtype)))
598 end
599 else if mpropdef isa MVirtualTypeDef then
600 node.labels.add "MVirtualTypeDef"
601 var bound = mpropdef.bound
602 if bound != null then
603 node.out_edges.add(new NeoEdge(node, "BOUND", to_node(bound)))
604 end
605 end
606 return node
607 end
608
609 # Build a new `MPropDef` from a `node`.
610 #
611 # REQUIRE `node.labels.has("MPropDef")`
612 private fun to_mpropdef(model: Model, node: NeoNode): MPropDef do
613 var m = mentities.get_or_null(node.id.as(Int))
614 if m isa MPropDef then return m
615
616 assert node.labels.has("MPropDef")
617 var mclassdef = to_mclassdef(model, node.in_nodes("DECLARES").first)
618 var mproperty = to_mproperty(model, node.out_nodes("DEFINES").first)
619 var location = to_location(node["location"].to_s)
620 var mpropdef: nullable MPropDef = null
621 if node.labels.has("MMethodDef") then
622 mpropdef = new MMethodDef(mclassdef, mproperty.as(MMethod), location)
623 mpropdef.is_abstract = node["is_abstract"].as(Bool)
624 mpropdef.is_intern = node["is_intern"].as(Bool)
625 mpropdef.is_extern = node["is_extern"].as(Bool)
626 mentities[node.id.as(Int)] = mpropdef
627 mpropdef.msignature = to_mtype(model, node.out_nodes("SIGNATURE").first).as(MSignature)
628 else if node.labels.has("MAttributeDef") then
629 mpropdef = new MAttributeDef(mclassdef, mproperty.as(MAttribute), location)
630 mentities[node.id.as(Int)] = mpropdef
631 var static_mtype = node.out_nodes("TYPE")
632 if not static_mtype.is_empty then mpropdef.static_mtype = to_mtype(model, static_mtype.first)
633 else if node.labels.has("MVirtualTypeDef") then
634 mpropdef = new MVirtualTypeDef(mclassdef, mproperty.as(MVirtualTypeProp), location)
635 mentities[node.id.as(Int)] = mpropdef
636 var bound = node.out_nodes("BOUND")
637 if not bound.is_empty then mpropdef.bound = to_mtype(model, bound.first)
638 end
639 if mpropdef == null then
640 print "not yet implemented to_mpropdef for {node.labels.join(",")}"
641 abort
642 end
643 set_doc(node, mpropdef)
644 return mpropdef
645 end
646
647 # Build a `NeoNode` representing `mtype`.
648 private fun mtype_node(mtype: MType): NeoNode do
649 var node = make_node(mtype)
650 node.labels.add "MType"
651 if mtype isa MClassType then
652 node.labels.add "MClassType"
653 node.out_edges.add(new NeoEdge(node, "CLASS", to_node(mtype.mclass)))
654 for arg in mtype.arguments do
655 node.out_edges.add(new NeoEdge(node, "ARGUMENT", to_node(arg)))
656 end
657 if mtype isa MGenericType then
658 node.labels.add "MGenericType"
659 end
660 else if mtype isa MVirtualType then
661 node.labels.add "MVirtualType"
662 node.out_edges.add(new NeoEdge(node, "PROPERTY", to_node(mtype.mproperty)))
663 else if mtype isa MParameterType then
664 node.labels.add "MParameterType"
665 node["rank"] = mtype.rank
666 node.out_edges.add(new NeoEdge(node, "CLASS", to_node(mtype.mclass)))
667 else if mtype isa MNullableType then
668 node.labels.add "MNullableType"
669 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mtype.mtype)))
670 else if mtype isa MSignature then
671 node.labels.add "MSignature"
672 var names = new JsonArray
673 var rank = 0
674 for mparameter in mtype.mparameters do
675 names.add mparameter.name
676 var pnode = to_node(mparameter)
677 pnode["rank"] = rank
678 node.out_edges.add(new NeoEdge(node, "PARAMETER", pnode))
679 end
680 if not names.is_empty then node["parameter_names"] = names
681 var return_mtype = mtype.return_mtype
682 if return_mtype != null then
683 node.out_edges.add(new NeoEdge(node, "RETURNTYPE", to_node(return_mtype)))
684 end
685 end
686 return node
687 end
688
689 # Build a new `MType` from a `node`.
690 #
691 # REQUIRE `node.labels.has("MType")`
692 private fun to_mtype(model: Model, node: NeoNode): MType do
693 var m = mentities.get_or_null(node.id.as(Int))
694 if m isa MType then return m
695
696 assert node.labels.has("MType")
697 if node.labels.has("MClassType") then
698 var mclass = to_mclass(model, node.out_nodes("CLASS").first)
699 var args = new Array[MType]
700 for narg in node.out_nodes("ARGUMENT") do
701 args.add to_mtype(model, narg)
702 end
703 var mtype = mclass.get_mtype(args)
704 mentities[node.id.as(Int)] = mtype
705 return mtype
706 else if node.labels.has("MParameterType") then
707 var mclass = to_mclass(model, node.out_nodes("CLASS").first)
708 var rank = node["rank"].to_s.to_i
709 var mtype = mclass.mparameters[rank]
710 mentities[node.id.as(Int)] = mtype
711 return mtype
712 else if node.labels.has("MNullableType") then
713 var intype = to_mtype(model, node.out_nodes("TYPE").first)
714 var mtype = intype.as_nullable
715 mentities[node.id.as(Int)] = mtype
716 return mtype
717 else if node.labels.has("MVirtualType") then
718 var mproperty = to_mproperty(model, node.out_nodes("PROPERTY").first)
719 assert mproperty isa MVirtualTypeProp
720 var mtype = mproperty.mvirtualtype
721 mentities[node.id.as(Int)] = mtype
722 return mtype
723 else if node.labels.has("MSignature") then
724 # Get all param nodes
725 var mparam_nodes = new HashMap[String, MParameter]
726 for pnode in node.out_nodes("PARAMETER") do
727 var mparam = to_mparameter(model, pnode)
728 mparam_nodes[mparam.name] = mparam
729 end
730 # Load params in the good order
731 var mparam_names = node["parameter_names"]
732 var mparameters = new Array[MParameter]
733 if mparam_names isa JsonArray then
734 for mparam_name in mparam_names do
735 var mparam = mparam_nodes[mparam_name.to_s]
736 mparameters.add mparam
737 end
738 end
739 var return_mtype: nullable MType = null
740 var ret_nodes = node.out_nodes("RETURNTYPE")
741 if not ret_nodes.is_empty then
742 return_mtype = to_mtype(model, ret_nodes.first)
743 end
744 var mtype = new MSignature(mparameters, return_mtype)
745 mentities[node.id.as(Int)] = mtype
746 return mtype
747 end
748 print "not yet implemented to_mtype for {node.labels.join(",")}"
749 abort
750 end
751
752 # Build a `NeoNode` representing `mparameter`.
753 private fun mparameter_node(mparameter: MParameter): NeoNode do
754 var node = make_node(mparameter)
755 node.labels.add "MParameter"
756 node["name"] = mparameter.name
757 node["is_vararg"] = mparameter.is_vararg
758 node.out_edges.add(new NeoEdge(node, "TYPE", to_node(mparameter.mtype)))
759 return node
760 end
761
762 # Build a new `MParameter` from `node`.
763 #
764 # REQUIRE `node.labels.has("MParameter")`
765 private fun to_mparameter(model: Model, node: NeoNode): MParameter do
766 var m = mentities.get_or_null(node.id.as(Int))
767 if m isa MParameter then return m
768
769 assert node.labels.has("MParameter")
770 var name = node["name"].to_s
771 var mtype = to_mtype(model, node.out_nodes("TYPE").first)
772 var is_vararg = node["is_vararg"].as(Bool)
773 var mparameter = new MParameter(name, mtype, is_vararg)
774 mentities[node.id.as(Int)] = mparameter
775 return mparameter
776 end
777
778 # Get a `Location` from its string representation.
779 private fun to_location(loc: String): Location do
780 #TODO filepath
781 var parts = loc.split_with(":")
782 var file = new SourceFile.from_string(parts[0], "")
783 var pos = parts[1].split_with("--")
784 var pos1 = pos[0].split_with(",")
785 var pos2 = pos[1].split_with(",")
786 var line_s = pos1[0].to_i
787 var line_e = pos2[0].to_i
788 var column_s = pos1[1].to_i
789 var column_e = 0
790 if pos2.length == 2 then pos2[1].to_i
791 return new Location(file, line_s, line_e, column_s, column_e)
792 end
793
794 # Get a `MVisibility` from its string representation.
795 private fun to_visibility(vis: String): MVisibility do
796 if vis == intrude_visibility.to_s then
797 return intrude_visibility
798 else if vis == public_visibility.to_s then
799 return public_visibility
800 else if vis == protected_visibility.to_s then
801 return protected_visibility
802 else if vis == private_visibility.to_s then
803 return private_visibility
804 else
805 return none_visibility
806 end
807 end
808
809 # Get a `MKind` from its string representation.
810 private fun to_kind(kind: String): MClassKind do
811 if kind == abstract_kind.to_s then
812 return abstract_kind
813 else if kind == concrete_kind.to_s then
814 return concrete_kind
815 else if kind == interface_kind.to_s then
816 return interface_kind
817 else if kind == enum_kind.to_s then
818 return enum_kind
819 else if kind == extern_kind.to_s then
820 return extern_kind
821 end
822 abort
823 end
824
825 # Extract the `MDoc` from `node` and link it to `mentity`.
826 private fun set_doc(node: NeoNode, mentity: MEntity) do
827 if node.has_key("mdoc") then
828 var lines = new Array[String]
829 for e in node["mdoc"].as(JsonArray) do
830 lines.add e.to_s#.replace("\n", "\\n")
831 end
832 var mdoc = new MDoc
833 mdoc.content.add_all(lines)
834 mdoc.original_mentity = mentity
835 mentity.mdoc = mdoc
836 end
837 end
838 end