From 078594cac6c79757566b337bcf53610c60e790bb Mon Sep 17 00:00:00 2001 From: Grzegorz Adam Hankiewicz Date: Sat, 19 Jul 2014 14:36:47 +0200 Subject: [PATCH] Improves macro tutorial with hyperlinks and numbered lines. --- doc/tut2.txt | 101 +++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/doc/tut2.txt b/doc/tut2.txt index 1e23618e08..87f659ab79 100644 --- a/doc/tut2.txt +++ b/doc/tut2.txt @@ -752,9 +752,8 @@ variable number of arguments: debug(a[0], a[1], x) -The macro call expands to: +The macro call expands to:: -.. code-block:: nimrod write(stdout, "a[0]") write(stdout, ": ") writeln(stdout, a[0]) @@ -851,9 +850,9 @@ to be included along the program containing the license information:: The ``readCfgAtRuntime`` proc will open the given filename and return a ``TTable`` from the `tables module `_. The parsing of the file is done (without much care for handling invalid data or corner cases) using the -``splitLines`` proc from the `strutils module `_. There are many -things which can fail; mind the purpose is explaining how to make this run at -compile time, not how to properly implement a DRM scheme. +`splitLines proc from the strutils module `_. There +are many things which can fail; mind the purpose is explaining how to make +this run at compile time, not how to properly implement a DRM scheme. The reimplementation of this code as a compile time proc will allow us to get rid of the ``data.cfg`` file we would need to distribute along the binary, plus @@ -876,6 +875,8 @@ time string with the *generated source code*, which we then pass to the modified source code implementing the macro: .. code-block:: nimrod + :number-lines: + import macros, strutils macro readCfgAndBuildSource(cfgFilename: string): stmt = @@ -902,29 +903,31 @@ modified source code implementing the macro: echo cfglicenseKey echo cfgversion -The good news is not much has changed! First, we need to change the handling of -the input parameter. In the dynamic version the ``readCfgAtRuntime`` proc -receives a string parameter. However, in the macro version it is also declared -as string, but this is the *outside* interface of the macro. When the macro is -run, it actually gets a ``PNimrodNode`` object instead of a string, and we have -to call the ``strVal`` proc from the `macros module `_ to obtain -the string being passed in to the macro. +The good news is not much has changed! First, we need to change the handling +of the input parameter (line 3). In the dynamic version the +``readCfgAtRuntime`` proc receives a string parameter. However, in the macro +version it is also declared as string, but this is the *outside* interface of +the macro. When the macro is run, it actually gets a ``PNimrodNode`` object +instead of a string, and we have to call the `strVal proc +`_ (line 5) from the `macros module `_ to +obtain the string being passed in to the macro. -Second, we cannot use the ``readFile`` proc from the `system module -`_ due to FFI restriction at compile time. If we try to use this -proc, or any other which depends on FFI, the compiler will error with the -message ``cannot evaluate`` and a dump of the macro's source code, along with a -stack trace where the compiler reached before bailing out. We can get around -this limitation by using the ``slurp`` proc from the `system module -`_, which was precisely made for compilation time (just like -``gorge`` which executes an external program and captures its output). +Second, we cannot use the `readFile proc `_ from the +`system module `_ due to FFI restriction at compile time. If we +try to use this proc, or any other which depends on FFI, the compiler will +error with the message ``cannot evaluate`` and a dump of the macro's source +code, along with a stack trace where the compiler reached before bailing out. +We can get around this limitation by using the `slurp proc +`_ from the `system module `_, which was +precisely made for compilation time (just like `gorge `_ +which executes an external program and captures its output). -The interesting thing is that our macro does not return a runtime ``TTable`` -object. Instead, it builds up Nimrod source code into the ``source`` variable. -For each line of the configuration file a ``const`` variable will be generated. -To avoid conflicts we prefix these variables with ``cfg``. In essence, what the -compiler is doing is replacing the line calling the macro with the following -snippet of code: +The interesting thing is that our macro does not return a runtime `TTable +`_ object. Instead, it builds up Nimrod source code into +the ``source`` variable. For each line of the configuration file a ``const`` +variable will be generated (line 15). To avoid conflicts we prefix these +variables with ``cfg``. In essence, what the compiler is doing is replacing +the line calling the macro with the following snippet of code: .. code-block:: nimrod const cfgversion= "1.1" @@ -933,13 +936,14 @@ snippet of code: You can verify this yourself adding the line ``echo source`` somewhere at the end of the macro and compiling the program. Another difference is that instead -of calling the usual ``quit`` proc to abort (which we could still call) this -version calls the ``error`` proc. The ``error`` proc has the same behavior as -``quit`` but will dump also the source and file line information where the -error happened, making it easier for the programmer to find where compilation -failed. In this situation it would point to the line invoking the macro, but -**not** the line of ``data.cfg`` we are processing, that's something the macro -itself would need to control. +of calling the usual `quit proc `_ to abort (which we could +still call) this version calls the `error proc `_ (line +14). The ``error`` proc has the same behavior as ``quit`` but will dump also +the source and file line information where the error happened, making it +easier for the programmer to find where compilation failed. In this situation +it would point to the line invoking the macro, but **not** the line of +``data.cfg`` we are processing, that's something the macro itself would need +to control. Generating AST by hand @@ -947,11 +951,11 @@ Generating AST by hand To generate an AST we would need to intimately know the structures used by the Nimrod compiler exposed in the `macros module `_, which at first -look seems a daunting task. But we can use as helper shortcut the ``dumpTree`` -macro, which is used as a statement macro instead of an expression macro. -Since we know that we want to generate a bunch of ``const`` symbols we can -create the following source file and compile it to see what the compiler -*expects* from us: +look seems a daunting task. But we can use as helper shortcut the `dumpTree +macro `_, which is used as a statement macro instead of +an expression macro. Since we know that we want to generate a bunch of +``const`` symbols we can create the following source file and compile it to +see what the compiler *expects* from us: .. code-block:: nimrod import macros @@ -997,6 +1001,8 @@ with this knowledge, let's look at the finished version of the AST building macro: .. code-block:: nimrod + :number-lines: + import macros, strutils macro readCfgAndBuildAST(cfgFilename: string): stmt = @@ -1032,19 +1038,20 @@ Since we are building on the previous example generating source code, we will only mention the differences to it. Instead of creating a temporary ``string`` variable and writing into it source code as if it were written *by hand*, we use the ``result`` variable directly and create a statement list node -(``nnkStmtList``) which will hold our children. +(``nnkStmtList``) which will hold our children (line 7). For each input line we have to create a constant definition (``nnkConstDef``) and wrap it inside a constant section (``nnkConstSection``). Once these -variables are created, we fill them hierarchichally like the previous AST dump -tree showed: the constant definition is a child of the section definition, and -the constant definition has an identifier node, an empty node (we let the -compiler figure out the type), and a string literal with the value. +variables are created, we fill them hierarchichally (line 17) like the +previous AST dump tree showed: the constant definition is a child of the +section definition, and the constant definition has an identifier node, an +empty node (we let the compiler figure out the type), and a string literal +with the value. A last tip when writing a macro: if you are not sure the AST you are building looks ok, you may be tempted to use the ``dumpTree`` macro. But you can't use it *inside* the macro you are writting/debugging. Instead ``echo`` the string -generated by ``treeRepr``. If at the end of the this example you add ``echo -treeRepr(result)`` you should get the same output as using the ``dumpTree`` -macro, but of course you can call that at any point of the macro where you -might be having troubles. +generated by `treeRepr `_. If at the end of the this +example you add ``echo treeRepr(result)`` you should get the same output as +using the ``dumpTree`` macro, but of course you can call that at any point of +the macro where you might be having troubles.