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