114 lines
3.5 KiB
ReStructuredText
114 lines
3.5 KiB
ReStructuredText
|
==============
|
||
|
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:
|
||
|
|
||
|
.. code-block:: clj
|
||
|
|
||
|
(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 an
|
||
|
expression to be matched, like what a regular expression does for text. Here's
|
||
|
a pattern for a ``try`` form of the above kind:
|
||
|
|
||
|
.. code-block:: clj
|
||
|
|
||
|
(import [funcparserlib.parser [maybe many]])
|
||
|
(import [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:
|
||
|
|
||
|
.. code-block:: clj
|
||
|
|
||
|
(,
|
||
|
['(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.
|
||
|
|
||
|
The best reference for Hy's parsers is the docstrings (use ``(help
|
||
|
hy.model-patterns)``), but again, here are some of the more important ones:
|
||
|
|
||
|
- ``FORM`` matches anything.
|
||
|
- ``SYM`` matches any symbol.
|
||
|
- ``(sym "foo")`` or ``(sym ":foo")`` matches and discards (per ``skip``) the
|
||
|
named symbol or keyword.
|
||
|
- ``(brackets ...)`` matches the arguments in square brackets.
|
||
|
- ``(pexpr ...)`` matches the arguments in parentheses.
|
||
|
|
||
|
Here's how you could write a simple macro using model patterns:
|
||
|
|
||
|
.. code-block:: clj
|
||
|
|
||
|
(defmacro pairs [&rest args]
|
||
|
(import [funcparserlib.parser [many]])
|
||
|
(import [hy.model-patterns [whole SYM FORM]])
|
||
|
(setv [args] (->> args (.parse (whole [
|
||
|
(many (+ SYM FORM))]))))
|
||
|
`[~@(->> args (map (fn [x]
|
||
|
(, (name (get x 0)) (get x 1)))))])
|
||
|
|
||
|
(print (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
|