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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
==============
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:
|