=encoding utf8

=head1 NAME

Sidef::Types::Regex::Regex - Regular expression type for pattern matching, searching, and text substitution.

=head1 DESCRIPTION

This class implements regular expression pattern matching functionality in Sidef. The Regex class provides methods for compiling patterns, matching against strings, comparing patterns, and combining patterns together.

Regular expressions in Sidef support standard Perl-compatible regular expression syntax, including character classes, quantifiers, anchors, capturing groups, and various modifiers.

A regex constructed with the trailing C<g> modifier (e.g. C</\d+/g>, or C<Regex('\d+', 'g')>) behaves as a I<stepping> regex: each match — whether performed through L</match> (or its aliases C<run>/C<=~>) or the dedicated global-matching methods — starts where the previous one on that same object left off, and a failed match resets that internal position back to the start. Use L</global_match> or L</global_matches> if you want this stepping behavior on a regex that was not itself built with the C<g> modifier.

=head1 SYNOPSIS

    # Create a regex object
    var regex = /\d+/
    var regex2 = Regex('\w+', 'i')  # case-insensitive

    # Match against a string
    if (("hello123" =~ /\d+/)) {
        say "Found digits!"
    }

    # Extract all matches
    var matches = /(\d+)/.gmatches("abc123def456")
    say matches  # [123, 456]

    # Concatenate regexes
    var combined = (/foo/ + /bar/)  # matches "foobar"

=head1 INHERITS

Inherits methods from L<Sidef::Object::Object>.

=head1 CONSTRUCTION

=head2 new

    Regex.new(pattern, mode)
    Regex(pattern, mode)

Compiles a new Regex object from a pattern string and an optional modifier string. Both C<pattern> and C<mode> may be given as plain strings or as string-like objects (they are stringified automatically).

    var re1 = Regex('\d+')
    var re2 = Regex('\w+', 'i')       # case-insensitive
    var re3 = Regex('^start', 'gm')   # global + multi-line

Recognized modifier letters include C<i> (case-insensitive), C<s> (single-line, dot matches newline), C<m> (multi-line, C<^>/C<$> match line boundaries), C<x> (extended, ignore whitespace/comments in the pattern), and the special C<g> (global/stepping matching, handled separately from the others — see L</DESCRIPTION>).

Aliases: I<call>

=cut

=head1 MATCHING

=head2 match

    self.match(string)
    self.match(string, pos)

Matches the regex pattern against a string, returning a L<Sidef::Types::Regex::Match> object. The Match object is truthy if the pattern was found. An optional starting offset C<pos> may be given to begin the search partway through the string; this is only meaningful for non-stepping regexes (see L</DESCRIPTION>) since a stepping regex tracks its own position internally.

    var text = "Hello, World!"
    var match = (text =~ /(W\w+)/)

    if (match) {
        say match  # "World"
    }

    # With capturing groups
    if ("age: 25" =~ /(\d+)/) { |m|
        say m[0]  # 25
    }

    # With an explicit starting offset
    var m = "aaa".match(/(a+)/, 1)

Aliases: I<run>, I<=~>

=cut

=head2 global_match

    self.global_match(string)
    self.global_match(string, pos)

Performs a single step of a global (stepping) match against C<string>, regardless of whether this regex was itself built with the C<g> modifier. Returns a L<Sidef::Types::Regex::Match> object.

Called without C<pos>, each call continues from wherever the previous global match on this regex left off — so repeated calls walk through the successive matches in the string one at a time, and a failed call resets the internal position back to the start, ready to begin again.

    var text = "abc123def456ghi789"
    var re = /(\d+)/

    var m1 = re.global_match(text)
    say m1  # 123
    var m2 = re.global_match(text)
    say m2  # 456
    var m3 = re.global_match(text)
    say m3  # 789
    var m4 = re.global_match(text)
    say m4.matched  # false — and the internal position has reset

Passing an explicit C<pos> performs a one-off match starting at that offset without disturbing the ongoing stepping position, so it's safe to use for a quick look-ahead in the middle of an iteration:

    re.global_match(text)          # advances the internal position
    re.global_match(text, 0)       # a one-off probe at offset 0; doesn't disturb the above
    re.global_match(text)          # continues from where the first call left off

Aliases: I<gmatch>

=cut

=head2 global_matches

    self.global_matches(string)
    self.global_matches(string, pos)
    self.global_matches(string, block)
    self.global_matches(string, pos, block)

Finds all successive matches of the regex pattern in C<string>, driven by the same stepping mechanism as L</global_match>, and returns them as an array. The optional arguments after C<string> may be given in either order: a Number sets the starting offset, and a Block is invoked for each match (as C<block.run(index, match)>), with the block's return value collected in place of the raw Match object.

The scan stops automatically once the match position stops advancing, which protects against infinite loops on patterns that can match a zero-width string.

    var text = "The year 2024 has 365 days"
    var numbers = /(\d+)/.gmatches(text)
    say numbers  # [2024, 365]

    # Starting from an offset
    var more = /(\d+)/.gmatches("a1b2c3d4", 4)

    # With a block, transforming each match
    var tagged = /(\d+)/.gmatches(text, { |i, m| "match ##{i}: #{m}" })

Aliases: I<gmatches>, I<all_matches>, I<map_matches>, I<repeated_match>

=cut

=head1 COMBINING REGEXES

=head2 add

    a + b

Concatenates two regular expressions (or a regex and a string) to create a new regex that matches the first pattern followed immediately by the second.

    var re1 = /foo/
    var re2 = /bar/
    var combined = (re1 + re2)  # matches "foobar"

    say (("foobar" =~ combined) ? true : false)   # true
    say (("foo bar" =~ combined) ? true : false)  # false

The resulting regex still matches correctly, but note that its own C<dump>/C<to_s> representation embeds the operands' already-compiled forms rather than a clean concatenation of their raw pattern text — see L</NOTES>.

Aliases: I<concat>, I<+>

=cut

=head2 union

    a | b

Builds a new regex from the raw pattern text of both operands, inserting C<|> between them. If the right-hand side is a plain string, it is treated literally and quoted before being added.

The modifiers of both operands are merged into the result. If the same flag appears more than once across the inputs, the resulting regex preserves the highest repetition count seen for that flag. The global/stepping flag is kept if either operand has it. Any extra modifier letters passed as a third argument are appended to the merged flags before the final regex is built.

    var re = (/cat/ | /dog/)

This produces a regex equivalent to C</cat|dog/>.

A literal string on the right-hand side is escaped automatically:

    var re = (/cat/ | "d.g")

This produces a regex equivalent to C</cat|d\.g/>.

An optional third argument lets you add extra modifier letters when calling this as a method:

    var re2 = /foo/i.union(/bar/m, 'x')

Aliases: I<|>

=cut

=head1 COMPARISON

=head2 eq

    a == b

Tests whether two regular expressions are equal, by comparing their compiled forms (pattern text plus embedded modifiers such as C<i>, C<m>, C<s>, C<x>).

    var re1 = /abc/i
    var re2 = /abc/i
    var re3 = /abc/

    say ((re1 == re2) ? true : false)  # true
    say ((re1 == re3) ? true : false)  # false (different modifiers)

The C<g> (stepping) modifier is tracked separately from the compiled pattern and is not reflected in this comparison — see L</NOTES>.

Aliases: I<==>

=cut

=head2 ne

    a != b

The negation of L</eq>: true if the regex patterns differ in their compiled form.

    var re1 = /abc/
    var re2 = /xyz/
    say ((re1 != re2) ? true : false)  # true

Aliases: I<!=>, I<≠>

=cut

=head2 cmp

    a <=> b

Performs a three-way comparison between two regular expressions based on their compiled string form. Returns -1, 0, or 1.

    var re1 = /abc/
    var re2 = /xyz/
    say (re1 <=> re2)  # -1

    var re3 = /abc/
    say (re1 <=> re3)  # 0

Aliases: I<<< <=> >>>

=cut

=head2 lt

    a < b

Returns true if the first regex is lexicographically less than the second, based on L</cmp>.

    var re1 = /abc/
    var re2 = /xyz/
    say ((re1 < re2) ? true : false)  # true

Aliases: I<<< < >>>

=cut

=head2 le

    a <= b

Returns true if the first regex is lexicographically less than or equal to the second, based on L</cmp>.

    var re1 = /abc/
    var re2 = /abc/
    say ((re1 <= re2) ? true : false)  # true

Aliases: I<<< <= >>>, I<≤>

=cut

=head2 gt

    a > b

Returns true if the first regex is lexicographically greater than the second, based on L</cmp>.

    var re1 = /xyz/
    var re2 = /abc/
    say ((re1 > re2) ? true : false)  # true

Aliases: I<<< > >>>

=cut

=head2 ge

    a >= b

Returns true if the first regex is lexicographically greater than or equal to the second, based on L</cmp>.

    var re1 = /xyz/
    var re2 = /abc/
    say ((re1 >= re2) ? true : false)  # true

Aliases: I<<< >= >>>, I<≥>

=cut

=head1 CONVERSION

=head2 to_regex

    self.to_regex

Returns the regex object itself, unchanged. Provided for interface compatibility in places that expect a "convert to Regex" method.

    var regex = /test/i
    var same = regex.to_regex
    say ((regex == same) ? true : false)  # true

Aliases: I<to_re>

=cut

=head2 dump

    self.dump

Returns a string representation of the regular expression in slash-delimited form, including its pattern and modifiers, e.g. C</hello/i>.

    var regex = /hello/i
    say regex.dump  # /hello/i

    var complex = /\d{3}-\d{4}/
    say complex.dump  # /\d{3}-\d{4}/

Note that this differs from a Regex object's implicit stringification (e.g. via string interpolation), which instead shows Perl's own compiled-pattern form (something like C<(?^i:hello)>).

Aliases: I<to_s>, I<to_str>

=cut

=head1 LOW-LEVEL ACCESS

=head2 get_value

    self.get_value

Returns the underlying compiled pattern object backing this Regex, used internally to implement string, boolean, and numeric context conversions. In boolean context a Regex object is always true, since it is always a defined compiled pattern (unlike a Match object, whose truthiness reflects whether it actually matched). Prefer L</dump> for a readable string form.

=cut

=head1 REGEX MODIFIERS

Sidef supports various regex modifiers that can be applied when creating regular expressions:

=over 4

=item * B<i> - Case-insensitive matching

=item * B<s> - Single-line mode (dot matches newlines)

=item * B<m> - Multi-line mode (^ and $ match line boundaries)

=item * B<x> - Extended mode (ignore whitespace and comments in pattern)

=item * B<g> - Stepping/global matching (see L</DESCRIPTION>)

=back

Example usage:

    var case_insensitive = /hello/i
    var multiline = /^start/m
    var combined = /pattern/ims

=head1 EXAMPLES

=head2 Extracting All Numbers From Text

    var text = "The year 2024 has 365 days"
    var numbers = /(\d+)/.gmatches(text)
    say numbers  # [2024, 365]

=head2 Stepping Through Matches One at a Time

    var re = /(\d+)/
    var text = "a1b2c3"

    loop {
        var m = re.global_match(text)
        m.matched || break
        say m
    }

=head2 Building a Pattern From Parts

    var word = /\w+/
    var space = /\s+/
    var two_words = (word + space + word)

    say ((("Hello World" =~ two_words)) ? true : false)  # true

=head2 Comparing Regexes

    var re1 = /abc/i
    var re2 = /abc/i

    say ((re1 == re2) ? true : false)  # true

=head1 SEE ALSO

=over 4

=item * L<Sidef::Types::Regex::Match> - Result object returned by matching methods

=item * L<Sidef::Types::String::String> - String manipulation methods

=item * L<Sidef::Types::Array::Array> - Array operations for processing matches

=back

=head1 NOTES

The C<g> modifier is tracked separately from the compiled pattern, so two regexes that differ only in whether they have a trailing C<g> compare as equal under L</eq>/L</ne>/L</cmp> and the related operators.

=cut
