-
Notifications
You must be signed in to change notification settings - Fork 25
Regular expressions
Hydra's hydra.lib.regex primitives — find, findAll, matches, replace, replaceAll, split —
operate on regular-expression patterns given as strings.
Historically each Hydra host delegated these primitives to its native engine
(java.util.regex on the JVM, Python's re, Text.Regex.TDFA on Haskell, JavaScript's RegExp, and
so on), which meant a pattern's meaning was host-defined: the same string could match differently,
or fail to compile at all, depending on where it ran.
Hydra now defines a single, translingual regular-expression syntax. A pattern means the same thing on every host. This page describes that syntax as a user sees it; the formal grammar, abstract syntax tree, and per-host translation rules live in the contributor-facing regex specification.
A Hydra regular expression is parsed into a host-independent abstract syntax tree (the hydra.regex
kernel type), and each host translates that tree into its own engine's dialect.
The public string → string translation on any target is print.<target>.regex ∘ parse.regex:
parse the pattern once into the shared AST, then render it into the target dialect.
The syntax is the familiar intersection of the POSIX ERE and ECMA-262 (JavaScript) families — the constructs almost every regex user already knows, chosen so that each one maps faithfully onto every host engine.
| Construct | Meaning |
|---|---|
| literal | An ordinary character matches itself. A metacharacter is matched by escaping it: \., \*, \(. |
. |
Matches any single character, including newline (see note below). |
[...] |
A character class: matches any one of the listed characters. Ranges are written a-z. |
[^...] |
A negated class: matches any one character not listed. [^\n] is "any character except newline". |
^ $
|
Anchors: the start and end of the input. |
(...) |
A group, for precedence and quantification. Groups do not capture (there are no backreferences). |
| |
Alternation: cat|dog matches either branch. |
* + ?
|
Zero-or-more, one-or-more, zero-or-one. |
{n} {n,} {n,m}
|
Exactly n, at least n, and between n and m (inclusive) repetitions. |
Characters are full Unicode scalar values (up to U+10FFFF), so astral characters (emoji, etc.) are first-class both as literals and inside classes and ranges.
Unlike most regex engines — whose . silently excludes the newline character — Hydra's . means
literally any character.
If you want the traditional line-oriented behavior, write it explicitly as [^\n].
This removes a long-standing footgun (.* mysteriously stopping at a line break) and makes "any"
mean any.
A literal metacharacter is always written with a backslash — Hydra never relies on positional
conventions (such as POSIX's "a ] immediately after [ is a literal").
Inside a class, a literal ], ^, -, or \ is written \], \^, \-, or \\.
To keep the "maps everywhere" guarantee, the following common-but-non-portable features are not in the core syntax. Most are POSIX/PCRE/ECMA divergences that cannot be rendered faithfully on every host:
- Perl shorthand classes
\d,\w,\s(absent from POSIX ERE). - Non-greedy / lazy quantifiers (
*?,+?). - Lookaround (
(?=...),(?!...)), backreferences, and named groups. - Unicode property classes (
\p{...}) and POSIX class names ([[:alpha:]]). - Inline flags (
(?i)), the up-to-m quantifier{,m}(redundant with{0,m}), and the empty / negated-empty classes[]and[^]. - Empty alternation branches (
a|,a||b): to match "fooor the empty string", write(foo)?.
These may be added to an advanced tier in future, each with an explicit per-host portability decision.
-
matchesis whole-string-anchored: a pattern must match the entire input, on every host. The substring-oriented primitives (find,findAll,split,replace,replaceAll) are not anchored. -
Anchors
^and$match at the start and end of the whole input (not at internal line breaks). -
Match preference is leftmost-longest (POSIX semantics). For an ambiguous pattern such as
a|abagainstab, Hydra specifies the longer match (ab). Hosts whose native engine prefers the leftmost-first alternative may differ on such patterns; the conformance suite pins these cases. -
A pattern is well-formed exactly when it parses under
parse.regex. Ill-formed patterns fail in the same, portable way on every host (via the primitives'either/optionalresult), rather than producing a host-specific error.
-
Regex specification
— the formal grammar (BNF), the
hydra.regexAST, per-dialect rendering rules, and the host-engine survey. - Issue #567 — the design and tracking issue.