Skip to content

Regular expressions

joshsh edited this page Jul 9, 2026 · 1 revision

Regular expressions in Hydra

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.

The model

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.

Supported syntax

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.

. matches everything, including newline

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.

Escaping

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 \\.

What is intentionally left out (for now)

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 "foo or the empty string", write (foo)?.

These may be added to an advanced tier in future, each with an explicit per-host portability decision.

Semantics

  • matches is 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|ab against ab, 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 / optional result), rather than producing a host-specific error.

See also

  • Regex specification — the formal grammar (BNF), the hydra.regex AST, per-dialect rendering rules, and the host-engine survey.
  • Issue #567 — the design and tracking issue.

Clone this wiki locally