summary refs log tree commit diff stats
path: root/doc/tut3.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/tut3.md')
-rw-r--r--doc/tut3.md417
1 files changed, 417 insertions, 0 deletions
diff --git a/doc/tut3.md b/doc/tut3.md
new file mode 100644
index 000000000..3a55d4790
--- /dev/null
+++ b/doc/tut3.md
@@ -0,0 +1,417 @@
+=======================
+Nim Tutorial (Part III)
+=======================
+
+:Author: Arne Döring
+:Version: |nimversion|
+
+.. default-role:: code
+.. include:: rstcommon.rst
+.. contents::
+
+
+Introduction
+============
+
+>  "With Great Power Comes Great Responsibility." -- Spider Man's Uncle
+
+This document is a tutorial about Nim's macro system.
+A macro is a function that is executed at compile-time and transforms
+a Nim syntax tree into a different tree.
+
+Examples of things that can be implemented in macros:
+
+* An assert macro that prints both sides of a comparison operator, if
+  the assertion fails. `myAssert(a == b)` is converted to
+  `if a != b: quit($a " != " $b)`
+
+* A debug macro that prints the value and the name of the symbol.
+  `myDebugEcho(a)` is converted to `echo "a: ", a`
+
+* Symbolic differentiation of an expression.
+  `diff(a*pow(x,3) + b*pow(x,2) + c*x + d, x)` is converted to
+  `3*a*pow(x,2) + 2*b*x + c`
+
+
+Macro Arguments
+---------------
+
+The types of macro arguments have two faces. One face is used for
+the overload resolution and the other face is used within the macro
+body. For example, if `macro foo(arg: int)` is called in an
+expression `foo(x)`, `x` has to be of a type compatible to int, but
+*within* the macro's body `arg` has the type `NimNode`, not `int`!
+Why it is done this way will become obvious later, when we have seen
+concrete examples.
+
+There are two ways to pass arguments to a macro, an argument can be
+either `typed` or `untyped`.
+
+
+Untyped Arguments
+-----------------
+
+Untyped macro arguments are passed to the macro before they are
+semantically checked. This means the syntax tree that is passed down
+to the macro does not need to make sense for Nim yet, the only
+limitation is that it needs to be parsable. Usually, the macro does
+not check the argument either but uses it in the transformation's
+result somehow. The result of a macro expansion is always checked
+by the compiler, so apart from weird error messages, nothing bad
+can happen.
+
+The downside for an `untyped` argument is that these do not play
+well with Nim's overloading resolution.
+
+The upside for untyped arguments is that the syntax tree is
+quite predictable and less complex compared to its `typed`
+counterpart.
+
+
+Typed Arguments
+---------------
+
+For typed arguments, the semantic checker runs on the argument and
+does transformations on it, before it is passed to the macro. Here
+identifier nodes are resolved as symbols, implicit type
+conversions are visible in the tree as calls, templates are
+expanded, and probably most importantly, nodes have type information.
+Typed arguments can have the type `typed` in the arguments list.
+But all other types, such as `int`, `float` or `MyObjectType`
+are typed arguments as well, and they are passed to the macro as a
+syntax tree.
+
+
+Static Arguments
+----------------
+
+Static arguments are a way to pass values as values and not as syntax
+tree nodes to a macro. For example for `macro foo(arg: static[int])`
+in the expression `foo(x)`, `x` needs to be an integer constant,
+but in the macro body `arg` is just like a normal parameter of type
+`int`.
+
+  ```nim
+  import std/macros
+
+  macro myMacro(arg: static[int]): untyped =
+    echo arg # just an int (7), not `NimNode`
+
+  myMacro(1 + 2 * 3)
+  ```
+
+
+Code Blocks as Arguments
+------------------------
+
+It is possible to pass the last argument of a call expression in a
+separate code block with indentation. For example, the following code
+example is a valid (but not a recommended) way to call `echo`:
+
+  ```nim
+  echo "Hello ":
+    let a = "Wor"
+    let b = "ld!"
+    a & b
+  ```
+
+For macros this way of calling is very useful; syntax trees of arbitrary
+complexity can be passed to macros with this notation.
+
+
+The Syntax Tree
+---------------
+
+In order to build a Nim syntax tree one needs to know how Nim source
+code is represented as a syntax tree, and how such a tree needs to
+look like so that the Nim compiler will understand it. The nodes of the
+Nim syntax tree are documented in the [macros](macros.html) module.
+But a more interactive way to explore the Nim
+syntax tree is with `macros.treeRepr`, it converts a syntax tree
+into a multi-line string for printing on the console. It can be used
+to explore how the argument expressions are represented in tree form
+and for debug printing of generated syntax tree. `dumpTree` is a
+predefined macro that just prints its argument in a tree representation,
+but does nothing else. Here is an example of such a tree representation:
+
+  ```nim
+  dumpTree:
+    var mt: MyType = MyType(a:123.456, b:"abcdef")
+
+  # output:
+  #   StmtList
+  #     VarSection
+  #       IdentDefs
+  #         Ident "mt"
+  #         Ident "MyType"
+  #         ObjConstr
+  #           Ident "MyType"
+  #           ExprColonExpr
+  #             Ident "a"
+  #             FloatLit 123.456
+  #           ExprColonExpr
+  #             Ident "b"
+  #             StrLit "abcdef"
+  ```
+
+
+Custom Semantic Checking
+------------------------
+
+The first thing that a macro should do with its arguments is to check
+if the argument is in the correct form. Not every type of wrong input
+needs to be caught here, but anything that could cause a crash during
+macro evaluation should be caught and create a nice error message.
+`macros.expectKind` and `macros.expectLen` are a good start. If
+the checks need to be more complex, arbitrary error messages can
+be created with the `macros.error` proc.
+
+  ```nim
+  macro myAssert(arg: untyped): untyped =
+    arg.expectKind nnkInfix
+  ```
+
+
+Generating Code
+---------------
+
+There are two ways to generate the code. Either by creating the syntax
+tree with expressions that contain a lot of calls to `newTree` and
+`newLit`, or with `quote do:` expressions. The first option offers
+the best low-level control for the syntax tree generation, but the
+second option is much less verbose. If you choose to create the syntax
+tree with calls to `newTree` and `newLit` the macro
+`macros.dumpAstGen` can help you with the verbosity.
+
+`quote do:` allows you to write the code that you want to generate literally.
+Backticks are used to insert code from `NimNode` symbols into the
+generated expression.
+
+  ```nim  test = "nim c $1"
+  import std/macros
+  macro a(i) = quote do:
+    let `i` = 0
+
+  a b
+  doAssert b == 0
+  ```
+
+A custom prefix operator can be defined whenever backticks are needed.
+
+  ```nim  test = "nim c $1"
+  import std/macros
+  macro a(i) = quote("@") do:
+    assert @i == 0
+
+  let b = 0
+  a b
+  ```
+
+The injected symbol needs accent quoted when it resolves to a symbol.
+
+  ```nim  test = "nim c $1"
+  import std/macros
+  macro a(i) = quote("@") do:
+    let `@i` = 0
+
+  a b
+  doAssert b == 0
+  ```
+
+Make sure to inject only symbols of type `NimNode` into the generated syntax
+tree. You can use `newLit` to convert arbitrary values into
+expressions trees of type `NimNode` so that it is safe to inject
+them into the tree.
+
+
+  ```nim  test = "nim c $1"
+  import std/macros
+
+  type
+    MyType = object
+      a: float
+      b: string
+
+  macro myMacro(arg: untyped): untyped =
+    var mt: MyType = MyType(a:123.456, b:"abcdef")
+
+    # ...
+
+    let mtLit = newLit(mt)
+
+    result = quote do:
+      echo `arg`
+      echo `mtLit`
+
+  myMacro("Hallo")
+  ```
+
+The call to `myMacro` will generate the following code:
+
+  ```nim
+  echo "Hallo"
+  echo MyType(a: 123.456'f64, b: "abcdef")
+  ```
+
+
+Building Your First Macro
+-------------------------
+
+To give a starting point to writing macros we will show now how to
+implement the `myAssert` macro mentioned earlier. The first thing to
+do is to build a simple example of the macro usage, and then just
+print the argument. This way it is possible to get an idea of what a
+correct argument should look like.
+
+  ```nim  test = "nim c $1"
+  import std/macros
+
+  macro myAssert(arg: untyped): untyped =
+    echo arg.treeRepr
+
+  let a = 1
+  let b = 2
+
+  myAssert(a != b)
+  ```
+
+  ```
+  Infix
+    Ident "!="
+    Ident "a"
+    Ident "b"
+  ```
+
+
+From the output, it is possible to see that the argument is an infix
+operator (node kind is "Infix"), as well as that the two operands are
+at index 1 and 2. With this information, the actual macro can be
+written.
+
+  ```nim  test = "nim c $1"
+  import std/macros
+
+  macro myAssert(arg: untyped): untyped =
+    # all node kind identifiers are prefixed with "nnk"
+    arg.expectKind nnkInfix
+    arg.expectLen 3
+    # operator as string literal
+    let op  = newLit(" " & arg[0].repr & " ")
+    let lhs = arg[1]
+    let rhs = arg[2]
+
+    result = quote do:
+      if not `arg`:
+        raise newException(AssertionDefect,$`lhs` & `op` & $`rhs`)
+
+  let a = 1
+  let b = 2
+
+  myAssert(a != b)
+  myAssert(a == b)
+  ```
+
+
+This is the code that will be generated. To debug what the macro
+actually generated, the statement `echo result.repr` can be used, in
+the last line of the macro. It is also the statement that has been
+used to get this output.
+
+  ```nim
+  if not (a != b):
+    raise newException(AssertionDefect, $a & " != " & $b)
+  ```
+
+
+Going further
+-------------
+
+It is possible to create more complex macros by combining different
+`NimNode` symbols with `quote do:` expressions. For example, you may
+use `newStmtList` to build your macro iteratively, and `ident` in cases
+in which you wish to create an identifier from a string, as shown below.
+
+  ```nim
+  import std/macros
+
+  macro createProcedures() =
+    result = newStmtList()
+
+    for i in 0..<10:
+      let name = ident("myProc" & $i)
+      let content = newLit("I am procedure number #" & $i)
+
+      result.add quote do:
+        proc `name`() =
+          echo `content`
+
+  createProcedures()
+  myProc7()
+  ```
+
+The call to `myProc7` will echo `I am procedure number #7`.
+
+
+With Power Comes Responsibility
+-------------------------------
+
+Macros are very powerful. A piece of good advice is to use them as little as
+possible, but as much as necessary. Macros can change the semantics of
+expressions, making the code incomprehensible for anybody who does not
+know exactly what the macro does with it. So whenever a macro is not
+necessary and the same logic can be implemented using templates or
+generics, it is probably better not to use a macro. And when a macro
+is used for something, the macro should better have a well-written
+documentation. For all the people who claim to write only perfectly
+self-explanatory code: when it comes to macros, the implementation is
+not enough for documentation.
+
+Limitations
+-----------
+
+Since macros are evaluated in the compiler in the NimVM, macros share
+all the limitations of the NimVM. They have to be implemented in pure Nim
+code. Macros can start external processes on the shell, but they
+cannot call C functions except those that are built in the
+compiler.
+
+
+More Examples
+=============
+
+This tutorial can only cover the basics of the macro system. There are
+macros out there that could be an inspiration for you of what is
+possible with it.
+
+
+Strformat
+---------
+
+In the Nim standard library, the `strformat` library provides a
+macro that parses a string literal at compile time. Parsing a string
+in a macro like here is generally not recommended. The parsed AST
+cannot have type information, and parsing implemented on the VM is
+generally not very fast. Working on AST nodes is almost always the
+recommended way. But still `strformat` is a good example for a
+practical use case for a macro that is slightly more complex than the
+`assert` macro.
+
+[Strformat](https://github.com/nim-lang/Nim/blob/devel/lib/pure/strformat.nim)
+
+Ast Pattern Matching
+--------------------
+
+Ast Pattern Matching is a macro library to aid in writing complex
+macros. This can be seen as a good example of how to repurpose the
+Nim syntax tree with new semantics.
+
+[Ast Pattern Matching](https://github.com/nim-lang/ast-pattern-matching)
+
+OpenGL Sandbox
+--------------
+
+This project has a working Nim to GLSL compiler written entirely in
+macros. It scans recursively through all used function symbols to
+compile them so that cross library functions can be executed on the GPU.
+
+[OpenGL Sandbox](https://github.com/krux02/opengl-sandbox)