===================
Source Code Filters
===================

.. contents::

A `Source Code Filter (SCF)`  transforms the input character stream to an in-memory
output stream before parsing. A filter can be used to provide templating
systems or preprocessors.

To use a filter for a source file the ``#?`` notation is used::

  #? stdtmpl(subsChar = '$', metaChar = '#')
  #proc generateXML(name, age: string): string =
  #  result = ""
  <xml>
    <name>$name</name>
    <age>$age</age>
  </xml>

As the example shows, passing arguments to a filter can be done
just like an ordinary procedure call with named or positional arguments. The
available parameters depend on the invoked filter. Before version 0.12.0 of
the language ``#!`` was used instead of ``#?``.

**Hint:** With ``--hint[codeBegin]:on`` or ``--verbosity:2``
(or higher) while compiling or `nim check`, Nim lists the processed code after
each filter application.

Usage
=====

First, put your SCF code in a separate file with filters specified in the first line. 
**Note:** You can name your SCF file with any file extension you want, but the
conventional extension is ``.nimf``
(it used to be ``.tmpl`` but that was too generic, for example preventing github to
recognize it as Nim source file).

If we use `generateXML` code shown above and call the SCF file `xmlGen.nimf`
In your `main.nim`:

.. code-block:: nim
  include "xmlGen.nimf"
  
  echo generateXML("John Smith","42")

Pipe operator
=============

Filters can be combined with the ``|`` pipe operator::

  #? strip(startswith="<") | stdtmpl
  #proc generateXML(name, age: string): string =
  #  result = ""
  <xml>
    <name>$name</name>
    <age>$age</age>
  </xml>


Available filters
=================

Replace filter
--------------

The replace filter replaces substrings in each line.

Parameters and their defaults:

  ``sub: string = ""``
    the substring that is searched for

  ``by: string = ""``
    the string the substring is replaced with


Strip filter
------------

The strip filter simply removes leading and trailing whitespace from
each line.

Parameters and their defaults:

  ``startswith: string = ""``
    strip only the lines that start with *startswith* (ignoring leading
    whitespace). If empty every line is stripped.

  ``leading: bool = true``
    strip leading whitespace

  ``trailing: bool = true``
    strip trailing whitespace


StdTmpl filter
--------------

The stdtmpl filter provides a simple templating engine for Nim. The
filter uses a line based parser: Lines prefixed with a *meta character*
(default: ``#``) contain Nim code, other lines are verbatim. Because
indentation-based parsing is not suited for a templating engine, control flow
statements need ``end X`` delimiters.

Parameters and their defaults:

  ``metaChar: char = '#'``
    prefix for a line that contains Nim code

  ``subsChar: char = '$'``
    prefix for a Nim expression within a template line

  ``conc: string = " & "``
    the operation for concatenation

  ``emit: string = "result.add"``
    the operation to emit a string literal

  ``toString: string = "$"``
    the operation that is applied to each expression

Example::

  #? stdtmpl | standard
  #proc generateHTMLPage(title, currentTab, content: string,
  #                      tabs: openArray[string]): string =
  #  result = ""
  <head><title>$title</title></head>
  <body>
    <div id="menu">
      <ul>
    #for tab in items(tabs):
      #if currentTab == tab:
      <li><a id="selected"
      #else:
      <li><a
      #end if
      href="${tab}.html">$tab</a></li>
    #end for
      </ul>
    </div>
    <div id="content">
      $content
      A dollar: $$.
    </div>
  </body>

The filter transforms this into:

.. code-block:: nim
  proc generateHTMLPage(title, currentTab, content: string,
                        tabs: openArray[string]): string =
    result = ""
    result.add("<head><title>" & $(title) & "</title></head>\n" &
      "<body>\n" &
      "  <div id=\"menu\">\n" &
      "    <ul>\n")
    for tab in items(tabs):
      if currentTab == tab:
        result.add("    <li><a id=\"selected\" \n")
      else:
        result.add("    <li><a\n")
      #end
      result.add("    href=\"" & $(tab) & ".html\">" & $(tab) & "</a></li>\n")
    #end
    result.add("    </ul>\n" &
      "  </div>\n" &
      "  <div id=\"content\">\n" &
      "    " & $(content) & "\n" &
      "    A dollar: $.\n" &
      "  </div>\n" &
      "</body>\n")


Each line that does not start with the meta character (ignoring leading
whitespace) is converted to a string literal that is added to ``result``.

The substitution character introduces a Nim expression *e* within the
string literal. *e* is converted to a string with the *toString* operation
which defaults to ``$``. For strong type checking, set ``toString`` to the
empty string. *e* must match this PEG pattern::

  e <- [a-zA-Z\128-\255][a-zA-Z0-9\128-\255_.]* / '{' x '}'
  x <- '{' x+ '}' / [^}]*

To produce a single substitution character it has to be doubled: ``$$``
produces ``$``.

The template engine is quite flexible. It is easy to produce a procedure that
writes the template code directly to a file::

  #? stdtmpl(emit="f.write") | standard
  #proc writeHTMLPage(f: File, title, currentTab, content: string,
  #                   tabs: openArray[string]) =
  <head><title>$title</title></head>
  <body>
    <div id="menu">
      <ul>
    #for tab in items(tabs):
      #if currentTab == tab:
      <li><a id="selected"
      #else:
      <li><a
      #end if
      href="${tab}.html" title = "$title - $tab">$tab</a></li>
    #end for
      </ul>
    </div>
    <div id="content">
      $content
      A dollar: $$.
    </div>
  </body>
