Overview
Regular expressions written as s-expressions, checked and compiled at compile time.
(import (std rx))
(def phone #rx(seq :bos
(=> :area (= 3 :ascii-digit)) ; three digits, named :area
(lit "-")
(=> :line (= 4 :ascii-digit)) ; four digits, named :line
:eos))
(rx-match? phone "555-1234") ; #t
(rx-search #rx((+ :digit)) "a42b") ; (Some <match on "42">)
A pattern is written #rx(...) in a notation like Scheme’s SRE,
not as a string such as "[a-z]+", so nothing is ever escaped
twice. It is read, checked and compiled to a pattern at compile
time, and a bad pattern is a compile error pointing at the sub-form that
is wrong. The pattern language is described with #rx.
Using a pattern. A #rx(...) is an Rx, a compiled
regular expression. Compiling is cached by pattern, so evaluating the
same #rx(...) again does not build a second regex.
rx-match? and rx-match match the
whole string; rx-search? and rx-search find the leftmost
match anywhere in it; rx-matches finds them all. A match is an
RxMatch, read with rx-text, rx-start,
rx-end and the rx-group family. rx-replace,
rx-replace-with and rx-split rewrite a string. In a
match, the patterns Rx and RxIn do the same as
rx-match and rx-search.
The engine. The matching is BjoRx, a translation of Rust’s
regex crate: it matches in time linear in the length of the
string, over the string’s UTF-8 as it is, and a match is the leftmost
one, with alternatives tried in the order written. Only ($ ...)
and (=> :name ...) capture. It has no backreferences,
lookarounds, atomic groups or conditionals, the constructs that would
tie Bjolang to a backtracking engine for good. So these are not
supported, on purpose:
- Lookarounds, backreferences and conditionals.
- Complementing a whole pattern. A character set can be
complemented:
(~ :digit). - Case-insensitive matching, which would depend on the runtime’s culture tables.
- Patterns built at run time. A pattern is built at compile time,
so
,xinside#rx(...)is an error: every pattern is known statically.
Unicode. The alphabet is the Unicode scalar, as it is for
char and the string cursors: :any is one character, a
match never begins or ends inside one, and a character set may hold any
scalar, (in "😀") included.
The named sets are written out as explicit ranges from tables in
rx.bjo, generated from .NET’s CharUnicodeInfo so that
:alpha agrees with char-alphabetic? below U+10000. A
compiled program means the same thing on every runtime.
See also: rx-match?, rx-search, rx-compile, RxMatch
Reference
Types
RxMatch
type alias ↑ Types
One match: the string it was found in, where, and its groups.
See also: rx-text, rx-start, rx-group, rx-group/n
Functions
rx-match
function ↑ Functions
(: rx-match (-> Rx string (Option RxMatch)))
(rx-match rx s)
Matches a pattern against the whole string.
rx- The pattern.
s- The string.
- returns
- The match, anchored at both ends, or None.
(match (rx-match phone "555-1234")
((Some m) (rx-group m :area)) ; (Some "555")
(None None))
rx-match?
function ↑ Functions
(: rx-match? (-> Rx string bool))
(rx-match? rx s)
Whether a pattern matches the whole string.
rx- The pattern.
s- The string.
- returns
- #t if the whole of s matches.
(rx-match? #rx((+ :ascii-digit)) "123") ; #t
(rx-match? #rx((+ :ascii-digit)) "123a") ; #f
See also: rx-match, rx-search?
rx-search
function ↑ Functions
(: rx-search (-> Rx string (Option RxMatch)))
(rx-search rx s)
The leftmost match anywhere in the string.
rx- The pattern.
s- The string.
- returns
- The leftmost match, or None.
(rx-search #rx((+ :digit)) "a42b") ; (Some <match on "42">)
See also: rx-search?, rx-match, rx-matches, RxIn
rx-search?
function ↑ Functions
(: rx-search? (-> Rx string bool))
(rx-search? rx s)
Whether a pattern matches anywhere in the string.
rx- The pattern.
s- The string.
- returns
- #t if some part of s matches.
rx-matches
function ↑ Functions
(: rx-matches (-> Rx string (Vec RxMatch)))
(rx-matches rx s)
Every non-overlapping match, left to right.
rx- The pattern.
s- The string.
- returns
- The matches, in order; empty if there are none.
(vec-map rx-text (rx-matches #rx((+ :digit)) "a1b22c333")) ; ["1" "22" "333"]
Each search starts where the last match ended. After an empty match it starts one character on, so it never starts inside one.
rx-replace
function ↑ Functions
(: rx-replace (-> Rx string string string))
(rx-replace rx s replacement)
The string with every match replaced by a fixed text.
rx- The pattern.
s- The string.
replacement- The text to put in place of each match, taken literally.
- returns
- The new string.
(rx-replace #rx((+ :space)) "a b c" " ") ; "a b c"
There is no $1 substitution syntax: that would be a second
pattern language, arriving without being decided on. Use
rx-replace-with to build the replacement from the match.
See also: rx-replace-with
rx-replace-with
function ↑ Functions
(: rx-replace-with (-> Rx string (-> RxMatch string) string))
(rx-replace-with rx s f)
The string with every match replaced by what a function makes of it.
rx- The pattern.
s- The string.
f- Function taking a match and returning its replacement string.
- returns
- The new string.
(rx-replace-with #rx(($ (+ :ascii-digit)))
"3 and 4"
(fun (m) (str "<" (rx-text m) ">"))) ; "<3> and <4>"
See also: rx-replace
rx-split
function ↑ Functions
(: rx-split (-> Rx string (Vec string)))
(rx-split rx s)
The pieces of the string between the matches.
rx- The separator pattern.
s- The string.
- returns
- The pieces, in order. The empty pieces before a leading and after a trailing separator are kept; with no match, the whole string.
(rx-split #rx(seq "," (* :space)) "a, b,c") ; ["a" "b" "c"]
An empty match is not a separator, so a pattern that can match nothing does not split between every character.
See also: rx-matches
rx-text
function ↑ Functions
(: rx-text (-> RxMatch string))
(rx-text m)
The text a match matched.
m- The match.
- returns
- The matched part of the string.
rx-input
function ↑ Functions
(: rx-input (-> RxMatch string))
(rx-input m)
The string a match was found in.
m- The match.
- returns
- The whole string that was searched, which its cursors index.
rx-start
function ↑ Functions
(: rx-start (-> RxMatch StringCursor))
(rx-start m)
Where a match begins.
m- The match.
- returns
- A cursor into rx-input, on the match's first character.
rx-end
function ↑ Functions
(: rx-end (-> RxMatch StringCursor))
(rx-end m)
Where a match ends.
m- The match.
- returns
- A cursor into rx-input, just after the match's last character.
rx-group
function ↑ Functions
(: rx-group (-> RxMatch Keyword (Option string)))
(rx-group m name)
The text a named group captured.
m- The match.
name- The keyword the group was named with, in
(=> :name ...). - returns
- The captured text, or None if the group took no part in the match. An unknown name is None too.
(rx-group m :area) ; (Some "555")
See also: rx-group/n, rx-group-start, rx-group-end
rx-group-start
function ↑ Functions
(: rx-group-start (-> RxMatch Keyword (Option StringCursor)))
(rx-group-start m name)
Where a named group’s capture begins.
m- The match.
name- The keyword the group was named with.
- returns
- A cursor into rx-input, or None if the group took no part in the match or there is no such group.
See also: rx-group, rx-group-end
rx-group-end
function ↑ Functions
(: rx-group-end (-> RxMatch Keyword (Option StringCursor)))
(rx-group-end m name)
Where a named group’s capture ends.
m- The match.
name- The keyword the group was named with.
- returns
- A cursor into rx-input just after the capture, or None if the group took no part in the match or there is no such group.
See also: rx-group, rx-group-start
rx-group/n
function ↑ Functions
(: rx-group/n (-> RxMatch int (Option string)))
(rx-group/n m n)
The text a group captured, by its number.
m- The match.
n- The group's number, counted from zero in the order the groups open. Named and unnamed groups are counted alike.
- returns
- The captured text, or None if the group took no part in the match or there is no such group.
(match (rx-search #rx(seq ($ (+ :ascii-alpha)) "=" ($ (+ :ascii-digit))) "x=42")
((Some m) (rx-group/n m 1)) ; (Some "42")
(None None))
See also: rx-group, rx-group-count
rx-group-count
function ↑ Functions
(: rx-group-count (-> RxMatch int))
(rx-group-count m)
How many groups the pattern has.
m- The match.
- returns
- The number of groups, named and unnamed, whether or not they took part in this match.
See also: rx-group/n
rx-compile
function ↑ Functions
(: rx-compile (-> string string Rx))
(rx-compile pattern names)
Compiles a pattern into an Rx: what #rx(…) expands into.
pattern- The pattern, as
#rxwrites it: BjoRx's tree as text. names- The Bjolang name of each group in order, separated by commas. An unnamed group has an empty name.
- returns
- The compiled regular expression.
- raises
System.ArgumentException - When pattern is not a pattern as #rx writes them.
An Rx is a compiled regular expression. Compiling is cached by
the pattern and the names, so the same pattern compiled twice is one
regex.
It is there for #rx, which checks what it emits. The text is one
parenthesized form per node: (l 61 62) a literal, (c 30-39)
a class of scalar ranges, (s ...) a sequence, (a ...) an
alternation, (r least most g pattern) a repetition (- for no
bound, l for lazy), (g n pattern) group n counting
from 1, (A) and (z) the start and end, (e) nothing.
Scalars are hexadecimal. Commas cannot occur in a keyword, so they
cannot occur in a name.
See also: rx-pattern
rx-pattern
function ↑ Functions
(: rx-pattern (-> Rx string))
(rx-pattern rx)
The pattern an Rx was compiled from.
rx- The compiled regular expression.
- returns
- The pattern text, so that a test can check what was emitted and not only what it matches.
See also: rx-compile
Macros
#rx
reader extension ↑ Macros
#rx(pattern)
A regular expression, checked and compiled at compile time.
pattern- The pattern, in the notation below.
#rx(seq :bos (+ :digit) (lit "-") ($ (** 4 4 :digit)) :eos)
#rx(:digit) ; one argument is the pattern itself
#rx((+ :digit))
#rx(+ :digit) ; and the same as #rx((+ :digit))
It expands to (rx-compile "<pattern>" "<names>"), with the
pattern and the group names worked out at compile time. Its value is an
Rx. #rx(seq a b) reads as (#rx seq a b), so a form
may be written with or without its own parentheses.
Two spelling rules. Anything that takes no argument is a
keyword: :bos, :any, :digit. Anything that takes
arguments is a form headed by a symbol: (seq ...), (+ ...),
(in ...). A string or a character is itself: a literal sequence
in pattern position, the set of its characters inside a set.
| Pattern | Matches |
|---|---|
"text", #\c | Exactly that text or character. |
(lit "text") | Exactly that text. It takes one string or character. |
(seq a b ...) | a, then b, and so on. |
(or a b ...) | a or b or … |
(* r), (+ r), (? r) | Zero or more, one or more, or zero or one of r. |
(*? r), (+? r), (?? r) | The same, lazily: as few as will do. |
(** least most r) | r, from least to most times. |
(= n r), (>= n r) | r exactly n times, or at least n times. |
($ r ...) | r, captured in an unnamed group. |
(=> :name r ...) | r, captured in a group named by the keyword. |
:bos, :eos | The beginning and the end of the string. |
:any | Any one character, newlines included. |
:nonl | Any one character but U+000A, the only line break it knows. |
| a named set, or a set form | One character of the set. |
A repetition, $ or => given several patterns takes them
in sequence: (+ "a" "b") is (+ (seq "a" "b")). Counts are
whole numbers. Groups are numbered from zero in the order they open.
Character sets match one character. They are worked out at compile time, and a set that can match nothing is an error.
| Set | Holds |
|---|---|
#\c, "abc" | Those characters. |
(in a b ...) | Every character in any of the sets; (or a b ...) inside a set is the same. |
(/ lo hi ...) | The ranges from lo to hi, the endpoints taken in pairs. |
(~ a b ...) | Every character in none of the sets. |
(- a b ...) | The characters of a that are in none of the others. |
(& a b ...) | The characters in all of the sets. |
(/ #\a #\z #\0 #\9) and (/ "az09") are the same set,
the lowercase letters and the digits: the endpoints are taken in pairs
however they are written. A range runs upwards. A set member or a range
endpoint is a character in U+0000 to U+FFFF; above that, match a
literal instead.
The named sets are keywords. Where two names are given they are the same set.
| Name | Holds |
|---|---|
:alpha, :alphabetic | Letters, in all of Unicode. |
:digit, :numeric | Every Unicode decimal digit, matching char-numeric?. |
:alnum, :alphanumeric | Letters and decimal digits. |
:upper, :upper-case | Uppercase letters. |
:lower, :lower-case | Lowercase letters. |
:space, :whitespace | Whitespace: U+0009 to U+000D, the space, and Unicode’s others. |
:word | Letters, digits, marks and connector punctuation such as _. |
:control | U+0000 to U+001F and U+007F to U+009F. |
:hex-digit | 0 to 9, A to F and a to f. |
:ascii | U+0000 to U+007F. |
:ascii-digit | 0 to 9. |
:ascii-alpha | A to Z and a to z. |
:ascii-alnum | The ASCII letters and digits. |
:any | Every character. |
:nonl | Every character but U+000A. |
Mind :digit. It holds every Unicode decimal digit, such as ٩
and ०, not only 0 to 9. A pattern for the ASCII digits
wants :ascii-digit, and the same goes for :alpha and
:alnum.
See also: rx-compile, rx-match, rx-search
Rx
macro ↑ Macros
(Rx regex pattern)
A match pattern: matches a regular expression against the whole string.
regex- The regular expression: an
Rx, such as a#rx(...)or a name bound to one. pattern- Sub-pattern matched against the
(Option RxMatch)returned byrx-match.
(match line
((Rx phone (Some m)) (str "phone: " (option-ref-or (rx-group m :area) "?")))
((RxIn #rx((+ :digit)) (Some m)) (str "a number: " (rx-text m)))
(_ "neither"))
Rx is also the name of the type of a compiled regular expression,
which rx-compile describes.
A view covers nothing for exhaustiveness, so a match using
Rx or RxIn still needs a clause that matches anything.
The regular expression is evaluated each time the clause is tried. The
compiled regex is cached, but a #rx(...) written in the clause is
still looked up anew each time, so bind the pattern once, at the top
level, and name it in the clause:
(def digits #rx((+ :digit)))
(match text
((RxIn digits (Some m)) (rx-text m))
(_ "none"))
RxIn
macro ↑ Macros
(RxIn regex pattern)
A match pattern: searches the string for a regular expression.
regex- The regular expression: an
Rx, such as a#rx(...)or a name bound to one. pattern- Sub-pattern matched against the
(Option RxMatch)returned byrx-search.
As Rx, but finding the leftmost match anywhere in the string. A
match using it still needs a clause that matches anything.