==============
Model patterns
==============

The module ``hy.model-patterns`` provides a library of parser combinators for
parsing complex trees of Hy models. Model patterns exist mostly to help
implement the compiler, but they can also be useful for writing macros.

A motivating example
--------------------

The kind of problem that model patterns are suited for is the following.
Suppose you want to validate and extract the components of a form like::

    (setv form '(try
      (foo1)
      (foo2)
      (except [EType1]
        (foo3))
      (except [e EType2]
        (foo4)
        (foo5))
      (except []
        (foo6))
      (finally
        (foo7)
        (foo8))))

You could do this with loops and indexing, but it would take a lot of code and
be error-prone. Model patterns concisely express the general form of a model
tree to be matched, like what a regular expression does for text. Here's a
pattern for a ``try`` form of the above kind::

    (import
      funcparserlib.parser [maybe many]
      hy.model-patterns *)

    (setv parser (whole [
      (sym "try")
      (many (notpexpr "except" "else" "finally"))
      (many (pexpr
        (sym "except")
        (| (brackets) (brackets FORM) (brackets SYM FORM))
        (many FORM)))
      (maybe (dolike "else"))
      (maybe (dolike "finally"))]))

You can run the parser with ``(.parse parser form)``. The result is::

    #(
      ['(foo1) '(foo2)]
      [
        '([EType1] [(foo3)])
        '([e EType2] [(foo4) (foo5)])
        '([] [(foo6)])]
      None
      '((foo7) (foo8)))

which is conveniently utilized with an assignment such as ``(setv [body
except-clauses else-part finally-part] result)``. Notice that ``else-part``
will be set to ``None`` because there is no ``else`` clause in the original
form.

Usage
-----

Model patterns are implemented as funcparserlib_ parser combinators. We won't
reproduce funcparserlib's own documentation, but here are some important
built-in parsers:

- ``(+ ...)`` matches its arguments in sequence.
- ``(| ...)`` matches any one of its arguments.
- ``(>> parser function)`` matches ``parser``, then feeds the result through
  ``function`` to change the value that's produced on a successful parse.
- ``(skip parser)`` matches ``parser``, but doesn't add it to the produced
  value.
- ``(maybe parser)`` matches ``parser`` if possible. Otherwise, it produces
  the value ``None``.
- ``(some function)`` takes a predicate ``function`` and matches a form if it
  satisfies the predicate.

Some of the more important of Hy's own parsers are:

- :data:`FORM <hy.model_patterns.FORM>` matches anything.
- :data:`SYM <hy.model_patterns.SYM>` matches any symbol.
- :func:`sym <hy.model_patterns.sym>` matches and discards (per ``skip``) the
  named symbol or keyword.
- :func:`brackets <hy.model_patterns.brackets>` matches the arguments in square
  brackets.
- :func:`pexpr <hy.model_patterns.pexpr>` matches the arguments in parentheses.

Here's how you could write a simple macro using model patterns::

    (defmacro pairs [#* args]
      (import
        funcparserlib.parser [many]
        hy.model-patterns [whole SYM FORM])
      (setv [args] (.parse
        (whole [(many (+ SYM FORM))])
        args))
      `[~@(gfor  [a1 a2] args  #((str a1) a2))])

    (print (hy.repr (pairs  a 1  b 2  c 3)))
    ; => [#("a" 1) #("b" 2) #("c" 3)]

A failed parse will raise ``funcparserlib.parser.NoParseError``.

.. _funcparserlib: https://github.com/vlasovskikh/funcparserlib

Reference
---------

.. automodule:: hy.model_patterns
   :members:
