// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// The bridge to the core library's engine.
///
/// Core ships a regular-expression engine of its own, addressed through
/// `@string.Regex` and the `lexmatch` grammar, with a small set of combinators
/// to build patterns in code. This library reads three bigger grammars and
/// runs the parts of them core cannot. The two meet here: a pattern that says
/// nothing core cannot say is lowered onto core's engine, and the combinators
/// are the same five, spelled the same way, so a pattern assembled in code and
/// a pattern written as text are interchangeable.
///|
/// The pattern as a core `@string.Regex`, when the core engine can run it.
///
/// The core engine speaks one grammar, so this answers only for the patterns
/// that fit inside it: literals, classes, `.`, `^`, `$`, `\b`, `\B`, groups,
/// alternation, quantifiers up to 256. Anything else — backreferences,
/// lookaround, conditionals, recursion, atomic groups, fuzzy matching, `\X`,
/// Unicode word boundaries, Python's `$` — answers `None`, and the pattern
/// runs on this library's own engine instead.
///
/// The result is an ordinary `@string.Regex`: it can be handed to core's
/// `execute`, `find`, `split`, `replace_by`, or to the lexer.
pub fn Pattern::lower(self : Pattern) -> @string.Regex? {
match self.ast.to_lexmatch() {
None => None
Some(text) => Some(@string.Regex::Regex(text)) catch { _ => None }
}
}
///|
/// Build a `Pattern` out of an `Ast` assembled in code.
fn Pattern::from_ast(ast : @parse.Ast) -> Pattern {
let program = @exec.compile(ast)
let pat = match ast.to_lexmatch() {
Some(text) => text
None => ast.dump()
}
Pattern::{
pat,
flags: ast.flags(),
dialect: @parse.Lexmatch,
ast,
program,
partial: false,
}
}
///|
/// A pattern that matches `text` literally, as `@string.Regex::string` does.
///
/// Nothing in `text` is read as syntax — the caller does not have to escape
/// it first.
pub fn Pattern::string(text : StringView) -> Pattern {
Pattern::from_ast(@parse.literal(text))
}
///|
/// This pattern followed by `other`, as `Regex + Regex` in core.
///
/// The result carries this pattern's flags; `other`'s groups are renumbered to
/// follow these, so the composed pattern numbers its groups the way a written
/// one would.
pub impl Add for Pattern with fn add(self : Pattern, other : Pattern) -> Pattern {
Pattern::from_ast(@parse.concat(self.ast, other.ast))
}
///|
#deprecated
pub extend Pattern with Add::{add}
///|
/// This pattern or `other`, as `Regex | Regex` in core.
pub impl BitOr for Pattern with fn lor(self : Pattern, other : Pattern) -> Pattern {
Pattern::from_ast(@parse.either(self.ast, other.ast))
}
///|
#deprecated
pub extend Pattern with BitOr::{lor}
///|
/// This pattern repeated, as `Regex::repeat` in core: `min` to `max` times,
/// greedily unless `greedy` is off.
pub fn Pattern::repeat(
self : Pattern,
min~ : Int32 = 0,
max? : Int32,
greedy~ : Bool = true,
) -> Pattern raise @parse.Refused {
Pattern::from_ast(@parse.repeated(self.ast, min~, max?, greedy~))
}
///|
/// This pattern wrapped in a named capture group, as `Regex::capture` in core.
///
/// The group is numbered after the ones the pattern already has, and
/// `groupindex` gains `name`.
pub fn Pattern::capture(
self : Pattern,
name : String,
) -> Pattern raise @parse.Refused {
Pattern::from_ast(@parse.captured(self.ast, name~))
}