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