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