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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
|
==============
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:
.. The below must be documented in this file rather than using
autodoc, due to a regression in Python 3.14.
.. data:: FORM
Match any token.
.. data:: SYM
Match a :class:`Symbol <hy.models.Symbol>`.
.. data:: KEYWORD
Match a :class:`Keyword <hy.models.Keyword>`.
.. data:: STR
Match a :class:`String <hy.models.String>`.
.. data:: LITERAL
Match any model type denoting a literal.
|