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