t-regx/T-Regx

View on GitHub
ChangeLog.md

Summary

Maintainability
Test Coverage
T-Regx Changelog
================

Incoming
--------

* Soon

Added in 0.41.5
---------------

* Deprecation
    * Deprecate `Pattern` methods unqualified for release candidate.
        * Deprecate `Pattern.fails()`
        * Deprecate `Pattern.search()`
        * Deprecate `Pattern.prune()`
        * Deprecate `Pattern.cut()`
        * Deprecate `Pattern.valid()`
        * Deprecate `Pattern::STUDY`
        * Deprecate `Pattern::RESTRICTIVE_ESCAPE`
        * Deprecate `Pattern::DOLLAR_ENDONLY`
        * Deprecate `Search`
    * Deprecate `Matcher` methods unqualified for release candidate.
        * Deprecate `Matcher.fails()`
        * Deprecate `Matcher.findFirst()`
        * Deprecate `Matcher.only()`
        * Deprecate `Matcher.nth()`
        * Deprecate `Matcher.forEach()`
        * Deprecate `Matcher.map()`
        * Deprecate `Matcher.filter()`
        * Deprecate `Matcher.flatMap()`
        * Deprecate `Matcher.toMap()`
        * Deprecate `Matcher.distinct()`
        * Deprecate `Matcher.stream()`
        * Deprecate `Matcher.groupBy()`
        * Deprecate `Matcher.groupByCallback()`
        * Deprecate `Matcher.reduce()`
        * Deprecate `Matcher.subject()`
        * Deprecate `Matcher.groupNames()`
        * Deprecate `Matcher.groupsCount()`
        * Deprecate `Matcher.groupExists()`
    * Deprecate `Detail` methods unqualified for release candidate.
        * Deprecate `Detail.get()`, to be renamed to `.group()`
        * Deprecate `Detail.group()`, to be refactored to return `string`
        * Deprecate `Detail.groups()`
        * Deprecate `Detail.namedGroups()`
        * Deprecate `Detail.matched()`, to be renamed to `.groupMatched()`
        * Deprecate `Detail.all()`
        * Deprecate `Detail.groupNames()`, to be refactored to `Pattern.groupNames()`
        * Deprecate `Detail.groupsCount()`, to be refactored to `Pattern.groupsCount()`
        * Deprecate `Detail.length()`
        * Deprecate `Detail.offset()`, to be renamed to `.start()`
        * Deprecate `Detail.tail()`, to be renamed to `.end()`
        * Deprecate `Detail.byteLength()`
        * Deprecate `Detail.byteOffset()`, to be renamed to `.byteStart()`
        * Deprecate `Detail.byteTail()`, to be renamed to `.byteEnd()`
        * Deprecate `Detail.byteLength()`
        * Deprecate `Detail.toInt()`
        * Deprecate `Detail.isInt()`
        * Deprecate `Intable`
        * `Detail` is to be refactored from an interface to a class
    * Deprecate `Group`, as unqualified for release candidate.
        * Deprecate `Group`, use `Detail` methods instead
        * Deprecate `Group.text()`, to be refactored to `Detail.group()`
        * Deprecate `Group.isInt()`
        * Deprecate `Group.toInt()`
        * Deprecate `Group.matched()`, to be refactored to `Detail.groupMatched()`
        * Deprecate `Group.equals()`, use `Detail.groupOrNull()` instead
        * Deprecate `Group.or()`, to be refactored to `Detail.groupOrNull()`
        * Deprecate `Group.index()`, use `Pattern.groupNames()` to calculate index based on name
        * Deprecate `Group.name()`, use `Pattern.groupNames()` to calculate name based on index
        * Deprecate `Group.usedIdentifier()`
        * Deprecate `Group.all()`
        * Deprecate `Group.subject()`
        * Deprecate `Group.offset()`, to be refactored to `Detail.groupStart()`
        * Deprecate `Group.byteOffset()`, to be refactored to `Detail.groupByteStart()`
        * Deprecate `Group.tail()`, to be refactored to `Detail.groupEnd()`
        * Deprecate `Group.byteTail()`, to be refactored to `Detail.groupByteEnd()`
        * Deprecate `Element` interface
        * Deprecate `Structure` interface
    * Deprecate `Replace` methods unqualified for release candidate.
        * Deprecate `Pattern.replace().with()`, to be refactored to `Pattern.replace()`
        * Deprecate `Pattern.replace().withGroup()`, to be refactored to `Pattern.replaceGroup()`
        * Deprecate `Pattern.replace().withReferences()`
        * Deprecate `Pattern.replace().callback()`, to be refactored to `Pattern.replaceCallback()`
        * Deprecate `Pattern.replace().count()`, to be refactored to `Pattern.replaceCount()`
        * Deprecate `Pattern.replace().exactly()`
        * Deprecate `Pattern.replace().atMost()`
        * Deprecate `Pattern.replace().atLeast()`
        * Deprecate `Pattern.replace().first()`, use `$limit` argument in replace methods
        * Deprecate `Pattern.replace().limit()`, use `$limit` argument in replace methods
    * Deprecate exceptions unqualified for release candidate.
        * Deprecate `EmptyOptionalException`
        * Deprecate `ExplicitDelimiterRequiredException`, to be refactored to `DelimiterException`
        * Deprecate `GroupNotMatchedException`, to be refactored to `GroupException`
        * Deprecate `IntegerFormatException`
        * Deprecate `IntegerOverflowException`
        * Deprecate `InternalCleanRegexException`
        * Deprecate `InvalidIntegerTypeException`
        * Deprecate `InvalidReplacementException`, to be refactored to `\UnexpectedValueException`
        * Deprecate `InvalidReturnValueException`
        * Deprecate `MalformedPcreTemplateException`, to be refactored to `SyntaxException`
        * Deprecate `MaskMalformedPatternException`
        * Deprecate `NonexistentGroupException`, to be refactored to `GroupException`
        * Deprecate `NoSuchNthElementException`
        * Deprecate `NoSuchStreamElementException`
        * Deprecate `PatternMalformedPatternException`
        * Deprecate `PlaceholderFigureException`
        * Deprecate `ReplacementExpectationFailedException`
        * Deprecate `SubjectNotMatchedException`, to be refactored to `NoMatchException`
        * Deprecate `UnevenCutException`
        * Deprecate `MalformedPatternException`, to be refactored to `SyntaxException`
        * Deprecate `PatternSyntaxException`
        * Deprecate `CatastrophicBacktrackingException`, to be refactored to `BacktrackException`
        * Deprecate `CompilePregException`, to be refactored to `SyntaxException`
        * Deprecate `JitStackLimitException`, to be refactored to `JitException`
        * Deprecate `PatternStructureException`
        * Deprecate `PregException`
        * Deprecate `PregMalformedPatternException`, to be refactored to `SyntaxException`
        * Deprecate `RuntimePregException`, to be refactored to `MatchException`
        * Deprecate `SubjectEncodingException`, to be refactored to `UnicodeException`
        * Deprecate `SuspectedReturnPregException`
    * Deprecate `preg::` methods
    * Deprecate `PcrePattern`, to be refactored to `PregPattern`
    * Deprecate factory methods unqualified for release candidate.
        * Deprecate `Pattern.list()`
        * Deprecate `PatternList`
    * Interface of prepared patterns will change in release candidate:
        * Deprecate `Pattern.literal()`, to be refactored
        * Deprecate `Pattern.alternation()`, to be refactored
        * Deprecate `Pattern.builder()`, to be refactored
        * Deprecate `Pattern.template()`, to be refactored
        * Deprecate `PatternTemplate`, to be refactored
        * Deprecate `TemplateBuilder`, to be refactored
    * Deprecate `Optional`
    * Deprecate `Pcre` helper
    * Any class in `Internal/` namespace is subject to change without notice

Added in 0.41.5
---------------

* Bug fixes
   * Empty alteration previously always matched in-place, now empty alteration never matches.
      * `Pattern::alteration([])` doesn't match anything
      * `Pattern::template('@')->alteration([])` doesn't match anything
      * `Pattern::builder('@')->alteration([])` doesn't match anything
   * Corrected exception when `Pattern::builder()` was used with not enough bound figures.
* Other
   * Changed signature of `Pattern::of()`, so `$modifiers` argument default value is no longer
     `null` but an empty string. It poses no difference in the behaviour of the method, but the
      type of the argument is similifed from `null|string` to `string` in documentation tools.

Added in 0.41.4
---------------

No functional changes.

Added in 0.41.3
---------------

* Other
    * Relaxed composer php version requirement up.

Added in 0.41.2
---------------

* Bug fixes
    * Fixed a bug with improper parsing of character classes starting with `^]`.
    * Fixed a bug with improper client error handlers, when user-supplied callbacks threw exceptions.
* Performance
    * Increased pattern instantiating performance by parsing continuous literal figures as one entity.
    * Additional performance optimisations with usage of xDebug profiler and QCacheGrind.
    * Increased performance of parsing certain features by using PHP built-in functions, instead of loops.
    * Increased performance of prepared patterns by looking up strategies by hashtable.
    * Reimplemented some of internal parser logic without unnecessary conditions and iterations.

Added in 0.41.1
---------------

* Features
    * Updated internal error handling, so T-Regx works in an environment with overriden error handler.

Added in 0.41.0
---------------

* Features
    * Since `0.41.0` T-Regx officially supports PHP 8.2.
    * Added full support for `n` modifier for every supported PHP version: `7.1` - `8.2`
        * Despite modifier `/n` being only valid in vanilla regexp in PHP only since `8.2`,
          modifier `/n` is fully supported in T-Regx in each PHP version.

        * Added full support for `n` modifier in `Pattern`
            * `Pattern::of($pattern, 'n')` is now accepted
            * `Pattern::inject($pattern, $figures, 'n')` is now accepted
            * `Pattern::template($pattern, 'n')` is now accepted
            * `Pattern::builder($pattern, 'n')` is now accepted
            * `Pattern::literal($text, 'n')` is now accepted
            * `Pattern::alteration($texts, 'n')` is now accepted
            * `Pattern::mask($mask, $keywords, 'n')` is now accepted
            * `Pattern::list()` now accepts patterns with `n` modifier
        * Added full support for `n` modifier in `PcrePattern`
            * `PcrePattern::of("/$pattern/n", )` is now accepted
            * `PcrePattern::inject("/$pattern/n", $figures)` is now accepted
            * `PcrePattern::template("/$pattern/n")` is now accepted
            * `PcrePattern::builder("/$pattern/n")` is now accepted
    * Added public modifier constant to `Pattern`.
        * Added `Pattern::NO_AUTOCAPTURE`

Added in 0.40.0
---------------

* Breaking changes
    * Removed `SubjectNotMatchedException.getSubject()`.
    * Moved `ChainedReplace` to `TRegx\CleanRegex\Replace\ `
    * Moved `PatternTemplate` to `TRegx\CleanRegex\ `
    * Moved `TemplateBuilder` to `TRegx\CleanRegex\ `
    * Updated `MalformedPatternException` message `Null byte in regex` to `Pattern may not contain null-byte`.
    * Updated `MalformedPatternException` message `...unexpected delimiter '\0'` to `...null-byte delimiter`.
* Bug fixes
    * Fixed a bug when using prepared patterns with `mask()` didn't take modifiers into account when running validation
        * Fixed the bug in `Pattern::mask()`
        * Fixed the bug in `Pattern::template()->mask()`
        * Fixed the bug in `Pattern::builder()->mask()`
        * Fixed the bug in `PcrePattern::template()->mask()`
        * Fixed the bug in `PcrePattern::builder()->mask()`
* Features
    * Added public modifier constants to `Pattern`.
        * Added `Pattern::CASE_INSENSITIVE`
        * Added `Pattern::UNICODE`
        * Added `Pattern::MULTILINE`
        * Added `Pattern::SINGLELINE`
        * Added `Pattern::IGNORE_WHITESPACE`
        * Added `Pattern::ANCHORED`
        * Added `Pattern::GREEDYNESS_INVERTED`
        * Added `Pattern::RESTRICTIVE_ESCAPE`
        * Added `Pattern::DUPLICATE_NAMES`
        * Added `Pattern::DOLLAR_ENDONLY`
        * Added `Pattern::STUDY`

      From now on, patterns can be constructed with such constants, for example:
      ```php
      Pattern::of('^foo$', Pattern::CASE_INSENSITIVE . Pattern::MULTILINE)
      ```

      String literal modifiers continue to work as before
      ```php
      Pattern::of('^foo$', 'im');
      ```
      
* Other
    * Exception `PlaceholderFigureException` now extend correctly implements `PatternException`.

Added in 0.39.0
---------------

* Breaking changes
    * Removed `Pattern.forArray().filter()`. Use `Pattern.filter()` instead.
    * Removed `Pattern.forArray().filterAssoc()`.
    * Removed `Pattern.forArray().filterByKeys()`.
    * Renamed `PatternList.chainedReplace()` to `PatternList.replace()`
* Bug fixes
    * Fixed a bug when using `PcrePattern` with delimiter `\n`, `\v`, `\f`, `t` didn't throw
      appropriate `MalformedPatternException`
* Features
    * Added `Pattern.filter()`, which filters subjects which match the pattern (exactly like previous `Pattern.forArray().filter()`).
    * Added `Pattern.reject()`, which filters subjects which do not match the pattern.
* Other
    * Public exceptions now extend `\RuntimeException`, instead of `\Exception`.

Added in 0.38.0
---------------

* Breaking changes
    * Revamped `Pattern.replace()` functionality.
    * Removed `focus()` and `substitute()` - useful feature, however it proved to be unreliable when used with
      look-aheads, look-behinds and `\K` resets. The functionality is available through a separate `"t-regx"` package.
    * Callback in `replace().callback()` no longer accepts `Detail` or `Group` as a return value.

Migration guide:

- Removed `focus()` - To continue using functionality, use a separate package.
- Removed `Group.substitute()` - To continue using functionality, use a separate package.
- Removed `replace().by()` - use `replace().callback()` or `replace().withGroup()`
- Removed `replace().counting()` - use `replace.count()`
- Removed `replace().all()` - omit call to `all()`
- Updated `replace().callback()` return type - return `Detail.text()` or `Group.text()` as a return value.
- Renamed `ReplaceLimit` to `Replace`.

Added in 0.37.2
---------------

* Bug fixes
    * Fixed a bug when using internal option setting before newline convention setting
      could've ended with improper placeholder parsing in comments.

Added in 0.37.1
---------------

* Features
    * Add `PatternList.count()`, which allows to count occurrences of many patterns in a subject.
* Bug fixes
    * Corrected prepared patterns in malformed patterns.

      When malformed pattern is used, `foo(?<bar@cat>door)` (because of unalloyed `@` character in the group name),
      previously T-Regx would treat `@` as a placeholder, and inject a figure into it. Currently, T-Regx
      ignores `@` character in the group name, so `PlaceholderFigureException` is not thrown, but
      `MalformedPatternException` is thrown instead.

Added in 0.37.0
---------------

* Breaking changes
    * Renamed `Pattern.compose()` to `Pattern.list()`
    * Renamed `CompositePattern` to `PatternList`
    * Renamed `flatMapAssoc()` to `toMap()`
        * Renamed `match().flatMapAssoc()` to `match().toMap()`
        * Renamed `search().flatMapAssoc()` to `search().toMap()`
        * Renamed `stream().flatMapAssoc()` to `stream().toMap()`
* Features
    * Corrected `Pattern.mask()`, so it respects the order of keywords,
      and treats it as the priority of assigning masks.

      In other words, if there is ambiguity of which keyword should be assigned,
      when there is no unique solution, the order of keywords is used to determine
      the mask. Previously it was the length of the keyword, but that caused problems
      with ambiguous keywords of the same length.
    * Updated `replace().callback()`, so it doesn't unnecessarily causes global search,
      even when used with `only(int)` or `first()`.

Added in 0.36.0
---------------

* Breaking changes
    * Removed previously deprecated feature of tracking subject modification in replacement
        * Removed previously deprecated `ReplaceGroup.modifiedSubject()`
        * Removed previously deprecated `ReplaceGroup.modifiedOffset()`
        * Removed previously deprecated `ReplaceGroup.byteModifiedOffset()`
        * Removed previously deprecated `ReplaceGroup`
    * Removed `ReplaceDetail.limit()`
    * Removed `ReplaceDetail`

Added in 0.35.0
---------------

* Breaking changes
    * Renamed `MatchPattern` to `Matcher`
    * Revoke certain classes as parts of public library API:
        * `MatchedGroup` is no longer part of public API, use `Group` instead.
        * `NotMatchedGroup` is no longer part of public API, use `Group` instead.
        * `MatchDetail` is no longer part of public API, use `Detail` instead.
        * `Intable` is no longer part of public API, return `int` instead.
    * Simplified `\TRegx\CleanRegex\Match` namespace
        * Moved `Detail` to `\TRegx\CleanRegex\Match\Detail`
        * Moved `Group` to `\TRegx\CleanRegex\Match\Group`
        * Moved `Structure` to `\TRegx\CleanRegex\Match\Structure`
        * Moved `Element` to `\TRegx\CleanRegex\Match\Element`

Added in 0.34.2
---------------

* Deprecation
    * Deprecated keeping track of modified subjects
        * Deprecated `ReplaceDetail.modifiedSubject()`
        * Deprecated `ReplaceDetail.modifiedOffset()`
        * Deprecated `ReplaceDetail.byteModifiedOffset()`
        * Deprecated `ReplaceGroup.modifiedSubject()`
        * Deprecated `ReplaceGroup.modifiedOffset()`
        * Deprecated `ReplaceGroup.byteModifiedOffset()`
    * Deprecated replace by map and with group
        * Deprecated `replace().by().group()`
        * Deprecated `replace().by().group().orElseWith()`
        * Deprecated `replace().by().group().orElseThrow()`
        * Deprecated `replace().by().group().orElseCalling()`
        * Deprecated `replace().by().group().orElseIgnore()`
        * Deprecated `replace().by().group().orElseEmpty()`
        * Deprecated `replace().by().group().map()`
        * Deprecated `replace().by().group().mapIfExists()`
        * Deprecated `replace().by().map()`
        * Deprecated `replace().by().mapIfExists()`
    * Deprecated `ReplaceDetail`. It will be moved to `Internal/` namespace in the future release
      and won't be considered part of public T-Regx API anymore.
    * Deprecated `ReplaceGroup`. It will be moved to `Internal/` namespace in the future release
      and won't be considered part of public T-Regx API anymore.

Added in 0.34.1
---------------

* Features
    * Prepared patterns now support changing new line conventions with flag `x` (`EXTENDED` mode).

      This code is now valid:
      ```php
      Pattern::inject("(*CR)#comment@\r@", ['value'], 'x');
      ```
      The first placeholder character `"@"` is considered a part of an extended comment, but the second
      placeholder `@` is assigned value `'value'`.

    * All types of PCRE newline conventions are supported: `(*LF)`, `(*CR)`, `(*CRLF)`, `(*ANYCRLF)`, `(*ANY)`, `(*NUL)`.

Added in 0.34.0
---------------

* Breaking changes
    * Moved `PlaceholderFigureException` from `TRegx\CleanRegex\Internal\Prepared\Figure\ ` to `TRegx\CleanRegex\Exception\ `
* Features
    * Added support for quantifiers to placeholders in prepared patterns.
        * Added support for quantifiers in `Pattern::inject()`
        * Added support for quantifiers in `Pattern::template()`
        * Added support for quantifiers in `Pattern::builder()`
        * Added support for quantifiers in `PcrePattern::inject()`
        * Added support for quantifiers in `PcrePattern::template()`
        * Added support for quantifiers in `PcrePattern::builder()`

      These constructs are now valid:
      ```php
      Pattern::inject('Find:@?', ['Value']); // matches "Find:Value" and "Find:"
      ```
      ```php
      Pattern::inject('Find:@*', ['a']); // matches "Find:", "Find:a", "Find:aa"... 
      ```
      ```php
      Pattern::inject('Find:@+', ['a']); // matches "Find:a", "Find:aa"... 
      ```
      ```php
      Pattern::inject('Find:@{3,4}', ['a']); // matches "Find:aaa" and "Find:aaaa" 
      ```
      ```php
      Pattern::inject('Find:@{0}', ['a']); // matches only "Find:" 
      ```

    * Added support for empty placeholders in prepared patterns:
      ```php
      Pattern::inject('Find:@?', ['']); // matches only "Find:"
      ```

    * Updated backtracking in prepared patterns.
        * Updated backtracking in `Pattern.pattern()`.

          ```php
          Pattern::template('@:Bar')->pattern('Foo:Bar|Foo'); // matches "Foo:Bar" or "Foo:Bar:Bar"
          ```

        * Updated backtracking in `Pattern.mask()`.

          ```php
          $template = Pattern::template('@:Bar');
          $template->mask('*', ['*' => 'Foo:Bar|Foo']); // matches "Foo:Bar" or "Foo:Bar:Bar"
          ```

* Other
    * Passing invalid arguments which also don't match the number of arguments in `Pattern::inject()`,
      now always prefers `\InvalidArgumentException` over `PlaceholderFigureException`.

Added in 0.33.0
---------------

* Breaking changes
    * Removed previously deprecated `Pattern.match().group()`.
    * Removed `Pattern.match().asInt()`. Use `stream().asInt()` or `Detail.toInt()`.
    * `Pattern.match().first()` no longer accepts `callable` as its argument
    * `Pattern.match().findFirst()` no longer accepts `callable` as its argument
    * `Stream.first()` no longer accepts `callable` as its argument
    * `Stream.findFirst()` no longer accepts `callable` as its argument
    * Refactored `Pattern.match()` into `match()` and `search()`.

      `Pattern.match()` and `Pattern.search()` have virtually the same set of methods,
      with a slight difference. All of `Pattern.search()` operate on `string` which
      is the matched occurrence, and all of `Pattern.match()` methods now operate on
      `Detail`.
* Features
    * Added `Pattern.search()`, which is similar to `Pattern.match()`, but its methods
      only operate on `string`. From now on, `Pattern.match()` only operates on `Detail`.

Added in 0.32.0
---------------

* Breaking changes
    * Removed `Pattern.match().tuple()`.
    * Removed `Pattern.match().triple()`.

      These methods were added in earlier versions of T-Regx, when getting two
      groups was complicated. However now that the library matured enough, there
      is plenty of ways to get two groups. Additionally, `tuple()` implicitly
      calls first, but it's not obvious from the look of the method.
    * `Pattern.match().forEach()` callback no longer receives `$index` as its second argument
    * `Pattern.match().group().forEach()` callback no longer receives `$index` as its second argument
    * `Pattern.match().stream().forEach()` callback no longer receives `$key` as its second argument
* Features
    * Added `Stream.mapEntries()`, similar to `Stream.map()`, but accepts `callable` with two
      arguments `($key, $value)`, whereas `Stream.map()`'s `callable` accepts `($value)`.

Added in 0.31.0
---------------

* Breaking changes
    * Removed `Detail.usingDuplicateGroup()`. Using `J` modifier is still allowed, though of little use now.
    * Previously `Optional.get()` threw exception depending on where the optional came from. Now
      `Optional.get()` always throws `EmptyOptionalException`.
* Bug fixes
    * `Pattern.match().groupBy()` now correctly groups by duplicate name with `J` modifier.

Added in 0.30.0
---------------

* Breaking changes
    * Completely refactored `Detail.groups()` and `Detail.namedGroups()`
        * Removed `Detail.groups().count()`
        * Removed `Detail.groups().texts()`
        * Removed `Detail.groups().offsets()`
        * Removed `Detail.groups().byteOffsets()`
        * Removed `Detail.groups().names()`
        * Removed `Detail.namedGroups().count()`
        * Removed `Detail.namedGroups().texts()`
        * Removed `Detail.namedGroups().offsets()`
        * Removed `Detail.namedGroups().byteOffsets()`
        * Removed `Detail.namedGroups().names()`
* Features
    * Now `Detail.groups()` and `Detail.namedGroups()` return an `array` of `Group`.

        * `Detail.groups()` returns a sequential `array` of `Group` (sequential means indexed `0`, `1`, `2`, etc.)
        * `Detail.namedGroups()` returns an associative `array` of `Group`, where array keys are the group names.

Added in 0.29.0
---------------

* Breaking changes
    * Renamed `Detail.hasGroup()` to `Detail.groupExists()`.
    * Renamed `ReplaceDetail.hasGroup()` to `ReplaceDetail.groupExists()`.
    * Renamed `Pattern.match().hasGroup()` to `Pattern.match().groupExists()`.

Added in 0.28.1
---------------

* Bug fixes
    * Calling `Detail.group().asInt()` with invalid integer base on an unmatched group, threw `GroupNotMatchedException`
      . Now it throws `InvalidArgumentException`.
    * Calling `Detail.group().toInt()` with invalid integer base on an unmatched group, threw `GroupNotMatchedException`
      . Now it throws `InvalidArgumentException`.

Added in 0.28.0
---------------

* Breaking changes
    * Removed previously deprecated `NotMatched`. Every functionality of `NotMatched` was also added to
      `Pattern.match()` in previous release. This was done to unify the interface of each `Optional` implementation.
    * Removed previously deprecated `Group.map()`.
    * Removed `IntStream.asInt()` which returned itself.
* Bug fixes
    * Fixed a bug when using `usingDuplicateName().group().all()` in certain situations resulted in a list of
      indexed groups, not duplicately-named groups.
    * Corrected exception messages of `asInt().limit()`.
* Features
    * Updated usages of certain methods, like `Detail.groupNames()` and others, so that they don't call full matches
      when it's not necessary, making it usable even when "regular" usage would
      throw `CatastrophicBacktrackingException`.
* Deprecation
    * Deprecated inline-groups `Pattern.match().group()`.
* Other
    * Previously, just looking up `Detail.group()` caused full match, potentially ending in catastrophic backtracking.
      Now, `group()` doesn't do full match unless it's necessary. Only one call to PREG happens either way, meaning
      there isn't any performance loss.

Added in 0.27.0
---------------

* Breaking changes
    * Removed `Pattern.match().offsets()`. Use `Pattern.match().map()`.
    * Removed `Pattern.match().group().offsets()`. Use `Pattern.match().group().map()`.
    * When using `Pattern.split()`, previously unmatched separators were represented as an empty `string`. Now, they're
      represented as `null`.
    * Previously `Pattern.match().flatMap()` reindexed `int` keys, and preserved `string` keys, making it similarly
      ill-behaved as other PHP functions. From now on `flatMap()` treats any `array` as a sequential `array`, that is:
        * It's returned value will be a sequential array (as if `array_values()` was called on it)
        * Duplicate keys returned from `flatMap(callable)` will not make values no appear in the result. Only the
          sequence matters.

      Method `Pattern.match().flatMapAssoc()` is unchanged, in that case the elements are treated as a dictionary/map -
      neither `string` nor `int` keys are reindexed.
    * Method `Optional.orThrow()` can no longer be called without an argument. Instead, use `Optional.get()`.
* Bug fixes
    * Fixed a bug when calling `tuple($a,$b)` where `$a` was a non-existent group, and `$b` was a malformed
      group, T-Regx threw `NonexistentGroupException`, instead of `InvalidArgumentException`. Now it
      throws `InvalidArgumentException`.
    * Fixed a bug when calling `triple($a,$a,$b)` where `$a` was a non-existent group, and `$b` was a malformed
      group, T-Regx threw `NonexistentGroupException`, instead of `InvalidArgumentException`. Now it
      throws `InvalidArgumentException`.
    * Updated misleading message of `GroupNotMatchedException` thrown when using `groupByCallback()`.
* Features
    * Added `Pattern.splitStart()`, which works similarly to `Pattern.split()` but accepts a non-negative `maxSplits`
      argument, which can be used to limit splits from the start of the subject.
    * Added `Pattern.splitEnd()`, which works similarly to `Pattern.split()` but accepts a non-negative `maxSplits`
      argument, which can be used to limit splits from the end of the subject.
    * Added method `Optional.get()`, which returns the optional value or throws an exception (similarly to
      previous `Optional.orThrow(null)`).
    * Added methods to `Pattern.match()`:
        * Added `Pattern.match().subject()`, same as `Detail.subject()` and `NotMatched.subject()`.
        * Added `Pattern.match().hasGroup()`, same as `Detail.hasGroup()` and `NotMatched.hasGroup()`.
        * Added `Pattern.match().groupNames()`, same as `Detail.groupNames()` and `NotMatched.groupNames()`.
        * Added `Pattern.match().groupsCount()`, same as `Detail.groupsCount()` and `NotMatched.groupNames()`.
* Deprecation
    * Deprecated `NotMatched` passed as `findFirst().orElse()` argument.

Added in 0.26.0
---------------

* Breaking changes
    * `match().groupBy(string|int)` now returns `array` of `Detail`, previously returned `array` of `string`.
    * Removed `match().groupBy().all()`. Use `match.groupBy()`
    * Removed `match().groupBy().offsets()`. Use `stream().groupByCallback()`
    * Removed `match().groupBy().byteOffsets()`. Use `stream().groupByCallback()`
    * Removed `match().groupBy().map()`. Use `stream().groupByCallback().map()`
    * Removed `match().groupBy().flatMap()`. Use `stream().groupByCallback().flatMap()`
    * Removed `match().groupBy().flatMapAssoc()`. Use `stream().groupByCallback().flatMapAssoc()`
    * Previously `groupBy()` simply ignored unmatched groups. Now `GroupNotMatchedException` is thrown. To
      control unmatched elements use `Group.matched()`, `stream().filter()` or other elements of T-Regx API.

Added in 0.25.0
---------------

* Breaking changes
    * `Detail.group()` no longer implements `Optional`
    * `Detail.usingDuplicateName().group()` no longer implements `Optional`
    * Removed previously deprecated `Detail.group().orReturn()`. Use `or()` instead.
    * Removed previously deprecated `Detail.group().orElse()`
    * Removed previously deprecated `Detail.group().orThrow()`
    * Removed previously deprecated `ReplaceDetail.group().orReturn()`. Use `or()` instead.
    * Removed previously deprecated `ReplaceDetail.group().orElse()`
    * Removed previously deprecated `ReplaceDetail.group().orThrow()`
    * Updated how `Stream.distinct()` removes elements:
        * Now `'1'` and `true` are no longer considered equal
        * Now `''` and `false` are no longer considered equal
        * Now `0` and `false` are no longer considered equal
        * Now `1` and `true` are no longer considered equal
        * Now `0` and `'0'` are no longer considered equal
        * Now `null` and `false` are no longer considered equal

      For all intents and purposes, now `Stream.distinct()` works as-if it used strict-comparison `===`.
    * `Stream.filter()` no longer reindexes stream elements. To reindex them, chain the stream with `values()`.
      `match().filter()` still returns a sequential array with reindexed values.
    * Removed `Stream.only()`. Use `Stream.limit().all()` instead.
    * Removed `IntStream.only()`. Use `IntStream.limit().all()` instead.
    * Renamed `Group.textLength()` to `Group.length()`.
    * Renamed `Group.textByteLength()` to `Group.byteLength()`.
    * Renamed `Detail.textLength()` to `Detail.length()`.
    * Renamed `Detail.textByteLength()` to `Detail.byteLength()`.
* Bug fixes
    * Fixed a bug when using `Stream.values().keys().first()` didn't always reindex to `0`.
    * Fixed a bug when using regular groups in `stream()->asInt()` was allowed, but `usingDuplicateName()` groups
      weren't.
      Now both kinds of groups are correctly passed into `stream()->asInt()`.
    * Fixed a bug when using regular groups in `groupByCallback()` was allowed, but `usingDuplicateName()` groups
      weren't.
      Now both kinds of groups are correctly passed into `groupByCallback()`.
* Features
    * Added `Detail.group().or()` which behaves similarly to `orReturn()` but only accepts a non-nullable `string`.
    * Added `Stream.limit()`, which limits elements present in a stream from the end
    * Added `Stream.skip()`, which limits elements present in a stream from the start
    * Added `IntStream.limit()`, which limits elements present in a stream from the end
    * Added `IntStream.skip()`, which limits elements present in a stream from the end
* Other
    * Previously, using `Stream.keys().first()` return `0` for sequential arrays, and T-Regx didn't evaluate previous
      chains, such as `map()` or `flatMap()`. As of this release, they will be called for completeness,
      even though their results won't be used.

Added in 0.24.0
---------------

* Breaking changes
    * Removed previously deprecated `Detail.limit()`. `ReplaceDetail.limit()` remains unchanged.
    * Integer methods (`toInt()`, `isInt()`, `asInt()`) accept `$base` as their optional argument,
      which defaults to `10`.
        * Previously passing `null` as argument to integer methods was allowed, which also defaulted
          to `10`. Currently, the argument can either be of type `int` or omitted, but `null` is
          no longer allowed as `$base`.
    * Refactored `Pattern::pcre()->of()` to `PcrePattern::of()`
    * Refactored `Pattern::pcre()->inject()` to `PcrePattern::inject()`
    * Refactored `Pattern::pcre()->builder()` to `PcrePattern::builder()`
    * Refactored `Pattern::pcre()->template()` to `PcrePattern::template()`
    * Removed `Pattern::pcre()`
* Deprecation
    * Deprecated `Detail.group().orReturn()`
    * Deprecated `Detail.group().orElse()`
    * Deprecated `Detail.group().orThrow()`
    * Deprecated `Detail.group().map()`
    * Deprecated `ReplaceDetail.group().orReturn()`
    * Deprecated `ReplaceDetail.group().orElse()`
    * Deprecated `ReplaceDetail.group().orThrow()`
    * Deprecated `ReplaceDetail.group().map()`

Added in 0.23.1
---------------

* Bug fixes
    * Normally, pattern errors take precedence over subject errors. That is, `NonexistentGroupException`
      should be thrown before `CatastrophicBacktrackingException`.
        * Fixed a bug when replacing a pattern prone to catastrophic backtracking and unmatched group
          threw `CatastrophicBacktrackingException`. Now it throws `NonexistentGroupException`.

Added in 0.23.0
---------------

* Breaking changes
    * Previously deprecated `Optional.orThrow()` accepted exception class name as `string`. Currently, `orThrow()`
      accepts an instance of [`\Throwable`].
    * Removed `ClassExpectedException`, which was thrown when an invalid class name was passed to `orThrow()`.
    * Removed `NoSuitableConstructorException`, which was thrown when an invalid class was passed to `orThrow()`.
    * Previously T-Regx used whatever encoding was set for [`mb_internal_encoding()`] for `Detail.offset()`/`tail()`
      /`textLength()`. Now, T-Regx always uses UTF-8 regardless of [`mb_internal_encoding()`]. For byte manipulation in
      encoding other than UTF-8 use `byteOffset()`/`byteTail()`/`byteTextLength()`.
    * Removed previously deprecated `Detail.setUserData()`/`getUserData()`
* Bug fixes
    * Fixed a bug when using `match()->groupByCallback()` didn't throw `InvalidReturnValueException` for invalid group
      value
* Deprecation
    * Deprecated `Detail.limit()`

[`\Throwable`]: https://www.php.net/manual/en/class.throwable.php

[`mb_internal_encoding()`]: https://www.php.net/manual/en/function.mb-internal-encoding.php

Added in 0.22.0
---------------

* Breaking changes
    * Changed the namespace of `LazyDetail` to `TRegx\CleanRegex\Replace\By\LazyDetail`.
* Bug fixes
    * Fixed a bug when `groups()->texts()` with `LazyDetail` returned `''` for an unmatched group on PHP7.1.

      `LazyDetail` is the implementation of `Detail` used with `replace()->orElseCalling()`.
    * Fixed a bug when using `replace()->by()->group()` with a nonexistent group, didn't
      throw `NonexistentGroupException` for unmatched subject.
* Deprecation
    * User-data in `Detail` will be removed in future release
        * Deprecated `Detail.setUserData()`
        * Deprecated `Detail.getUserData()`
* Other
    * Updated `replace()->by()->group()`, so that certain scenarios which would normally
      throw `CatastrophicBacktrackingException`, are handled in such a way that no catastrophic backtracking happens.

Added in 0.21.0
---------------

* Breaking changes
    * Renamed `GroupLimit` to `GroupMatch`.
    * Previously `nth()`/`findNth()` threw different exceptions when the subject wasn't matched and when the item was
      missing. Now they always throw `NoSuchNthElementException` (regardless of whether the subject was matched or not).
      Exception messages still remain, to inform you whether there was not enough occurrences or whether the subject
      wasn't matched.
        * `match()->nth()` throws `NoSuchNthElementException`, instead of `SubjectNotMatchedException`
        * `match()->asInt()->nth()` throws `NoSuchNthElementException`, instead of `NoSuchStreamElementException`
        * `match()->stream()->nth()` throws `NoSuchNthElementException`, instead of `NoSuchStreamElementException`
        * `match()->group()->nth()` throws `NoSuchNthElementException`, instead of `SubjectNotMatchedException`
        * `match()->group()->asInt()->nth()` throws `NoSuchNthElementException`, instead
          of `NoSuchStreamElementException`
        * `match()->group()->stream()->nth()` throws `NoSuchNthElementException`, instead
          of `NoSuchStreamElementException`
* Bug fixes
    * `match()->offsets()` and `match()->group()->offsets()` now return offsets as characters. Previously they returned
      them as bytes.
* Features
    * Added `SubjectNotMatchedException.getSubject()`

Added in 0.20.3
---------------

* Performance
    * Improved performance of certain validations, by not parsing PHP errors, when it's not necessary.
* Features
    * Added `pattern()->match()->asInt()->reduce()`

Added in 0.20.2
---------------

* Compatibility fixed
    * Removed usage of `str_contains()` and `str_starts_with()` from previous version, to maintain support for PHP 7.1.
* Bug fixes
    * Fixed a bug when using `match()->groupBy()` for a nonexistent group didn't throw `NonexistentGroupException`.
* Features
    * Added `Pattern.cut()` which splits string into exactly two parts.
* Performance
    * Refactored internal PCRE parser, to minimise number of instances created.
    * Improved performance of `CompositePattern.chainedReplace()` by using single call to PCRE.
* Deprecation
    * Deprecated `Detail.substitute()`.
    * Deprecated `pattern()->replace()->focus()`.

Added in 0.20.1
---------------

* Bug fixes
    * Corrected a bug in prepared patterns, where closing group after in-pattern (`(?i)`) modifier didn't update the
      flags properly, resulting in improper parsing of placeholders in `Pattern::inject()`/`Pattern::template()`.
    * Corrected a bug when using modifier reset `(?^)` didn't properly unset extended pattern, leading to improper
      placeholder `@` parsing inside comments.
* Other
    * Previously T-Regx removed duplicate flags before passing them to PCRE. Now flags are passed without modification.

Footnote (15.03.2022):

* Accidental compatibility breaks:
    * We mistakenly used `str_contains()` and `str_starts_with()`. Our tests didn't find it, because we also used
      `php-coveralls/php-coveralls` which introduced polyfill for PHP8 methods. It's fixed in 0.20.2.

Added in 0.20.0
---------------

* Breaking changes
    * Removed `pattern()->match()->remaining()`, as it was a failed idea.
* Bug fixes
    * Fixed a bug when `stream()->map()->asInt()` ignored integer-base, assumed base 10.
* Features
    * Added `pattern()->match()->reduce()`

Added in 0.19.2
---------------

* Features
    * Added `$flags` parameter to `preg::replace_callback()` which was added in PHP 7.4.

Added in 0.19.1
---------------

* Bug fixes
    * Fixed a bug when pattern `[]]` with prepared pattern was parsed incorrectly.
    * Fixed a bug when using character classes (e.g. `[:alpha:]`) with prepared pattern were parsed incorrectly.

Added in 0.19.0
---------------

* Breaking changes
    * Rename `BaseDetail` to `Structure`
* Features
    * Add second argument `Structure` as second parameter to `replace()->counting()`

Added in 0.18.1
---------------

* Features
    * `replace()->by()->map()` allows for replacing `int` and numeric strings (`"12"`)

Added in 0.18.0
---------------

* Breaking changes
    * Removed `replace()->otherwise()`. Use `counting()` instead.
    * Removed `replace()->otherwiseReturning()`. Use `counting()` instead.
    * Removed `replace()->otherwiseThrowing()`. Use `counting()` or `atLeast()` instead.
* Other
    * T-Regx follows [Semantic Versioning 2](https://semver.org/)

Added in 0.17.0
---------------

* Bug fixes
    * Fixed a bug when returning non-string value from `orElseCalling()` didn't throw `InvalidReturnValueException`
* Breaking changes
    * Renamed `FluentMatchPattern` to `Stream`, similar to Java 8 streams
    * Renamed `fluent()` to `stream()`, similar to Java 8 streams
    * Renamed `NoSuchElementFluentException` to `NoSuchStreamElementException`
* Features
    * Add `IntStream.stream()`

Added in 0.16.0
---------------

* Breaking changes
    * Removed `FluentMatchPatternException`. In case of `asInt()`, `InvalidIntegerTypeException` is thrown instead.
    * Methods `asInt()` and `offsets()` return `IntStream` instead of `FluentMatchPattern`.
    * Updated the rules when exceptions are thrown from `asInt()`, `offsets()` and `fluent()`:
        * Exceptions thrown from `IntStream`:
            * `pattern()->match()->asInt()` throws `SubjectNotMatchedException`
            * `pattern()->match()->offsets()` throws `SubjectNotMatchedException`
            * `pattern()->match()->group()->asInt()` throws `SubjectNotMatchedException` or `GroupNotMatchedException`
            * `pattern()->match()->group()->offsets()` throws `SubjectNotMatchedException` or `GroupNotMatchedException`
        * Exception thrown from `FluentMatchPattern`:
            * `pattern()->match()->fluent()` throws `NoSuchElementFluentException`
            * `pattern()->match()->asInt()->fluent()` throws `NoSuchElementFluentException`
            * `pattern()->match()->offsets()->fluent()` throws `NoSuchElementFluentException`

      Basically, `MatchPattern` and `IntStream` throw match-related exceptions (`SubjectNotMatchedException`
      or `GroupNotMatchedException`), whereas `FluentMatchPattern` throws fluent-related
      exception: `NoSuchElementFluentException`.
    * Updated exception messages from `asInt()`, `offsets()` and `fluent()`.
    * `MatchPatternInterface` is no longer part of T-Regx public API.

Added in 0.15.0
---------------

* Breaking changes
    * Renamed `Pattern::template()` to `Pattern::builder()`
* Features
    * Added `Pattern::template()`, which works similarly to `Pattern::builder()` but allows only one chain

Added in 0.14.1
---------------

* Bug fixes
    * Fixed a bug when calling `filter()->first()` called predicate for more than the first item.
* Deprecation
    * Deprecated `Optional.orThrow()`. Currently `orThrow()` accepts the exception class name. In the future it will
      accept a real `\Throwable` instance. To preserve current behaviour of `orThrow()`, use `orElse()`.

Added in 0.14.0
---------------

* Breaking changes
    * Renamed `Pattern::compose()->allMatch()` to `testAll()`
    * Renamed `Pattern::compose()->anyMatches()` to `testAny()`
    * Renamed `Pattern::compose()->chainedRemove()` to `prune()`
* Features
    * Added `Pattern::compose()->failAny()`, returning `true` if any of the patterns didn't match the subject
    * Added `Pattern::compose()->failAll()`, returning `true` if all the patterns didn't match the subject

Added in 0.13.8
---------------

* Bug fixes
    * Fixed a bug, where using `Pattern::inject('()(?)')` failed parsing
    * Fixed a bug, where using unicode in groups failed parsing
    * Fixed a bug, where using pattern in unclosed comment group failed parsing
    * Added workaround for PHP inconsistencies regarding backslash in patterns:
        * PHP reports `\c\` as invalid entity, all T-Regx entry points correctly recognize it as valid
        * PHP reports `\Q\` as invalid entity, all T-Regx entry points correctly recognize it as valid
        * PHP reports `(?#\` as invalid entity, all T-Regx entry points correctly recognize it as valid
        * PHP reports `#\` as invalid entity in `X`tended mode, all T-Regx entry points correctly recognize it as valid
* Features
    * Added `Optional.map()`, which resembles Java 8 optionals.
    * `pattern()->match()->asInt()->findFirst()->orElse()` receive `NotMatched` argument
    * `pattern()->match()->asInt()->findNth()->orElse()` receive `NotMatched` argument
    * `pattern()->match()->offsets()->findFirst()->orElse()` receive `NotMatched` argument
    * `pattern()->match()->offsets()->findNth()->orElse()` receive `NotMatched` argument

Added in 0.13.7
---------------

* Breaking changes
    * `Pattern::inject()` no longer supports alteration. Use `Pattern::template()->alteration()`.
* Features
    * Added `pattern()->match()->forEach()` consumer accepts index as a second argument

Added in 0.13.6
---------------

* Bug fixes
    * Fixed a bug, where using `match()->filter()` didn't throw `InvalidReturnValueException`.
    * Fixed a bug, where using `group()->filter()` didn't throw `InvalidReturnValueException`.
    * Fixed a bug, where `Pattern::template()->mask()` keywords weren't taken into account, when choosing a delimiter
* Features
    * Added `Pattern::template()->pattern()`
* Others
    * Updated `ExplicitDelimiterRequiredException` message for `Pattern::of()`
    * Updated `ExplicitDelimiterRequiredException` message for `Pattern::mask()`
    * Updated `ExplicitDelimiterRequiredException` message for `Pattern::template()`

Added in 0.13.5
---------------

* Breaking changes
    * Refactored `Pattern::pcre()` to `Pattern::pcre()->of()`
    * Refactored `Pattern::builder()->pcre()->inject()` to `Pattern::pcre()->inject()`
    * Refactored `Pattern::builder()->pcre()->template()` to `Pattern::pcre()->template()`
    * Removed `Pattern::builder()`.
    * Moved `ReplaceDetail` to `TRegx\CleanRegex\Replace\Detail` namespace
    * Moved `ReplaceGroup` to `TRegx\CleanRegex\Replace\Detail\Group` namespace
* Features
    * Added `Pattern::alteration()` which allows building `Pattern` with just an alteration group.
        * For example `Pattern::alteration(['foo', 'bar'])` is `/(?:foo|bar)/`
    * Added `Pattern::template()->alteration()`
* Bug fixes
    * Fixed a bug, where passing `false` as an alteration value didn't throw `\InvalidArgumentException`.

Added in 0.13.4
---------------

* Features
    * Every method `toInt()`/`isInt()` receives a `$base` optional argument, which defaults to `10`:
        * `Detail.toInt()`, `Detail.isInt()`,
        * `Group.toInt()`, `Group.isInt()`,
        * `ReplaceDetail.toInt()`, `ReplaceDetail.isInt()`,
        * `pattern()->match()->asInt()`
        * `pattern()->match()->group()->asInt()`
* Other
    * We added continuous integration runs for PHP on 32-bit architecture, to test 32-bit integers with `toInt()`.

Added in 0.13.3
---------------

* Bug fixes
    * Fixed w bug where using `Detail.usingDuplicateName()` didn't throw `NonexistentGroupException`.

Added in 0.13.2
---------------

* Bug fixes
    * Fixed a bug when using `match()->asInt()->keys()->first()` for malformed integers would not
      throw `NumberFormatException`.
    * Fixed a bug when using `fluent()->keys()->keys()` (double `keys()`) would silence T-Regx exceptions.
* Breaking changes
    * Previously `remaining()` and `filter()` would leave a resulting array with keys that aren't exactly sequential,
      giving the impression the iterated collection is not a list. Now it's fixed, so the resulting array is indexed.

Added in 0.13.1
---------------

* Bug fixes
    * Fixed a bug when using `match()->fluent()->first()` exposed a false-negative in `hasGroup()` from PHP.
    * Fixed a bug when using `match()->group()->fluent()->first()` exposed a false-negative in `hasGroup()` from PHP.
* Other
    * Internal implementation revamp

Added in 0.13.0
---------------

* Breaking changes
    * `pattern()->forArray()` is now the same as previous `pattern()->forArray()->strict()`.
    * Removed `pattern()->forArray()->strict()`.
    * Removed `Pattern::quote()`. Use `preg::quote()`, which behaves in exactly the same way.
    * Move `Pattern::unquote()` to `preg::unquote()`, which behaves in exactly the same way.
    * Removed `pattern()->remove()->all()`. Use `pattern()->prune()` instead.
    * Removed `pattern()->match()->asArray()`. Use `Detail.groups()` or `Detail.namedGroups()` instead.
* Other
    * Using TDD in T-Regx, it wasn't hard to reach 100% coverage quite easily in T-Regx. In order to make the tests even
      better, we decided that the integration tests won't report any coverage, since it doesn't provide any more
      information now (it's always 100%). That doesn't mean all the cases are tested tough, so we decided to disable the
      coverage reports on the Integration tests. Now, the coverage badge will drop, but that doesn't mean we remove any
      tests. We just mark them as "non-reportable", so that we can use the coverage to actually find more untested
      cases.

Added in 0.12.0
---------------

* Features
    * We added internal regular expression parser, that's used when creating Prepared patterns. Now in-pattern
      structures can be properly recognized, eliminating cases of misuse. Most notably `[@]`, `\Q@\E`, `\@`, `\c@` and
      others, like comment groups and comments in extended mode.
* Breaking changes
    * Prepared patterns now use internal regular expression parser, to determine what is a placeholder and what isn't:
        * Previously, `[@]` would be injected. Now it's treated as `"@"` character-class.
        * Previously, `\Q@\E` would be injected. Now it's treated as `@` literal.
        * Previously, `\c@` would be injected. Now it's `\c@` control character.
        * Previously, `#@\n` would be injected. Now, if `x` flag is used (globally, or as a subpattern), then it's
          treated as `@` comment.
        * Previously, `(?#@)` would be injected. Now it's treated as `@` comment.
        * Previously, `\@` would be treated as `@` literal. This remains unchanged.
    * Mask placeholders are no longer represented as `&` in templates, use `@`.
    * Refactored `Pattern::template()->builder()`. Use `Pattern::template()` now.
    * Removed `Pattern::bind()`. Use `Pattern::inject()` or `Pattern::template()->literal()`.
    * Removed `Pattern::prepare()`. Use `Pattern::inject()`.
    * Removed `Pattern::pcre()->bind()`.
    * Removed `Pattern::pcre()->prepare()`.
    * Removed `Pattern::template()->bind()`.
* Bug fixes
    * Correct type-error in `ValidPattern.isValid()` on PHP 8.1.

Added in 0.11.0
---------------

* Features
    * Added `Detail.usingDuplicateName().get()` #101
    * Added `Detail.usingDuplicateName().matched()` #101
    * Method `Pattern:template()->literal(string)` now accepts `string` argument, allowing for inserting arbitrary
      strings into the pattern.
    * Added `Pattern::builder()`, which works similarly to how `PatternBuilder::builder()` worked.
    * Added `Pattern::literal()` which creates an instance of a pattern with which matches an arbitrary string exactly,
      even when `x` (`EXTENDED`) flag is used. To add in-pattern structures, like `^` or `$`,
      use `Pattern::template()->literal()`.
    * Added `Pattern::template()->literal()`, which is a shorthand for
      `Pattern::template()->builder()->literal()->build()`.
    * Added `Pattern::template()->mask()`, which is a shorthand for `Pattern::template()->builder()->mask()->build()`.
    * Casting `PatternInterface` to `string` results in a delimited pattern
    * Add `Pcre` version helper
* Breaking changes
    * `match()->getIterator()` no longer preserves the keys of values (like `all()`)
    * `match()->group()->getIterator()` no longer preserves the keys of values (like `all()`)
    * Renamed `Pattern::format()` to `Pattern::mask()`
    * Renamed `Pattern::builder()->format()` to `Pattern::builder()->mask()`
    * Renamed `Pattern::template()->format()` to `Pattern::template()->mask()`
    * Refactored `Pattern::template()->formatting()` to `Pattern::template()->builder()->mask()`
    * Method `literal()` now requires argument `'&'`, to escape `&` in-pattern token
    * Removed `PatternBuilder::builder()`. Use `Pattern::builder()`
    * Removed `PatternBuilder::compose()`. Use `Pattern::compose()`
    * Renamed `FormatMalformedPatternException` to `MaskMalformedPatternException`
    * Removed interface `PatternInterface`. Now class `Pattern` is both an instance of a pattern, as well as a
      static-factory, i.e. `Pattern::of()`/`Pattern::inject()`.
* Bug fixes
    * `Pattern::template()` quoted values incorrectly, when delimiter other than `/` or `%` was chosen.

Added in 0.10.2
---------------

* Breaking changes
    * Rename `DetailGroup` to `Group`
    * Rename `ReplaceDetailGroup` to `ReplaceGroup`
    * Rename `BaseDetailGroup` to `CapturingGroup`
* Features
    * Calling `pattern()->replace()` without `all()`/`first()`/`only()`, implicitly assumes `all()`
* Bug fixes
    * Group name `"group\n"` used to be considered valid, now it's correctly being treated as invalid.
* Other
    * [`ReplaceMatch`] is now a class, not an interface.
    * When invalid strings, error messages will now also print invisible characters, for example `"Foo\n"`, instead of
      ```
      "Foo      
      "
      ```
    * Update messages and exceptions thrown in edge-cases from `group()->fluent()` #93

Added in 0.10.1
---------------

* Breaking changes
    * Chainable `pattern()->match()->filter()` is renamed to `remaining()`.

      `pattern()->match()->fluent()->filter()` is not being renamed.
    * After filtering `MatchPattern` with `remaining()`, consecutive `Detail.index()` will no longer be reindexed, they
      will preserve the `index()` they had had before `remaining()`.
    * `match()->fluent()->filter()` no longer reindexes values. To reindex, use `values()`.
* Bug fixes
    * Fixed a bug where `fluent()->flatMap()->first()` would return the `array`, instead of the first element
* Features
    * Add `pattern()->match()->filter()` which returns only matches allowed by the predicate.
    * Add `pattern()->match()->group()->asInt()`
* Other
    * `pattern()->match()->fluent()->filter()->first()` first calls [`preg_match()`], and if that result doesn't match
      the predicate, then it calls [`preg_match_all()`].

Added in 0.10.0
---------------

* Breaking changes
    * Previously deprecated [`Match`] and [`ReplaceMatch`] are now being removed, because of PHP8 keyword [`match`][8].

      Use [`Detail`] and [`ReplaceDetail`] instead.
* Other
    * T-Regx version 0.10 supports PHP 8.

Added in 0.9.14
---------------

* Breaking changes
    * Rename `DetailGroup.replace()` to `DetailGroup.substitute()`
    * Rename `match().groupBy().texts()` to `match().groupBy().all()`
    * [`ReplaceDetail.modifiedOffset()`][2] returned values as bytes, now returns them as characters
    * [`ReplaceDetailGroup.modifiedOffset()`][2] returned values as bytes, now returns them as characters
    * Move `MalformedPatternException` to namespace `\TRegx\Exception`
    * Move `RegexException` to namespace `\TRegx\Exception`
    * `MalformedPatternException` was a class extending `CompilePregException`. Now, `MalformedPatternException`
      extends only `RegexException`. New class, `PregMalformedPatternException` is being thrown everywhere
      `MalformedPatternException` used to be thrown. Don't refactor your `catch (MalformedPatternException $e)`, since
      that's still the recommended handling.
      (but say [`get_class()`] would return `PregMalformedPatternException`). Complete exception structure is described
      in "Exceptions".
    * Rename exceptions
        * Rename `Utf8OffsetPregException` to `UnicodeOffsetException`
        * Rename `SubjectEncodingPregException` to `SubjectEncodingException`
        * Rename `CatastrophicBacktrackingPregException` to `CatastrophicBacktrackingException`
        * Rename `RecursionLimitPregException` to `RecursionException`
        * Rename `JitStackLimitPregException` to `JitStackLimitException`
* Bug fixes
    * Fix a security bug in [`Pattern::bind()`]
    * Using pattern with a trailing backslash (e.g. `"(hello)\\"`) would throw
      `MalformedPatternException` with a really weird message, exposing the implementation details. Now the message
      is `Pattern may not end with a trailing backslash`.
    * Adapt `focus()->withReferences()` so it works exactly as [`preg_replace()`].

      Previously, using a nonexistent or unmatched group with `focus()->withReferences()`
      would throw an exception. But of course, [`preg_replace()`] references `$1` and `\1`
      simply are ignored by PCRE, being replaced by an empty string. So, as of this version both [`withReferences()`]
      and `focus()->withReferences()` ignore the unmatched or nonexistent group as well.
    * Fix an error where optionals didn't work properly for `match()->offsets()->fluent()`
    * Fix an error where `ReplaceDetail` would return malformed `modifiedSubject()` for utf-8 replacements

* Features
    * Add [`ReplaceDetail.byteModifiedOffset()`][2] which returns values as bytes
    * Add [`ReplaceDetailGroup.byteModifiedOffset()`][2] which returns values as bytes
    * Add [`ReplaceDetailGroup.modifiedSubject()`][2]
    * Add pattern formats and pattern templates, a new way of creating pseudo-patterns for user supplied data:
        * Add `Pattern::format()` #79
        * Add `Pattern::template()` #79
    * Add `Detail.textByteLength()` #88
    * Add `DetailGroup.textByteLength()` #88
    * Add `match()->flatMapAssoc()` #88
    * Add `match()->group()->flatMapAssoc()` #88
    * Add `match()->fluent()->flatMapAssoc()` #88
    * Add `match()->groupBy()->flatMapAssoc()` #88

      Otherwise identical to [`flatMap()`], but since `flatMapAssoc()` doesn't use
      [`array_merge()`], the `int` keys won't be reindexed - returning an integer key from a
      `flatMapAssoc()`. If a given key was already returned previously, the later value will be preserved. It's useful
      for associative arrays with `int` keys. For sequential arrays (or arrays with `string` keys), feel free to
      use [`flatMap()`].

    * Add `match()->groupByCallback()` (previously only `match()->fluent()->groupByCallback()` and
      `match()->groupBy()`) #80
    * Add `match()->nth()` (previously only `match()->fluent()->nth()`) #80
    * Add `replace()->counting()`, invoking a callback with the number of replacements performed #90
    * Add `replace()->exactly()`, validating that exactly one/only replacements were performed #90
    * Add `replace()->atLeast()`, validating that at least one/only replacements were performed #90
    * Add `replace()->atMost()`, validating that at most one/only replacements were performed #90
    * Add `pattern()->prune()` which removes every occurrence of a pattern from subject (identical to `remove()->all()`)
* Other:
    * Replace any usage of `\d` to `[0-9]` in the library, since it depends on PHP locale.
    * Added interface `PatternStructureException` which can be used to catch exceptions for errors solely in pattern
      structure (recursion, backtracking, jit limit).

Added in 0.9.13
---------------

* Breaking changes
    * None

* Deprecation
    * Deprecate [`Match`], use [`Detail`] instead.
    * Deprecate [`ReplaceMatch`], use [`ReplaceDetail`] instead.
    * Deprecate [`MatchGroup`], use [`DetailGroup`] instead.
    * Deprecate `ReplaceMatchGroup`, use `ReplaceDetailGroup` instead.

      In preparation for PHP 8, in which [`match`][8] is a new keyword, we deprecate [`Match`] and [`ReplaceMatch`]
      . [`Match`] will become an invalid class name in PHP 8.

      Classes [`Match`], [`ReplaceMatch`], [`MatchGroup`] and `ReplaceMatchGroup` will remain in T-Regx (as deprecated)
      as long as T-Regx doesn't support PHP 8.

* Features
    * Add `NotReplacedException.getSubject()`
    * Add `DetailGroup.subject()`
    * Add `ReplaceDetailGroup.subject()`
    * Add `pattern()->replace()->focus(group)` #82

      It allows the replacement mechanism to **focus** on a single group, so only the focused capturing group will
      change; the rest of the whole match will be left as it was.

    * Added proper handling of `/J` flag #84

      Previously, duplicate patterns added a form of unpredictability - the structure of the group (order, index, name)
      depended on the group appearance in the pattern, which is fine. However, its value (text, offset)
      depended on which group was matched (that's what we call strategy 2). That's the consequence of php storing only
      one named group in the result, since PHP arrays can't hold duplicate keys.

      That's another gotcha trap set by PHP, and we need a reasonable mechanism in T-Regx to handle it.

      Since now, every method (inline groups, group in [`Match`], etc.) predictably depends on the order of the group in
      the pattern (that's what we call strategy 1), even the value (text, offset), which previously were kind of random.

    * Added [`Match.usingDuplicateName()`] method, which allows the user to use the less predictable behaviour (which
      was the default, previously).

      For safety, groups returned from [`usingDuplicateName()`] don't have `index()` method, since it allows strategy 2,
      and strategy 2 indexes of groups are sometimes unpredictable. Group returned there extends a different interface,
      not [`DetailGroup`] as usual, but [`DuplicateNamedGroup`] - that's an otherwise identical interface, except it
      doesn't have `index()` method. Of course, regular `group(int|string)`
      groups still have `index()` method, since they use strategy 1 now.

        * `Match.group('group')` previously would return strategy 2, now returns strategy 1.
        * [`Match.usingDuplicateName().group('group')`] returns group by strategy 2 (previously default)

      There is currently no way to use strategy 2 for inline groups or aggregate group methods, only for [`Match`]
      /[`Detail`] details.

* Other
    * Updated some exceptions' messages format; most notably, indexed groups as formatted as `#2`, and named groups
      as `'group'`.

* SafeRegex
    * After calling [`preg_match()`] with overflowing offset, [`preg_last_error()`] would return
      [`PREG_INTERNAL_ERROR`], which T-Regx would handle, throwing `RuntimePregException` with proper message. Negative
      offsets would be ignored.

      Since now, T-Regx throws [`\InvalidArgumentException`] in both cases.

Added in 0.9.12
---------------

* Bug fixes
    * Fixed an occasional [`TypeError`] (Bug introduced in 0.9.11, fixed in 0.9.12)

      Calling `group()->orThrow()` on a non-matched group without argument would cause [`TypeError`].

Added in 0.9.11
---------------

* Breaking changes
    * Added `null`-safety to [`pattern()->replace()`]:
        * Returning `null` from [`replace()->callback()`] throws `InvalidReturnValueException`.
        * Returning `null` from `replace()->otherwise()` throws `InvalidReturnValueException`.
        * Returning `null` from `replace()->by()->group()->orElse()` throws `InvalidReturnValueException`.
    * Renamed `pattern()->replace()->by()->group()` methods:
        * Renamed [`orThrow(string)`] to [`orElseThrow(string)`].
        * Renamed [`orIgnore()`] to [`orElseIgnore()`].
        * Renamed [`orEmpty()`] to [`orElseEmpty()`].
        * Renamed [`orReturn(string)`] to [`orElseWith(string)`].
        * Renamed [`orElse(callable)`] to [`orElseCalling(callable)`].
    * Renamed and added `pattern()->replace()->by()->group()->map()` methods:
        * Renamed [`orReturn(string)`][1] to [`orElseWith(string)`][1].
        * Renamed [`orElse(callable)`][1] to [`orElseCalling(callable)`][1].
        * Renamed [`orThrow(string)`][1] to [`orElseThrow(string)`][1].
        * Added [`orElseIgnore()`][1].
        * Added [`orElseEmpty()`][1].

* Features
    * Prepared patterns:
        * Restored [`Pattern::prepare()`], but without alteration. #78
        * Restored [`PatternBuilder::prepare()`], but without alteration. #78
    * Match tail (as `offset()`, but from the end-side):  #83
        * Add `Match.tail()`.
        * Add `Match.byteTail()`.
        * Add `MatchGroup.tail()`.
        * Add `MatchGroup.byteTail()`.
        * Add `ReplaceMatchGroup.tail()`.
        * Add `ReplaceMatchGroup.byteTail()`.
    * Added method `getPregPattern()` to exceptions: #85
        * `PregException`
            * `CompilePregException`
                * `MalformedPatternException`
            * `RuntimePregException`
                * `SubjectEncodingPregException`
                * `Utf8OffsetPregException`
                * `CatastrophicBacktrackingPregException`
                * `RecursionLimitPregException`
                * `JitStackLimitPregException`
            * `InvalidReturnValueException`
* Fixed inconsistencies
    * Duplicated pattern exception message changes offset after PHP 7.3. Since now, the messages will be identical on
      every PHP version.

Added in 0.9.10
---------------

* Breaking changes
    * Renamed `BacktrackLimitPregException` to `CatastrophicBacktrackingPregException`.
    * Removed [`Pattern::prepare()`].
    * Removed [`PatternBuilder::prepare()`].
    * Renamed `throwingOtherwise()` to `otherwiseThrowing()`.
    * Renamed `returningOtherwise()` to `otherwiseReturning()`.
* Features
    * Add `pattern()->match()->tuple()` method. #76
    * Add `pattern()->match()->triple()` method. #76

Added in 0.9.9
--------------

* Breaking changes
    * Renamed [`pattern()->delimiter()`] to [`pattern()->delimited()`]
* Features
    * Add `MatchGroup.equals()`, that allows to compare a potentially unmatched group with a string.
    * Add `pattern()->match()->group()->filter()` method. #22
    * Add `pattern()->replace()->by()->mapAndCallback()`, which first translates a match by a dictionary
      (like [`by()->map()`]), and then passes it through callback, before replacing (like [`callback()`]).
* Enhancements
    * [Prepared patterns] correctly handle whitespace with [`PCRE_EXTENDED`] mode. #40
* SafeRegex
    * `preg::quote()` throws [`InvalidArgumentException`] when it's called with a delimiter that's not a single
      character.
    * Handled PHP Bug [#77827](https://bugs.php.net/bug.php?id=77827), when `\r` was passed at then end of a pattern
      to [`preg_match()`]/[`preg_match_all()`].
* Bug fixes
    * Fixed a bug in [Prepared patterns] (PCRE mode), when using a malformed pattern caused [`TypeError`], instead
      of `MalformedPatternException`.

Added in 0.9.8
--------------

* Features
    * You can now use [`foreach`](https://www.php.net/manual/en/control-structures.foreach.php) on [`match()`], instead
      of [`forEach()`]:
      ```php
      foreach (Pattern::of('\d+')->match('127.0.0.1') as $match) {}
      ```
      and also
      ```php
      foreach (Pattern::of('\d+')->match('127.0.0.1')->asInt() as $digit) {}
      ```
      or
      ```php
      foreach (Pattern::of('\d+')->match('127.0.0.1')->all() as $text) {}
      ```
    * Added [`Match.get(string|int)`], which is a shorthand for `Match.group(string|int).text()`.
    * Restored `pattern()->match()->test()`/[`fails()`] that were removed in version 0.9.2.

Added in 0.9.7
--------------

* Breaking changes
    * `pattern()->replace()->orElse/Throw/Return->with()` are renamed to
      `otherwise()`/`throwingOtherwise()`/`returningOtherwise()`.
* Features
    * Added `pattern()->match()->asArray()->*` which returns results as an array (as if it was returned
      by [`preg_match()`], but fixed). More below.
* Bug fixes
    * Fixed a bug when [`findFirst()`] sometimes called [`preg_match_all()`], despite previous change.

---

When using [`preg_match()`] or [`preg_match_all()`] with [`PREG_SET_ORDER`], the last groups that are unmatched or
matched an empty string are removed by PHP! Missing group, unmatched group and group that matched `""` are
indistinguishable. Basically, PHP trims any `false`-y group.

T-Regx fixes it by filling the results:

- `null` always means a group is present, but unmatched
- `""` means a matched group, that matched an empty string

Added in 0.9.6
--------------

* Breaking changes
    * `pattern()->match()->fluent()->distinct()` will no longer re-index elements (will not remove keys).
        - To re-index keys, use `distinct()->values()`.
        - `pattern()->match()->distinct()` still re-indexes keys.
    * Rename `NoFirstElementFluentException` to `NoSuchElementFluentException`
* Enhancements 🔥
    * Every `match()->...()->first()` method calls [`preg_match()`], instead of [`preg_match_all()`]. More below.
* Features
    * Added `pattern()->match()->fluent()->nth(int)` used to get an element based on an ordinal number.
    * Added `pattern()->match()->asInt()`. More below.

---

#### About `preg_match()` vs `preg_match_all()`:

Previously [`preg_match()`] was called only by:

- [`match()->first()`]
- [`match()->findFirst()`]

Any other [`match()`] method (e.g. [`map()`], [`forEach()`], etc.) used [`preg_match_all()`]. From now on, where
possible, [`preg_match()`] is also used for:

- `fluent()->first()`
- `asInt()->first()` / `asInt()->fluent()->first()`
- `group()->first()`
- `offsets()->first()`
- `group()->offsets()->first()`
- Any method after `fluent()`, for example `fluent()->map()->first()`

The same applies to the methods above ending with [`findFirst()`].

The change was made because of two reasons:

- Performance (matching only the first occurrence is faster than all of them)
- There are cases where the 2nd (or 3rd, n-th) occurrence would have thrown an error (e.g. catastrophic backtracking).
  Now, such string can be worked with, by calling [`preg_match()`] and returning right after first match.

The only exception to this rule is `filter()->first()`, which still calls [`preg_match_all()`].

#### About `asInt()` chain

- New method `asInt()` can be chained with any [`match()`] method:
    - `match()->asInt()->all(): int[];`
    - `match()->asInt()->only(int $limit): int[];`
    - `match()->asInt()->first(callable $consumer = null): int;`
    - `match()->asInt()->forEach(callable $consumer): void;`
    - `match()->asInt()->findFirst(callable $consumer): Optional<int>;`
    - `match()->asInt()->count(): int;` though it doesn't change anything
    - `match()->asInt()->iterator(): \Iterator<int>;`
    - `match()->asInt()->map(callable $mapper): int[];`
    - `match()->asInt()->flatMap(callable $mapper);`
    - `match()->asInt()->distinct(): int[];`
    - `match()->asInt()->filter(callable $predicate): int[];`
- Callbacks passed to [`first()`]/[`map()`]/[`flatMap()`] etc. receive `int`.
- `asInt()->fluent()` is slightly better than `fluent()->asInt()`:
    - `fluent()->asInt()` creates [`Match`] details for each occurrence, which are then cast to `int`.
    - `asInt()->fluent()` simply returns matches as `int`.

Added in 0.9.5
--------------

* Breaking changes
    * Removed:
        - `pattern()->match()->fluent()->iterate()`
        - `pattern()->match()->group()->iterate()`
        - `pattern()->match()->group()->fluent()->iterate()`

      as [`iterate()`] was only needed as a substitute for [`forEach()`], pre PHP 7, where methods couldn't be named
      with keywords.
    * Renamed:
        - [`match()->forFirst()`] to [`findFirst()`] #70
* Enhancements
    * When no automatic delimiter (`/`, `#`, `%`, `~`, etc.) is applicable, character
      `0x01` is used (provided that it's not used anywhere else in the pattern). #71
* Features
    * Added `match()->group()->findFirst()` #22 #70
    * Added alternating groups in prepared patterns 🔥
        - [`Pattern::bind()`], [`Pattern::inject()`] and [`Pattern::prepare()`] still receive `string` (as a user input)
          , but they can also receive `string[]`, which will be treated as a regex *alternation group*:
          ```php
          Pattern::bind('Choice: @values', [
              'values' => ['apple?', 'orange', 'pear']
          ]);
          ```
          is similar to
          ```
          Pattern::of('Choice: (apple\?|orange|pear)')
          ```
          Of course `'apple?'` and other values are protected against user-input malformed patterns.
* Bug fixes
    * Previously, we added uniform quoting of `#` character on different PHP versions. Well, sorry to say that, we also
      made a bug doing that, when `#` was also a delimiter. This bug is fixed now.

Added in 0.9.4
--------------

* Breaking changes
    * Renamed `CleanRegexException` to `PatternException`
    * Moved `RegexException` to  `\TRegx` from `\TRegx\CleanRegex\Exception`
    * Simplified the namespace of public exceptions:
        - From `\TRegx\CleanRegex\Exception\CleanRegex` to `\TRegx\CleanRegex\Exception`
* Enhancements
    * Updated the hierarchy of public exceptions:
        - `RegexException`
            - `PregException` (extends `RegexException`, instead of [`\Exception`])
            - `PatternException`
                - `IntegerFormatException` (extends `PatternException`, instead of [`\Exception`])
                - `NoFirstElementFluentException` (extends `PatternException`, instead of [`\Exception`])
    * Previously, `RuntimePregException` was used to indicate every error that was reported by [`preg_last_error()`].
      Now, the following subclasses of `RuntimePregException` are thrown:
        - `SubjectEncodingPregException` for [`PREG_BAD_UTF8_ERROR`]
        - `Utf8OffsetPregException` for [`PREG_BAD_UTF8_OFFSET_ERROR`]
        - `BacktrackLimitPregException` for [`PREG_BACKTRACK_LIMIT_ERROR`]
        - `RecursionLimitPregException` for [`PREG_RECURSION_LIMIT_ERROR`]
        - `JitStackLimitPregException` for [`PREG_JIT_STACKLIMIT_ERROR`]
* Features
    * Added `match()->groupBy()`/`match()->filter()->groupBy()`:
        - `match()->groupBy()->texts()`
        - `match()->groupBy()->map(callable<Match>)`
        - `match()->groupBy()->flatMap(callable<Match>)`
        - `match()->groupBy()->offsets()`/`byteOffsets()`

      when `groupBy()` is preceded by `filter()`, it will take indexes, limits, matches order and user data into
      account.

Added in 0.9.3
--------------

* Breaking changes
    * Renamed exceptions:
        - `SafeRegexException` to `PregException`
        - `CompileSafeRegexException` to `CompilePregException`
        - `RuntimeSafeRegexException` to `RuntimePregException`
        - `SuspectedReturnSafeRegexException` to `SuspectedReturnPregException`
    * Removed [`pattern()->match()->iterate()`] - it was only needed as a substitute for [`forEach()`], pre PHP 7, where
      methods couldn't be named with keywords.
* Features
    * Added [`preg::last_error_msg()`], which works like `preg::last_error()`, but returns a human-readable message,
      instead of `int`.
* Fixing PHP
    * [`preg_match()`] in some cases returns `2`, instead of `1`. T-Regx fixes this bug by always returning `1`, on
      every PHP version (https://bugs.php.net/bug.php?id=78853).

Added in 0.9.2
--------------

* Breaking changes
    * Methods [`pattern()`]/[`Pattern::of()`] no longer "magically" guess whether a pattern is delimited or not.
      [`Pattern::of()`] assumes pattern *is* delimited, new [`Pattern::pcre()`] takes an old-school delimited pattern.
    * Constructor `new Pattern()` is no longer a part of T-Regx API. Use [`Pattern::of()`]/[`pattern()`]
    * Renamed [`Match.parseInt()`] to [`Match.toInt()`] (the same for [`MatchGroup`])
    * Removed [`pattern()->match()->test()`]/[`fails()`]. From now on, use [`pattern()->test()`]/[`fails()`]
    * Removed `is()`:
        - `is()->delimited()`
        - `is()->usable()`
        - `is()->valid()` is changed to [`valid()`]
    * Removed [`split()->ex()`], changed [`split()->inc()`] to [`split()`]
* Features
    * Added `Match.group().replace()` 🔥
    * Added `pattern()->match()->fluent()` 🔥
    * Added `pattern()->match()->asInt()`
    * Added `pattern()->match()->distinct()` (leaves only unique matches)
    * Added prepared pattern method [`Pattern::inject()`]/[`Pattern::bind()`] (see below)
    * In `pattern()->match()->groups()`:
        * Added `groups()->forEach()`/`iterate()`
        * Added `groups()->flatMap()`
        * Added `groups()->map()`
        * Added `group()->fluent()`
        * Added `groups()->names()` (and `namedGroups()->names()`)
        * Added `groups()->count()` (and `namedGroups()->count()`)
    * Added `match()->offsets()->fluent()`
    * Added `match()->group(string)->offsets()->fluent()`
    * Added `pattern()->forArray()->strict()` which throws for invalid values, instead of filtering them out
    * Added `pattern()->replace()->counting()`
* SafeRegex
    * Added `preg::grep_keys()` 🔥, that works exactly like `preg::grep()`, but filters by keys (also
      accepts [`PREG_GREP_INVERT`](https://www.php.net/manual/en/function.preg-grep.php))
* Enhancements/updates
    * Method [`by()->group()->orElse()`] now receives lazy-loaded [`Match`], instead of a subject
    * Added [`withReferences()`] to `CompositePattern.chainedReplace()`
    * Previously named [`Pattern::inject()`] is renamed to [`Pattern::bind()`]
    * The [`Pattern::bind()`] (old [`Pattern::inject()`]) still accepts values as an associative array, but
      new [`Pattern::inject()`] receives values without regard for the keys.
    * Fixed passing invalid types to [`forArray()`]. Previously, caused fatal error due to internal [`preg_grep()`]
      implementation.
* Other
    * Now `MalformedPatternException` is thrown, instead of `CompileSafeRegexException`, when using invalid PCRE syntax.
    * Returning [`Match`] from [`replace()->callback()`] (instead of [`Match.text()`] as `string`)
    * Match `+12` is no longer considered a valid integer for [`isInt()`]/[`toInt()`]
    * Unnamed group will be represented as `null` in `Match.groupNames()`, instead of being simply ignored
    * helper [`pattern()`] method, [`Pattern`] and [`PatternBuilder`] now return `PatternInterface`, instead
      of [`Pattern`]
      class.
      [`Pattern`] class now only holds static utility methods, and `PatternImpl` holds the pattern implementation.
* Maintenance
    * PhpUnit throws different exceptions because
      of [PHP `__toString()` exception policy change](https://wiki.php.net/rfc/tostring_exceptions).

Footnote:

- Apart from PHP type hints, every version up to this point could be run on PHP 5.3 (if one removes type hints from
  code, one can run T-Regx on PHP 5.3). Every error, exception, malfunction, inconsistency was handled correctly by
  T-Regx. From this version on (0.9.2), handling of the errors and inconsistencies is dropped, since T-Regx now only
  supports PHP 7.1.

Added in 0.9.1
--------------

* Features
    * Added `Match.textLength()`
    * Added `Match.group().textLength()`
    * Added `Match.groupsCount()`
    * Added:
        - [`by()->group()->orIgnore()`]
        - [`by()->group()->orElse()`]
        - `by()->group()->callback()` which accepts [`MatchGroup`] as an argument

Available in 0.9.0
------------------

* Features
    * Pass flags as [`pattern()`] second argument
    * Add `Match.groups()`
    * Add [`Match.limit()`](https://t-regx.com/docs/match-details#limit)
    * Add `Match.group()->all()`
    *
  Add [`Match.getUserData()`](https://t-regx.com/docs/match-details#user-data)
  /[`setUserData()`](https://t-regx.com/docs/match-details#user-data)
    * Add [`ReplaceMatch.modifiedSubject()`](https://t-regx.com/docs/replace-match-details#modifiedsubject-example)
    * Returning from [`match()->first(callable)`] modifies its return value
    * Add [`pattern()->remove()`](https://t-regx.com/docs/replace-with#remove-occurrence)
    * Add [`pattern()->replace()->by()`](https://t-regx.com/docs/replace-by-group)
    * Add [`match()->only(int)`](https://t-regx.com/docs/match#retrieve-multiple-matches)
    * Add [`match()->flatMap()`]
    * Add `match()->group()->all()`/`first()`/`only()`
    * Add [`match()->iterator()`](https://t-regx.com/docs/match-iterator)
    * Add [`match()->forFirst()`]
        * with methods [`orReturn()`](https://t-regx.com/docs/match-find-first#orreturn),
          [`orElse()`](https://t-regx.com/docs/match-find-first#orelse) and
          [`orThrow()`](https://t-regx.com/docs/match-find-first#orthrow)
        * [`orThrow()`](https://t-regx.com/docs/match-find-first#custom-exceptions-for-orthrow) can instantiate
          exceptions by class name (with one of predefined constructor signatures)
    * [`match->only(i)`](https://t-regx.com/docs/match#retrieve-multiple-matches) calls [`preg_match()`] for `i=1`,
      and [`preg_match_all()`] for other values
    * [`pattern()->match()`](https://t-regx.com/docs/match) is [`\Countable`]
        * Add UTF-8 support for methods `offset()`, `modifiedOffset()` and `modifiedSubject()`
        * Add [`split()->filter()`]
        * Add `NotMatched.groupsCount()`
        * Add [`CompositePattern`] (#8)
        * Add [`PatternBuilder`] with [`prepare()`], [`inject()`] and `compose()` methods (#25)
    * Use [`PREG_UNMATCHED_AS_NULL`] if PHP version is supported
        * Add [`Pattern::unquote()`]
* Tests
    * Split tests into `\Test\Unit`, `\Test\Integration`, `\Test\Functional` and `\Test\Feature` folders
    * Add dynamic skip for `ErrorsCleanerTest`
    * Handle [PHP bugfix in 7.1.13](https://bugs.php.net/bug.php?id=74183).
* Other
    * Set `\TRegx` namespace prefix
    * Add [`ext-mbstring`](https://www.php.net/manual/en/mbstring.installation.php) requirement to
      [`composer.json`](https://getcomposer.org/doc/04-schema.md).
    * [`preg_match()`] won't return unmatched groups at the end of list, which makes validating groups and general work
      with group names impossible. Then, a second call to [`preg_match_all()`] is done to get a list of all groups (even
      unmatched ones). The call to [`preg_match_all()`] is of course only in the case of [`hasGroup()`] or similar
      method. Regular methods like [`Match.text()`] won't call [`preg_match_all()`]
* Debug
    * Add `pregConstant` field to `RuntimeError`. Only reason to do it is so if you **catch the exception it in
      debugger**, you'll see the constant name (i.e. [`PREG_BAD_UTF8_ERROR`]) instead of the constant value (i.e. `4`).
    * Handle bug [PHP #75355](https://bugs.php.net/bug.php?id=75355)
* Bug fixes
    * `preg::replace()` and `preg::filter()` only consider `[]` error-prone if input subject was also an empty array.

[`pattern()`]: https://t-regx.com/docs/introduction-clean#entry-points

[`Pattern`]: https://t-regx.com/docs/introduction-clean#entry-points

[`Pattern::of()`]: https://t-regx.com/docs/introduction-clean#entry-points

[`preg_match()`]: https://www.php.net/manual/en/function.preg-match.php

[`preg_match_all()`]: https://www.php.net/manual/en/function.preg-match-all.php

[`preg_replace()`]: https://www.php.net/manual/en/function.preg-replace.php

[`preg_last_error()`]: https://www.php.net/manual/en/function.preg-last-error.php

[`preg_grep()`]: https://www.php.net/manual/en/function.preg-grep.php

[`array_merge()`]: https://www.php.net/manual/en/function.array-merge.php

[`InvalidArgumentException`]: https://www.php.net/manual/en/class.invalidargumentexception.php

[`\InvalidArgumentException`]: https://www.php.net/manual/en/class.invalidargumentexception.php

[Prepared patterns]: https://t-regx.com/docs/handling-user-input

[`PCRE_EXTENDED`]: https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php

[`PREG_INTERNAL_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_BAD_UTF8_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_BAD_UTF8_OFFSET_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_BACKTRACK_LIMIT_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_RECURSION_LIMIT_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_JIT_STACKLIMIT_ERROR`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_UNMATCHED_AS_NULL`]: https://www.php.net/manual/en/pcre.constants.php

[`PREG_SET_ORDER`]: https://www.php.net/manual/en/pcre.constants.php

[`Match`]: https://t-regx.com/docs/match-details

[`Detail`]: https://t-regx.com/docs/match-details

[`ReplaceMatch`]: https://t-regx.com/docs/replace-match-details

[`ReplaceDetail`]: https://t-regx.com/docs/replace-match-details

[`pattern()->delimiter()`]: https://t-regx.com/docs/delimiters

[`pattern()->delimited()`]: https://t-regx.com/docs/delimiters

[`TypeError`]: https://www.php.net/manual/en/class.typeerror.php

[`withReferences()`]: https://t-regx.com/docs/replace-with#php-style-intentional-references

[`Pattern::pcre()`]: https://t-regx.com/docs/introduction-clean#old-school-patterns

[`Pattern::prepare()`]: https://t-regx.com/docs/prepared-patterns#with-patternprepare

[`Pattern::inject()`]: https://t-regx.com/docs/prepared-patterns#with-patterninject

[`prepare()`]: https://t-regx.com/docs/prepared-patterns#with-patternprepare

[`inject()`]: https://t-regx.com/docs/prepared-patterns#with-patterninject

[`Pattern::bind()`]: https://t-regx.com/docs/prepared-patterns#with-patternbind

[`PatternBuilder::prepare()`]: https://t-regx.com/docs/prepared-patterns#pcre-styled-patterns

[`PatternBuilder`]: https://t-regx.com/docs/prepared-patterns#pcre-styled-patterns

[`preg::last_error_msg()`]: https://t-regx.com/docs/utils#preglast_error_msg

[`pattern()->match()->iterate()`]: https://t-regx.com/docs/match-for-each

[`iterate()`]: https://t-regx.com/docs/match-for-each

[`forEach()`]: https://t-regx.com/docs/match-for-each

[`valid()`]: https://t-regx.com/docs/valid

[`Pattern::unquote()`]: https://t-regx.com/docs/utils#patternunquotes

[`hasGroup()`]: https://t-regx.com/docs/match-groups#group-details

[`Match.get(string|int)`]: http://t-regx.com/docs/match-details#matched-text

[`\Countable`]: https://www.php.net/manual/en/class.countable.php

[`forArray()`]: https://t-regx.com/docs/filter

[`\Exception`]: https://www.php.net/manual/en/class.exception.php

[`match()`]: https://t-regx.com/docs/match

[`first()`]: https://t-regx.com/docs/match-first

[`match()->first(callable)`]: https://t-regx.com/docs/match-first

[`match()->first()`]: https://t-regx.com/docs/match-first#use-first-with-callback

[`findFirst()`]: https://t-regx.com/docs/match-find-first

[`match()->findFirst()`]: https://t-regx.com/docs/match-find-first

[`match()->forFirst()`]: https://t-regx.com/docs/match-find-first

[`map()`]: https://t-regx.com/docs/match-map

[`flatMap()`]: https://t-regx.com/docs/match-map#flatmap

[`match()->flatMap()`]: https://t-regx.com/docs/match-map#flatmap

[`pattern()->replace()`]:  https://t-regx.com/docs/replace

[`replace()->callback()`]: https://t-regx.com/docs/replace-callback

[`callback()`]: https://t-regx.com/docs/replace-callback

[`by()->map()`]: https://t-regx.com/docs/replace-by-map

[`split()`]: https://t-regx.com/docs/split

[`split()->ex()`]: https://t-regx.com/docs/split

[`split()->inc()`]: https://t-regx.com/docs/split

[`split()->filter()`]: https://t-regx.com/docs/split

[`CompositePattern`]: https://t-regx.com/docs/composite-pattern

[`Match.usingDuplicateName()`]: https://t-regx.com/docs/match-groups-j-modifier

[`usingDuplicateName()`]: https://t-regx.com/docs/match-groups-j-modifier

[`Match.usingDuplicateName().group('group')`]: https://t-regx.com/docs/match-groups-j-modifier

[`DuplicateNamedGroup`]: https://t-regx.com/docs/match-groups-j-modifier

[`MatchGroup`]: https://t-regx.com/docs/match-group

[`DetailGroup`]: https://t-regx.com/docs/match-group

[`pattern()->match()->test()`]: https://t-regx.com/docs/match#test-a-subject

[`pattern()->test()`]: https://t-regx.com/docs/match#test-a-subject

[`fails()`]: https://t-regx.com/docs/match#test-a-subject

[`Match.parseInt()`]: https://t-regx.com/docs/match-as-int

[`Match.toInt()`]: https://t-regx.com/docs/match-as-int

[`toInt()`]: https://t-regx.com/docs/match-as-int

[`isInt()`]: https://t-regx.com/docs/match-as-int

[`Match.text()`]: https://t-regx.com/docs/match-details#matched-text

[`by()->group()->orElse()`]: https://t-regx.com/docs/replace-by-group

[`by()->group()->orIgnore()`]: https://t-regx.com/docs/replace-by-group#orelseignore

[`orThrow(string)`]: https://t-regx.com/docs/replace-by-group/#orelsethrow

[`orElseThrow(string)`]: https://t-regx.com/docs/replace-by-group/#orelsethrow

[`orIgnore()`]: https://t-regx.com/docs/replace-by-group/#orelseignore

[`orElseIgnore()`]: https://t-regx.com/docs/replace-by-group/#orelseignore

[`orEmpty()`]: https://t-regx.com/docs/replace-by-group/#orelseempty

[`orElseEmpty()`]: https://t-regx.com/docs/replace-by-group/#orelseempty

[`orReturn(string)`]: https://t-regx.com/docs/replace-by-group/#orelsewithstring

[`orElseWith(string)`]: https://t-regx.com/docs/replace-by-group/#orelsewithstring

[`orElse(callable)`]: https://t-regx.com/docs/replace-by-group/#orelsecallingcallable

[`orElseCalling(callable)`]: https://t-regx.com/docs/replace-by-group/#orelsecallingcallable

[1]: https://t-regx.com/docs/replace-by-map#groups

[2]: https://t-regx.com/docs/replace-match-details

[`get_class()`]: https://www.php.net/manual/en/function.get-class.php

[8]: https://www.php.net/manual/en/control-structures.match.php