nitx: is now able to look for generic types
[nit.git] / src / nitx.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 # nit index, is a command tool used to display documentation
16 module nitx
17
18 import model_utils
19 import modelize_property
20
21 private class Pager
22 var content = new Buffer
23 fun add(text: String) do addn("{text}\n")
24 fun addn(text: String) do content.append(text.escape)
25 fun add_rule do add("\n---\n")
26 fun render do sys.system("echo \"{content}\" | pager -r")
27 end
28
29 # Main class of the nit index tool
30 # NitIndex build the model using the toolcontext argument
31 # then wait for query on std in to display documentation
32 class NitIndex
33 private var toolcontext: ToolContext
34 private var model: Model
35 private var mbuilder: ModelBuilder
36 private var mainmodule: MModule
37 private var arguments: Array[String]
38
39 init(toolcontext: ToolContext) do
40 # We need a model to collect stufs
41 self.toolcontext = toolcontext
42 self.toolcontext.option_context.options.clear
43 self.arguments = toolcontext.option_context.rest
44
45 if arguments.is_empty or arguments.length > 2 then
46 print "usage: ni path/to/module.nit [expression]"
47 toolcontext.option_context.usage
48 exit(1)
49 end
50
51 model = new Model
52 mbuilder = new ModelBuilder(model, toolcontext)
53
54 # Here we load an process std modules
55 #var dir = "NIT_DIR".environ
56 #var mmodules = modelbuilder.parse_and_build(["{dir}/lib/standard/standard.nit"])
57 var mmodules = mbuilder.parse([arguments.first])
58 if mmodules.is_empty then return
59 mbuilder.run_phases
60 assert mmodules.length == 1
61 self.mainmodule = mmodules.first
62 end
63
64 fun start do
65 if arguments.length == 1 then
66 welcome
67 prompt
68 else
69 seek(arguments[1])
70 end
71 end
72
73 fun welcome do
74 print "Welcome in the Nit Index."
75 print ""
76 print "Loaded modules:"
77 var mmodules = new Array[MModule]
78 mmodules.add_all(model.mmodules)
79 var sorter = new MModuleNameSorter
80 sorter.sort(mmodules)
81 for m in mmodules do
82 print "\t{m.name}"
83 end
84 print ""
85 help
86 end
87
88 fun help do
89 print "\nCommands:"
90 print "\tname\t\tlookup module, class and property with the corresponding 'name'"
91 print "\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
92 print "\treturn: Type\tlookup methods returning the corresponding 'Type'"
93 print "\tEnter ':q' to exit"
94 print "\tEnter ':h' to display this help message"
95 print ""
96 end
97
98 fun prompt do
99 printn ">> "
100 seek(stdin.read_line)
101 end
102
103 fun seek(entry: String) do
104 if entry.is_empty then
105 prompt
106 return
107 end
108 if entry == ":h" then
109 help
110 prompt
111 return
112 end
113 if entry == ":q" then exit(0)
114 var pager = new Pager
115 # seek return types
116 if entry.has_prefix("return:") then
117 var ret = entry.split_with(":")[1].replace(" ", "")
118 var matches = seek_returns(ret)
119 props_fulldoc(pager, matches)
120 else if entry.has_prefix("param:") then
121 var param = entry.split_with(":")[1].replace(" ", "")
122 var matches = seek_params(param)
123 props_fulldoc(pager, matches)
124 else
125 # seek for modules
126 var mmatches = new List[MModule]
127 for m in model.mmodules do
128 if m.name == entry then mmatches.add(m)
129 end
130 if not mmatches.is_empty then modules_fulldoc(pager, mmatches)
131 # seek for classes
132 var cmatches = new List[MClass]
133 for c in model.mclasses do
134 if c.name == entry then cmatches.add(c)
135 end
136 if not cmatches.is_empty then classes_fulldoc(pager, cmatches)
137 # seek for properties
138 var matches = new List[MProperty]
139 for p in model.mproperties do
140 if p.name == entry then matches.add(p)
141 end
142 if not matches.is_empty then props_fulldoc(pager, matches)
143 end
144 # no matches
145 if pager.content.is_empty then
146 print "Nothing known about '{entry}', type ':h' for help"
147 else
148 pager.render
149 end
150 if arguments.length == 1 then prompt
151 end
152
153 private fun modules_fulldoc(pager: Pager, mmodules: List[MModule]) do
154 for mmodule in mmodules do
155 # name and prototype
156 pager.add("# {mmodule.namespace}\n".bold)
157 # comment
158 if mbuilder.mmodule2nmodule.has_key(mmodule) then
159 var nmodule = mbuilder.mmodule2nmodule[mmodule]
160 if not nmodule.n_moduledecl.n_doc == null then
161 for comment in nmodule.n_moduledecl.n_doc.comment do pager.add(comment.green)
162 end
163 end
164 pager.add("{mmodule.prototype}\n")
165 # imports
166 var msorter = new MModuleNameSorter
167 var ms = mmodule.in_importation.greaters.to_a
168 if ms.length > 1 then
169 msorter.sort(ms)
170 pager.add("## imported modules".bold)
171 pager.addn("\t")
172 for i in [0..ms.length[ do
173 if ms[i] == mmodule then continue
174 pager.addn(ms[i].name)
175 if i < ms.length - 1 then pager.addn(", ")
176 end
177 pager.add("\n")
178 end
179 # clients
180 ms = mmodule.in_importation.smallers.to_a
181 if ms.length > 1 then
182 msorter.sort(ms)
183 pager.add("## known modules".bold)
184 pager.addn("\t")
185 for i in [0..ms.length[ do
186 if ms[i] == mmodule then continue
187 pager.addn(ms[i].name)
188 if i < ms.length - 1 then pager.addn(", ")
189 end
190 pager.add("\n")
191 end
192 # local classes and interfaces
193 var sorter = new MClassDefNameSorter
194 var intro_mclassdefs = new Array[MClassDef]
195 var redef_mclassdefs = new Array[MClassDef]
196 for mclassdef in mmodule.mclassdefs do
197 if mclassdef.is_intro then
198 intro_mclassdefs.add(mclassdef)
199 else
200 redef_mclassdefs.add(mclassdef)
201 end
202 end
203 # intro
204 if not intro_mclassdefs.is_empty then
205 sorter.sort(intro_mclassdefs)
206 pager.add("\n## introduced classes".bold)
207 for mclassdef in intro_mclassdefs do
208 pager.add("")
209 var nclass = mbuilder.mclassdef2nclassdef[mclassdef]
210 if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then
211 pager.add("\t{nclass.n_doc.short_comment.green}")
212 end
213 pager.add("\t{mclassdef.mclass.prototype}")
214 #TODO add redefs?
215 end
216 end
217 # redefs
218 if not redef_mclassdefs.is_empty then
219 sorter.sort(redef_mclassdefs)
220 pager.add("\n## refined classes".bold)
221 for mclassdef in redef_mclassdefs do
222 pager.add("")
223 #TODO intro comment?
224 var nclass = mbuilder.mclassdef2nclassdef[mclassdef]
225 if nclass isa AStdClassdef and not nclass.n_doc == null and not nclass.n_doc.short_comment.is_empty then
226 pager.add("\t# {nclass.n_doc.short_comment.green}")
227 end
228 pager.add("\t{mclassdef.mclass.prototype}")
229 pager.add("\t\t" + "introduced in {mclassdef.mclass.intro.mmodule.namespace.bold}".gray)
230 for odef in mclassdef.mclass.mclassdefs do
231 if odef.is_intro or odef == mclassdef or mclassdef.mmodule == mmodule then continue
232 pager.add("\t\t" + "refined in {mclassdef.mmodule.namespace.bold}".gray)
233 end
234 end
235 end
236 #TODO add inherited classes?
237 pager.add_rule
238 end
239 end
240
241 private fun classes_fulldoc(pager: Pager, mclasses: List[MClass]) do
242 for mclass in mclasses do
243 # title
244 pager.add("# {mclass.namespace}\n".bold)
245 # comment
246 if mbuilder.mclassdef2nclassdef.has_key(mclass.intro) then
247 var nclass = mbuilder.mclassdef2nclassdef[mclass.intro]
248 if nclass isa AStdClassdef and not nclass.n_doc == null then
249 for comment in nclass.n_doc.comment do pager.add(comment.green)
250 end
251 end
252 pager.addn("{mclass.prototype}")
253 if not mclass.in_hierarchy(mainmodule).direct_greaters.is_empty then
254 var supers = mclass.in_hierarchy(mainmodule).direct_greaters.to_a
255 pager.addn(" super ")
256 for i in [0..supers.length[ do
257 if supers[i] == mclass then continue
258 pager.addn("{supers[i].name}{supers[i].signature}")
259 if i < mclass.in_hierarchy(mainmodule).direct_greaters.length -1 then pager.addn(", ")
260 end
261 end
262 pager.add("\n")
263 # formal types
264 if not mclass.parameter_types.is_empty then
265 pager.add("## formal types".bold)
266 for ft, bound in mclass.parameter_types do
267 pager.add("")
268 pager.add("\t{ft.to_s.green}: {bound.to_console}")
269 end
270 pager.add("")
271 end
272 # get properties
273 var cats = new ArrayMap[String, Set[MPropDef]]
274 cats["virtual types"] = new HashSet[MPropDef]
275 cats["constructors"] = new HashSet[MPropDef]
276 cats["introduced methods"] = new HashSet[MPropDef]
277 cats["refined methods"] = new HashSet[MPropDef]
278
279 for mclassdef in mclass.mclassdefs do
280 for mpropdef in mclassdef.mpropdefs do
281 if mpropdef isa MAttributeDef then continue
282 if mpropdef isa MVirtualTypeDef then cats["virtual types"].add(mpropdef)
283 if mpropdef isa MMethodDef then
284 if mpropdef.mproperty.is_init then
285 cats["constructors"].add(mpropdef)
286 else if mpropdef.is_intro then
287 cats["introduced methods"].add(mpropdef)
288 else
289 cats["refined methods"].add(mpropdef)
290 end
291 end
292 end
293 end
294 # local mproperties
295 for cat, list in cats do
296 if not list.is_empty then
297 #sort list
298 var sorted = new Array[MPropDef]
299 sorted.add_all(list)
300 var sorter = new MPropDefNameSorter
301 sorter.sort(sorted)
302 pager.add("## {cat}".bold)
303 for mpropdef in sorted do
304 pager.add("")
305 if mbuilder.mpropdef2npropdef.has_key(mpropdef) then
306 var nprop = mbuilder.mpropdef2npropdef[mpropdef]
307 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
308 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
309 else
310 nprop = mbuilder.mpropdef2npropdef[mpropdef.mproperty.intro]
311 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
312 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
313 end
314 end
315 end
316 pager.add("\t{mpropdef.to_console}")
317 mainmodule.linearize_mpropdefs(mpropdef.mproperty.mpropdefs)
318 var previous_defs = new Array[MPropDef]
319 for def in mpropdef.mproperty.mpropdefs do
320 if def == mpropdef then continue
321 if def.is_intro then continue
322 if mclass.in_hierarchy(mainmodule) < def.mclassdef.mclass then
323 previous_defs.add(def)
324 end
325 end
326 if not mpropdef.is_intro then
327 pager.add("\t\t" + "introduced by {mpropdef.mproperty.intro.mclassdef.namespace.bold}".gray)
328 end
329 if not previous_defs.is_empty then
330 for def in previous_defs do pager.add("\t\t" + "inherited from {def.mclassdef.namespace.bold}".gray)
331 end
332 end
333 pager.add("")
334 end
335 end
336 # inherited mproperties
337 var inhs = new ArrayMap[MClass, Array[MProperty]]
338 var ancestors = mclass.in_hierarchy(mainmodule).greaters.to_a
339 mainmodule.linearize_mclasses(ancestors)
340 for a in ancestors do
341 if a == mclass then continue
342 for c in a.mclassdefs do
343 for p in c.intro_mproperties do
344 if p.intro_mclassdef == c then
345 if not inhs.has_key(a) then inhs[a] = new Array[MProperty]
346 inhs[a].add(p)
347 end
348 end
349 end
350 end
351 if not inhs.is_empty then
352 pager.add("## inherited properties".bold)
353 for a, ps in inhs do
354 pager.add("\n\tfrom {a.namespace.bold}: {ps.join(", ")}")
355 end
356 end
357 pager.add_rule
358 end
359 end
360
361 private fun props_fulldoc(pager: Pager, raw_mprops: List[MProperty]) do
362 # group by module
363 var cats = new HashMap[MModule, Array[MProperty]]
364 for mprop in raw_mprops do
365 if mprop isa MAttribute then continue
366 var key = mprop.intro.mclassdef.mmodule
367 if not cats.has_key(key) then cats[key] = new Array[MProperty]
368 cats[key].add(mprop)
369 end
370 #sort groups
371 var sorter = new MModuleNameSorter
372 var sorted = new Array[MModule]
373 sorted.add_all(cats.keys)
374 sorter.sort(sorted)
375 # display
376 for mmodule in sorted do
377 var mprops = cats[mmodule]
378 pager.add("# matches in module {mmodule.namespace.bold}")
379 var sorterp = new MPropertyNameSorter
380 sorterp.sort(mprops)
381 for mprop in mprops do
382 pager.add("")
383 if mbuilder.mpropdef2npropdef.has_key(mprop.intro) then
384 var nprop = mbuilder.mpropdef2npropdef[mprop.intro]
385 if not nprop.n_doc == null and not nprop.n_doc.comment.is_empty then
386 for comment in nprop.n_doc.comment do pager.add("\t{comment.green}")
387 end
388 end
389 pager.add("\t{mprop.intro.to_console}")
390 pager.add("\t\t" + "introduced in {mprop.intro_mclassdef.namespace.bold}".gray)
391 var mpropdefs = mprop.mpropdefs
392 mainmodule.linearize_mpropdefs(mpropdefs)
393 for mpdef in mpropdefs do
394 if not mpdef.is_intro then
395 pager.add("\t\t" + "refined in {mpdef.mclassdef.namespace.bold}".gray)
396 end
397 end
398 end
399 pager.add_rule
400 end
401 end
402
403 private fun seek_returns(entry: String): List[MProperty] do
404 var matches = new List[MProperty]
405 for mprop in model.mproperties do
406 var intro = mprop.intro
407 if intro isa MMethodDef then
408 if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_console.has_prefix(entry) then matches.add(mprop)
409 else if intro isa MAttributeDef then
410 if intro.static_mtype.to_console.has_prefix(entry) then matches.add(mprop)
411 end
412 end
413 return matches
414 end
415
416 private fun seek_params(entry: String): List[MProperty] do
417 var matches = new List[MProperty]
418 for mprop in model.mproperties do
419 var intro = mprop.intro
420 if intro isa MMethodDef then
421 var mparameters = intro.msignature.mparameters
422 for mparameter in mparameters do
423 print mparameter.mtype.to_console
424 if mparameter.mtype.to_console.has_prefix(entry) then matches.add(mprop)
425 end
426 else if intro isa MAttributeDef then
427 if intro.static_mtype.to_console.has_prefix(entry) then matches.add(mprop)
428 end
429 end
430 return matches
431 end
432 end
433
434 # Printing facilities
435
436 redef class MModule
437 # prototype of the module
438 # module ownername::name
439 private fun prototype: String do return "module {name}"
440
441 # namespace of the module
442 # ownername::name
443 private fun namespace: String do
444 if public_owner == null then
445 return self.name
446 else
447 return "{public_owner.namespace}::{self.name}"
448 end
449 end
450 end
451
452 redef class MClass
453 # return the generic signature of the class
454 # [E, F]
455 private fun signature: String do
456 var res = new Buffer
457 if arity > 0 then
458 res.append("[")
459 for i in [0..intro.parameter_names.length[ do
460 res.append(intro.parameter_names[i])
461 if i < intro.parameter_names.length - 1 then res.append(", ")
462 end
463 res.append("]")
464 end
465 return res.to_s
466 end
467
468 # return the prototype of the class
469 # class name is displayed with colors depending on visibility
470 # abstract interface Foo[E]
471 private fun prototype: String do
472 var res = new Buffer
473 res.append("{kind} ")
474 if visibility.to_s == "public" then res.append("{name}{signature}".bold.green)
475 if visibility.to_s == "private" then res.append("{name}{signature}".bold.red)
476 if visibility.to_s == "protected" then res.append("{name}{signature}".bold.yellow)
477 return res.to_s
478 end
479
480 private fun namespace: String do
481 return "{intro_mmodule.namespace}::{name}"
482 end
483 end
484
485 redef class MClassDef
486 private fun namespace: String do
487 return "{mmodule.full_name}::{mclass.name}"
488 end
489 end
490
491 redef class MProperty
492 fun to_console: String do
493 if visibility.to_s == "public" then return name.green
494 if visibility.to_s == "private" then return name.red
495 if visibility.to_s == "protected" then return name.yellow
496 return name.bold
497 end
498 end
499
500 redef class MPropDef
501 fun to_console: String is abstract
502 end
503
504 redef class MMethodDef
505 redef fun to_console do
506 var res = new Buffer
507 if not is_intro then res.append("redef ")
508 if not mproperty.is_init then res.append("fun ")
509 res.append(mproperty.to_console.bold)
510 if msignature != null then res.append(msignature.to_console)
511 # FIXME: modifiers should be accessible via the model
512 #if self isa ADeferredMethPropdef then ret = "{ret} is abstract"
513 #if self isa AInternMethPropdef then ret = "{ret} is intern"
514 #if self isa AExternMethPropdef then ret = "{ret} is extern"
515 return res.to_s
516 end
517 end
518
519 redef class MVirtualTypeDef
520 redef fun to_console do
521 var res = new Buffer
522 res.append("type ")
523 res.append(mproperty.to_console.bold)
524 res.append(": {bound.to_console}")
525 return res.to_s
526 end
527 end
528
529 redef class MSignature
530 redef fun to_console do
531 var res = new Buffer
532 if not mparameters.is_empty then
533 res.append("(")
534 for i in [0..mparameters.length[ do
535 res.append(mparameters[i].to_console)
536 if i < mparameters.length - 1 then res.append(", ")
537 end
538 res.append(")")
539 end
540 if return_mtype != null then
541 res.append(": {return_mtype.to_console}")
542 end
543 return res.to_s
544 end
545 end
546
547 redef class MParameter
548 fun to_console: String do
549 var res = new Buffer
550 res.append("{name}: {mtype.to_console}")
551 if is_vararg then res.append("...")
552 return res.to_s
553 end
554 end
555
556 redef class MType
557 fun to_console: String do return self.to_s
558 end
559
560 redef class MNullableType
561 redef fun to_console do return "nullable {mtype.to_console}"
562 end
563
564 redef class MGenericType
565 redef fun to_console do
566 var res = new Buffer
567 res.append("{mclass.name}[")
568 for i in [0..arguments.length[ do
569 res.append(arguments[i].to_console)
570 if i < arguments.length - 1 then res.append(", ")
571 end
572 res.append("]")
573 return res.to_s
574 end
575 end
576
577 redef class MParameterType
578 redef fun to_console do return mclass.intro.parameter_names[rank]
579 end
580
581 redef class MVirtualType
582 redef fun to_console do return mproperty.name
583 end
584
585 redef class ADoc
586 private fun comment: List[String] do
587 var res = new List[String]
588 for t in n_comment do
589 res.add(t.text.replace("\n", ""))
590 end
591 return res
592 end
593
594 private fun short_comment: String do
595 return n_comment.first.text.replace("\n", "")
596 end
597 end
598
599 redef class AAttrPropdef
600 private fun read_accessor: String do
601 var ret = "fun "
602 #FIXME bug with standard::stream::FDStream::fd
603 var name = mreadpropdef.mproperty.name
604 if mpropdef.mproperty.visibility.to_s == "public" then ret = "{ret}{name.green}"
605 if mpropdef.mproperty.visibility.to_s == "private" then ret = "{ret}{name.red}"
606 if mpropdef.mproperty.visibility.to_s == "protected" then ret = "{ret}{name.yellow}"
607 ret = "{ret}: {n_type.to_s}"
608 if n_kwredef != null then ret = "redef {ret}"
609 return ret
610 end
611
612 private fun write_accessor: String do
613 var ret = "fun "
614 var name = "{mreadpropdef.mproperty.name}="
615 if n_readable != null and n_readable.n_visibility != null then
616 if n_readable.n_visibility isa APublicVisibility then ret = "{ret}{name.green}"
617 if n_readable.n_visibility isa APrivateVisibility then ret = "{ret}{name.red}"
618 if n_readable.n_visibility isa AProtectedVisibility then ret = "{ret}{name.yellow}"
619 else
620 ret = "{ret}{name.red}"
621 end
622 ret = "{ret}({mreadpropdef.mproperty.name}: {n_type.to_s})"
623 if n_kwredef != null then ret = "redef {ret}"
624 return ret
625 end
626 end
627
628 # Redef String class to add a function to color the string
629 redef class String
630
631 private fun add_escape_char(escapechar: String): String do
632 return "{escapechar}{self}\\033[0m"
633 end
634
635 private fun esc: Char do return 27.ascii
636 private fun gray: String do return add_escape_char("{esc}[30m")
637 private fun red: String do return add_escape_char("{esc}[31m")
638 private fun green: String do return add_escape_char("{esc}[32m")
639 private fun yellow: String do return add_escape_char("{esc}[33m")
640 private fun blue: String do return add_escape_char("{esc}[34m")
641 private fun purple: String do return add_escape_char("{esc}[35m")
642 private fun cyan: String do return add_escape_char("{esc}[36m")
643 private fun light_gray: String do return add_escape_char("{esc}[37m")
644 private fun bold: String do return add_escape_char("{esc}[1m")
645 private fun underline: String do return add_escape_char("{esc}[4m")
646
647 private fun escape: String
648 do
649 var b = new Buffer
650 for c in self do
651 if c == '\n' then
652 b.append("\\n")
653 else if c == '\0' then
654 b.append("\\0")
655 else if c == '"' then
656 b.append("\\\"")
657 else if c == '\\' then
658 b.append("\\\\")
659 else if c == '`' then
660 b.append("'")
661 else if c.ascii < 32 then
662 b.append("\\{c.ascii.to_base(8, false)}")
663 else
664 b.add(c)
665 end
666 end
667 return b.to_s
668 end
669 end
670
671 # Create a tool context to handle options and paths
672 var toolcontext = new ToolContext
673 toolcontext.process_options
674
675 # Here we launch the nit index
676 var ni = new NitIndex(toolcontext)
677 ni.start
678
679 # TODO seek subclasses and super classes <.<class> >.<class>
680 # TODO seek subclasses and super types <:<type> >:<type>
681 # TODO seek with regexp
682 # TODO standardize namespaces with private option