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 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:
(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:
(, ['(foo1) '(foo2)] [ '([EType1] [(foo3)]) '([e EType2] [(foo4) (foo5)]) '( [(foo6)])] None '((foo7) (foo8)))
which is conveniently utilized with an assignment such as
except-clauses else-part finally-part] result). Notice that
will be set to
None because there is no
else clause in the original
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
functionto change the value that’s produced on a successful parse.
parser, but doesn’t add it to the produced value.
parserif possible. Otherwise, it produces the value
(some function)takes a predicate
functionand matches a form if it satisfies the predicate.
The best reference for Hy’s parsers is the docstrings (use
hy.model-patterns)), but again, here are some of the more important ones:
SYMmatches any symbol.
(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:
(defmacro pairs [#* args] (import [funcparserlib.parser [many]]) (import [hy.model-patterns [whole SYM FORM]]) (setv [args] (->> args (.parse (whole [ (many (+ SYM FORM))])))) `[~@(->> args (map (fn [x] (, (str (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