Convenience functions for working with tagged X-expressions.
A set of small but handy functions for improving the readability and reliability of programs that operate on tagged X-expressions (aka tagged-xexprs).
@section{Installation}
@ -20,54 +20,237 @@ At the command line:
After that, you can update the package from the command line:
@verbatim{raco pkg update tagged-xexpr}
@section{What’s a tagged X-expression?}
@section{What’s a tagged-xexpr?}
It's an X-expression with the following grammar:
@racketgrammar[
#:literals (cons list valid-char?)
tagged-xexpr (list symbol (list (list symbol string) ...) xexpr ...)
(cons symbol (list xexpr ...))
@racketgrammar*[
#:literals (cons list symbol? string? xexpr?)
[tagged-xexpr (list tag (list attr ...) element ...)
(cons tag (list element ...))]
[tag symbol?]
[attr (list symbol? string?)]
[element xexpr?]
]
A tagged X-expression has a symbol in the first position— the @italic{tag} — followed by a series of other X-expressions. Optionally, a tagged X-expression can have a list of @italic{attributes} in the second position, which are pairs of symbols and strings.
A tagged X-expression is a list with a symbol in the first position— the @italic{tag} — followed by a series of @italic{elements}, which are other X-expressions. Optionally, a tagged X-expression can have a list of @italic{attributes} in the second position.
Be careful with the last one. Because the key–value pair is not enclosed in a @racket[list], it's interpreted as a nested @racket[_tagged-xexpr] within the first, as you may not find out until you try to read its attributes:
The last one is a common mistake. Because the key–value pair is not enclosed in a @racket[list], it's interpreted as a nested tagged-xexpr within the first tagged-xexpr, as you may not find out until you try to read its attributes:
@margin-note{There's no way of eliminating this ambiguity, short of always requiring an attribute list — even empty —in your tagged X-expression. See also @racket[xexpr-drop-empty-attributes].}
@margin-note{There's no way of eliminating this ambiguity, short of always requiring an attribute list — empty if necessary —in your tagged-xexpr. See also @racket[xexpr-drop-empty-attributes].}
After converting to and from HTML, you get back your original X-expression. Well, not quite. The brackets turned into parentheses — no big deal, since they mean the same thing in Racket. Also true that @racket[string->xexpr] added an empty attribute list after @racket[em]. This is standard procedure, and also benign.
After converting to and from HTML, we get back the original X-expression. Well, almost. The brackets turned into parentheses — no big deal, since they mean the same thing in Racket. Also, per its usual practice, @racket[string->xexpr] added an empty attribute list after @racket[em]. This is also benign.
@section{Why not just use @exec{match}, @exec{quasiquote}, and so on?}
If you prefer those, please do. But I've found two benefits to using module functions:
@bold{Readability.} In code that already has a lot of matching and quasiquoting going on, these functions make it easy to see where & how tagged-xexprs are being used.
@bold{Reliability.} The fact that tagged-xexprs come in two close but not quite equal forms mean that careful coders will always have to take both casesinto account.
The programming is trivial, but the annoyance is real.
@section{Interface}
@defmodule[tagged-xexpr]
@deftogether[(
@defproc[
(tagged-xexpr?
[v any/c])
boolean?]
Simple predicate for functions that operate on @racket[tagged-xexpr]s.
@defproc[
(xexpr-tag?
[v any/c])
boolean?]
@defproc[
(xexpr-attr?
[v any/c])
boolean?]
@defproc[
(xexpr-element?
[v any/c])
boolean?]
)]
Predicates for @racket[_tagged-xexpr]s that implement this grammar:
@racketgrammar*[
#:literals (cons list symbol? string? xexpr?)
[tagged-xexpr (list tag (list attr ...) element ...)
(cons tag (list element ...))]
[tag symbol?]
[attr (list symbol? string?)]
[element xexpr?]
]
@deftogether[(
@defproc[
(xexpr-attrs?
[v any/c])
boolean?]
@defproc[
(xexpr-elements?
[v any/c])
boolean?]
)]
Shorthand for @code{(listof xexpr-attr?)} and @code{(listof xexpr-element?)}.
Assemble a @racket[_tagged-xexpr] from its parts. If you don't need attributes, but you do have elements, you'll need to pass @racket[empty] as the second argument. Note that unlike @racket[xml->xexpr], if the attribute list is empty, it's not included in the resulting expression.
Combine a series of attributes into a single @racket[_tagged-xexpr-attrs] item. This function addresses three annoyances that surface in working with tagged-xexpr attributes.
@itemlist[#:style 'ordered
@item{You can pass the attributes in multiple forms. The list of arguments can include single @racket[_xexpr-attr]s, lists of @racket[_xexpr-attr]s (i.e., what you get from @racket[tagged-xexpr-attrs]), or interleaved symbols and strings (each pair will be concatenated into a single @racket[_xexpr-attr]).}
@item{Attributes with the same name are merged, with the later value taking precedence (i.e., @racket[hash] behavior). }
@item{Attributes are sorted in alphabetical order.}]
Recursively apply @racket[_proc] to all elements, leaving tags and attributes alone. Using plain @racket[map] will only process elements at the top level of the current @racket[_tagged-xexpr]. Usually that's not what you want.
In practice, most @racket[_xexpr-element]s are strings. But woe befalls those who pass string procedures to @racket[map-element], because an @racket[_xexpr-element] can be any kind of @racket[xexpr?], and an @racket[xexpr?] is not necessarily a string.