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