summary refs log tree commit diff stats
path: root/doc/idetools.txt
blob: 97e110b1d2d4e01b59559534cf81125a743daefa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
discard """
  errormsg: "can raise an unlisted exception: ref IOError"
  file: "io.nim"
"""

type
  TObj {.pure, inheritable.} = object
  TObjB = object of TObj
    a, b, c: string

  IO2Error = ref object of IOError

proc forw: int {. .}

proc lier(): int {.raises: [IO2Error].} =
  writeLine stdout, "arg"

proc forw: int =
  raise newException(IOError, "arg")
75' href='#n275'>275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
================================
  Nimrod IDE Integration Guide
================================

:Author: Britney Spears
:Version: |nimrodversion|

.. contents::


.. raw:: html
  <blockquote><p>
  "yes, I'm the creator" -- Araq, 2013-07-26 19:28:32.
  </p></blockquote>

Nimrod differs from many other compilers in that it is really fast,
and being so fast makes it suited to provide external queries for
text editors about the source code being written. Through the
``idetools`` command of `the compiler <nimrodc.html>`_, any IDE
can query a ``.nim`` source file and obtain useful information like
definition of symbols or suggestions for completion.

This document will guide you through the available options. If you
want to look at practical examples of idetools support you can look
at the test files found in the `Test suite`_ or `various editor
integrations <https://github.com/Araq/Nimrod/wiki/Editor-Support>`_
already available.


Idetools invokation
===================

Specifying the location of the query
------------------------------------

All of the available idetools commands require you to specify a
query location through the ``--track`` or ``--trackDirty`` switches.
The general idetools invokations are::

    nimrod idetools --track:FILE,LINE,COL <switches> proj.nim

Or::

    nimrod idetools --trackDirty:DIRTY_FILE,FILE,LINE,COL <switches> proj.nim

``proj.nim``
    This is the main *project* filename. Most of the time you will
    pass in the same as **FILE**, but for bigger projects this is
    the file which is used as main entry point for the program, the
    one which users compile to generate a final binary.

``<switches>``
    This would be any of the other idetools available options, like
    ``--def`` or ``--suggest`` explained in the following sections.

``COL``
    An integer with the column you are going to query. For the
    compiler columns start at zero, so the first column will be
    **0** and the last in an 80 column terminal will be **79**.

``LINE``
    An integer with the line you are going to query. For the compiler
    lines start at **1**.

``FILE``
    The file you want to perform the query on. Usually you will
    pass in the same value as **proj.nim**.

``DIRTY_FILE``
    The **FILE** paramater is enough for static analysis, but IDEs
    tend to have *unsaved buffers* where the user may still be in
    the middle of typing a line. In such situations the IDE can
    save the current contents to a temporary file and then use the
    ``--trackDirty`` switch.

    Dirty files are likely to contain errors and they are usually
    compiled partially only to the point needed to service the
    idetool request. The compiler discriminates them to ensure that
    **a)** they won't be cached and **b)** they won't invalidate
    the cached contents of the original module.

    The other reason is that the dirty file can appear anywhere on
    disk (e.g. in tmpfs), but it must be treated as having a path
    matching the original module when it comes to usage of relative
    paths, etc. Queries, however, will refer to the dirty module
    name in their answers instead of the normal filename.


Definitions
-----------

The ``--def`` idetools switch performs a query about the definition
of a specific symbol. If available, idetools will answer with the
type, source file, line/column information and other accessory data
if available like a docstring. With this information an IDE can
provide the typical *Jump to definition* where a user puts the
cursor on a symbol or uses the mouse to select it and is redirected
to the place where the symbol is located.

Since Nimrod is implemented in Nimrod, one of the nice things of
this feature is that any user with an IDE supporting it can quickly
jump around the standard library implementation and see exactly
what a proc does, learning about the language and seeing real life
examples of how to write/implement specific features.

Idetools will always answer with a single definition or none if it
can't find any valid symbol matching the position of the query.


Suggestions
-----------

The ``--suggest`` idetools switch performs a query about possible
completion symbols at some point in the file. IDEs can easily provide
an autocompletion feature where the IDE scans the current file (and
related ones, if it knows about the language being edited and follows
includes/imports) and when the user starts typing something a
completion box with different options appears.

However such features are not context sensitive and work simply on
string matching, which can be problematic in Nimrod especially due
to the case insensitiveness of the language (plus underscores as
separators!).

The typical usage scenario for this option is to call it after the
user has typed the dot character for `the object oriented call
syntax <tut2.html#method-call-syntax>`_. Idetools will try to return
the suggestions sorted first by scope (from innermost to outermost)
and then by item name.


Invokation context
------------------

The ``--context`` idetools switch is very similar to the suggestions
switch, but instead of being used after the user has typed a dot
character, this one is meant to be used after the user has typed
an opening brace to start typing parameters.


Symbol usages
-------------

The ``--usages`` idetools switch lists all usages of the symbol at
a position. IDEs can use this to find all the places in the file
where the symbol is used and offer the user to rename it in all
places at the same time. Again, a pure string based search and
replace may catch symbols out of the scope of a funcion/loop.

For this kind of query the IDE will most likely ignore all the
type/signature info provided by idetools and concentrate on the
filename, line and column position of the multiple returned answers.


Expression evaluation
---------------------

This feature is still under development. In the future it will allow
an IDE to evaluate an expression in the context of the currently
running/debugged user project.


Compiler as a service (CAAS)
============================

The ocasional use of idetools is acceptable for things like
definitions, where the user puts the cursor on a symbol or double
clicks it and after a second or two the IDE displays where that
symbol is defined. Such latencies would be terrible for features
like symbol suggestion, plus why wait at all if we can avoid it?

The idetools command can be run as a compiler service (CAAS),
where you first launch the compiler and it will stay online as a
server, accepting queries in a telnet like fashion. The advantage
of staying on is that for many queries the compiler can cache the
results of the compilation, and subsequent queries should be fast
in the millisecond range, thus being responsive enough for IDEs.

If you want to start the server using stdin/stdout as communication
you need to type::

    nimrod serve --server.type:stdin proj.nim

If you want to start the server using tcp and a port, you need to type::

    nimrod serve --server.type:tcp --server.port:6000 \
      --server.address:hostname proj.nim

In both cases the server will start up and await further commands.
The syntax of the commands you can now send to the server is
practically the same as running the nimrod compiler on the commandline,
you only need to remove the name of the compiler since you are
already talking to it. The server will answer with as many lines
of text it thinks necessary plus an empty line to indicate the end
of the answer.

You can find examples of client/server communication in the idetools
tests found in the `Test suite`_.


Parsing idetools output
=======================

Idetools outputs is always returned on single lines separated by
tab characters (``\t``). The values of each column are:

1. Three characters indicating the type of returned answer (e.g.
   def for definition, ``sug`` for suggestion, etc).
2. Type of the symbol. This can be ``skProc``, ``skLet``, and just
   about any of the enums defined in the module ``compiler/ast.nim``.
3. Full qualitifed path of the symbol. If you are querying a symbol
   defined in the ``proj.nim`` file, this would have the form
   ``proj.symbolName``.
4. Type/signature. For variables and enums this will contain the
   type of the symbol, for procs, methods and templates this will
   contain the full unique signature (e.g. ``proc (TFile)``).
5. Full path to the file containing the symbol.
6. Line where the symbol is located in the file. Lines start to
   count at **1**.
7. Column where the symbol is located in the file. Columns start
   to count at **0**.
8. Docstring for the symbol if available or the empty string. To
   differentiate the docstring from end of answer in server mode,
   the docstring is always provided enclosed in double quotes, and
   if the docstring spans multiple lines, all following lines of the
   docstring will start with a blank space to align visually with
   the starting quote.

   Also, you won't find raw ``\n`` characters breaking the one
   answer per line format. Instead you will need to parse sequences
   in the form ``\xHH``, where *HH* is a hexadecimal value (e.g.
   newlines generate the sequence ``\x0A``).

The following sections define the expected output for each kind of
symbol for which idetools returns valid output.


skConst
-------

| **Third column**: module + [n scope nesting] + const name.
| **Fourth column**: the type of the const value.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    const SOME_SEQUENCE = @[1, 2]
    --> col 2: $MODULE.SOME_SEQUENCE
        col 3: seq[int]
        col 7: ""


skEnumField
-----------

| **Third column**: module + [n scope nesting] + enum type + enum field name.
| **Fourth column**: enum type grouping other enum fields.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    Open(filename, fmWrite)
    --> col 2: system.TFileMode.fmWrite
        col 3: TFileMode
        col 7: ""


skForVar
--------

| **Third column**: module + [n scope nesting] + var name.
| **Fourth column**: type of the var.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc looper(filename = "tests.nim") =
      for letter in filename:
        echo letter
    --> col 2: $MODULE.looper.letter
        col 3: char
        col 7: ""


skIterator, skClosureIterator
-----------------------------

The fourth column will be the empty string if the iterator is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the iterator.

| **Third column**: module + [n scope nesting] + iterator name.
| **Fourth column**: signature of the iterator including return type.
| **Docstring**: docstring if available.

.. code-block:: nimrod
    let
      text = "some text"
      letters = toSeq(runes(text))
    --> col 2: unicode.runes
        col 3: iterator (string): TRune
        col 7: "iterates over any unicode character of the string `s`."


skLabel
-------

| **Third column**: module + [n scope nesting] + name.
| **Fourth column**: always the empty string.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc test(text: string) =
      var found = -1
      block loops:
    --> col 2: $MODULE.test.loops
        col 3: ""
        col 7: ""


skLet
-----

| **Third column**: module + [n scope nesting] + let name.
| **Fourth column**: the type of the let variable.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    let
      text = "some text"
    --> col 2: $MODULE.text
        col 3: TaintedString
        col 7: ""


skMacro
-------

The fourth column will be the empty string if the macro is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the macro.

| **Third column**: module + [n scope nesting] + macro name.
| **Fourth column**: signature of the macro including return type.
| **Docstring**: docstring if available.

.. code-block:: nimrod
    proc testMacro() =
      expect(EArithmetic):
    --> col 2: idetools_api.expect
        col 3: proc (varargs[expr], stmt): stmt
        col 7: ""


skMethod
--------

The fourth column will be the empty string if the method is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the method.

Methods imply `dynamic dispatch <tut2.html#dynamic-dispatch>`_ and
idetools performs a static analysis on the code. For this reason
idetools may not return the definition of the correct method you
are querying because it may be impossible to know until the code
is executed. It will try to return the method which covers the most
possible cases (i.e. for variations of different classes in a
hierarchy it will prefer methods using the base class).

While at the language level a method is differentiated from others
by the parameters and return value, the signature of the method
returned by idetools returns also the pragmas for the method.

Note that at the moment the word ``proc`` is returned for the
signature of the found method instead of the expected ``method``.
This may change in the future.

| **Third column**: module + [n scope nesting] + method name.
| **Fourth column**: signature of the method including return type.
| **Docstring**: docstring if available.

.. code-block:: nimrod
    method eval(e: PExpr): int = quit "to override!"
    method eval(e: PLiteral): int = e.x
    method eval(e: PPlusExpr): int = eval(e.a) + eval(e.b)
    echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))
    --> col 2: $MODULE.eval
        col 3: proc (PPlusExpr): int
        col 7: ""


skParam
-------

| **Third column**: module + [n scope nesting] + param name.
| **Fourth column**: the type of the parameter.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc reader(filename = "tests.nim") =
      let text = readFile(filename)
    --> col 2: $MODULE.reader.filename
        col 3: string
        col 7: ""


skProc
------

The fourth column will be the empty string if the proc is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the proc.

While at the language level a proc is differentiated from others
by the parameters and return value, the signature of the proc
returned by idetools returns also the pragmas for the proc.

| **Third column**: module + [n scope nesting] + proc name.
| **Fourth column**: signature of the proc including return type.
| **Docstring**: docstring if available.

.. code-block:: nimrod
    Open(filename, fmWrite)
    --> col 2: system.Open
        col 3: proc (var TFile, string, TFileMode, int): bool
        col 7:
    "Opens a file named `filename` with given `mode`.

     Default mode is readonly. Returns true iff the file could be opened.
     This throws no exception if the file could not be opened."


skResult
--------

| **Third column**: module + [n scope nesting] + result.
| **Fourth column**: the type of the result.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc getRandomValue() : int =
      return 4
    --> col 2: $MODULE.getRandomValue.result
        col 3: int
        col 7: ""


skTemplate
----------

The fourth column will be the empty string if the template is being
defined, since at that point in the file the parser hasn't processed
the full line yet. The signature will be returned complete in
posterior instances of the template.

| **Third column**: module + [n scope nesting] + template name.
| **Fourth column**: signature of the template including return type.
| **Docstring**: docstring if available.

.. code-block:: nimrod
    let
      text = "some text"
      letters = toSeq(runes(text))
    --> col 2: sequtils.toSeq
        col 3: proc (expr): expr
        col 7:
    "Transforms any iterator into a sequence.

     Example:

     .. code-block:: nimrod
       let
         numeric = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
         odd_numbers = toSeq(filter(numeric) do (x: int) -> bool:
           if x mod 2 == 1:
             result = true)
       assert odd_numbers == @[1, 3, 5, 7, 9]"


skType
------

| **Third column**: module + [n scope nesting] + type name.
| **Fourth column**: the type.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc writeTempFile() =
      var output: TFile
    --> col 2: system.TFile
        col 3: TFile
        col 7: ""


skVar
-----

| **Third column**: module + [n scope nesting] + var name.
| **Fourth column**: the type of the var.
| **Docstring**: always the empty string.

.. code-block:: nimrod
    proc writeTempFile() =
      var output: TFile
      output.open("/tmp/somefile", fmWrite)
      output.write("test")
    --> col 2: $MODULE.writeTempFile.output
        col 3: TFile
        col 7: ""


Test suite
==========

To verify that idetools is working properly there are files in the
``tests/caas/`` directory which provide unit testing. If you find
odd idetools behaviour and are able to reproduce it, you are welcome
to report it as a bug and add a test to the suite to avoid future
regressions.


Running the test suite
----------------------

At the moment idetools support is still in development so the test
suite is not integrated with the main test suite and you have to
run it manually. First you have to compile the tester::

	$ cd my/nimrod/checkout/tests
	$ nimrod c testament/caasdriver.nim

Running the ``caasdriver`` without parameters will attempt to process
all the test cases in all three operation modes. If a test succeeds
nothing will be printed and the process will exit with zero. If any
test fails, the specific line of the test preceeding the failure
and the failure itself will be dumped to stdout, along with a final
indicator of the success state and operation mode. You can pass the
parameter ``verbose`` to force all output even on successfull tests.

The normal operation mode is called ``ProcRun`` and it involves
starting a process for each command or query, similar to running
manually the Nimrod compiler from the commandline. The ``CaasRun``
mode starts a server process to answer all queries. The ``SymbolProcRun``
mode is used by compiler developers.  This means that running all
tests involves processing all ``*.txt`` files three times, which
can be quite time consuming.

If you don't want to run all the test case files you can pass any
substring as a parameter to ``caasdriver``. Only files matching the
passed substring will be run. The filtering doesn't use any globbing
metacharacters, it's a plain match. For example, to run only
``*-compile*.txt`` tests in verbose mode::

	./caasdriver verbose -compile


Test case file format
---------------------

All the ``tests/caas/*.txt`` files encode a session with the compiler:

* The first line indicates the main project file.

* Lines starting with ``>`` indicate a command to be sent to the
  compiler and the lines following a command include checks for
  expected or forbidden output (``!`` for forbidden).

* If a line starts with ``#`` it will be ignored completely, so you
  can use that for comments.

* Since some cases are specific to either ``ProcRun`` or ``CaasRun``
  modes, you can prefix a line with the mode and the line will be
  processed only in that mode.

* The rest of the line is treated as a `regular expression <re.html>`_,
  so be careful escaping metacharacters like parenthesis.

Before the line is processed as a regular expression, some basic
variables are searched for and replaced in the tests. The variables
which will be replaced are:

* **$TESTNIM**: filename specified in the first line of the script.
* **$MODULE**: like $TESTNIM but without extension, useful for
  expected output.

When adding a test case to the suite it is a good idea to write a
few comments about what the test is meant to verify.