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