# Copyright (c) 2023 Yuki Kimoto
# MIT License
class Regex {
version "0.258";
use Resource::RE2;
use Fn;
use Regex::Replacer;
use Regex::RE2;
use Regex::Match;
use Regex::ReplaceInfo;
use Hash;
use List;
has re2 : Regex::RE2;
static method new : Regex ($pattern : string, $flags : string = undef) {
unless ($pattern) {
die "The regex pattern \$pattern must be defined.";
}
my $self = new Regex;
my $re2_pattern = &create_re2_pattern($pattern, $flags);;
$self->compile($re2_pattern);
return $self;
}
# Private class methods
private static method create_re2_pattern : string ($pattern : string, $flags : string = undef) {
my $re2_pattern = (string)undef;
if ($flags) {
$re2_pattern = "(?$flags)$pattern";
}
else {
$re2_pattern = $pattern;
}
return $re2_pattern;
}
private native method compile : void ($pattern : string);
# Instance Methods
method match : Regex::Match ($string : string, $offset_ref : int* = undef, $length : int = -1) {
my $string_offset = $offset_ref ? $$offset_ref : 0;
my $match_offset = 0;
my $regex_match = $self->match_string($string, $string_offset, \$match_offset, $length);
if ($offset_ref) {
$$offset_ref += $match_offset;
}
return $regex_match;
}
method replace : Regex::ReplaceInfo ($string : mutable string, $replace : string|Regex::Replacer, $offset_ref : int* = undef, $length : int = -1, $options : object[] = undef) {
unless ($string) {
die "The string \$string must be defined.";
}
my $string_buffer = StringBuffer->new_ref((mutable string)$string);
my $options = Fn->to_hash($options);
my $global = $options->{"global"}->(int);
my $string_length = $string_buffer->length;
my $string_offset = $offset_ref ? $$offset_ref : 0;
my $match_offset = 0;
my $regex_match = (Regex::Match)undef;
my $match_count = 0;
while (1) {
$regex_match = $self->match_string($string_buffer->get_string, $string_offset, \$match_offset, $length);
if ($regex_match) {
$match_count++;
}
else {
last;
}
my $replace_string : string;
if ($replace isa string) {
$replace_string = (string)$replace;
}
elsif ($replace isa Regex::Replacer) {
my $replacer = (Regex::Replacer)$replace;
$replace_string = $replacer->($self, $regex_match);
}
else {
die "\$replace must be a string or a Regex::Replacer object";
}
my $replace_string_length = length $replace_string;
my $match_start = $regex_match->match_start;
my $match_length = $regex_match->match_length;
$string_buffer->substr($match_start, $match_length, $replace_string);
unless ($global) {
last;
}
my $next_string_offset = $match_start + $replace_string_length;
$match_offset = $next_string_offset - $string_offset;
$length -= $match_offset;
}
my $replace_info = Regex::ReplaceInfo->new({replaced_count => $match_count, match => $regex_match});
if ($offset_ref) {
$$offset_ref += $match_offset;
}
if ($replace_info->replaced_count > 0) {
return $replace_info;
}
else {
return undef;
}
}
method replace_g : Regex::ReplaceInfo ($string : mutable string, $replace : string|Regex::Replacer, $offset_ref : int* = undef, $length : int = -1, $options : object[] = undef) {
return $self->replace($string, $replace, $offset_ref, $length, Fn->merge_options($options, {global => 1}));
}
precompile method split : string[] ($string : string, $limit : int = 0) {
unless ($string) {
die "The string \$string must be defined";
}
my $string_length = length $string;
my $parts = StringList->new;
my $offset = 0;
my $match_count = 0;
for (my $i = 0; $i < $string_length; $i++) {
if ($limit > 0 && $match_count >= $limit - 1) {
last;
}
my $current_offset = $offset;
my $regex_match = $self->match_string($string, 0, \$offset);
if ($regex_match) {
$match_count++;
my $match_start = $regex_match->match_start;
my $match_length = $regex_match->match_length;
my $part = Fn->substr($string, $current_offset, $match_start - $current_offset);
$parts->push($part);
if (@{$regex_match->captures}) {
$parts->push_($regex_match->captures);
}
$offset = $match_start + $match_length;
}
}
if ($offset == $string_length) {
$parts->push("");
}
else {
my $part = Fn->substr($string, $offset, $string_length - $offset);
$parts->push($part);
}
if ($limit == 0) {
while (@$parts > 0) {
if ($parts->[@$parts - 1] eq "") {
$parts->pop;
}
else {
last;
}
}
}
return $parts->get_array;
}
native method DESTROY : void ();
# Private Instance Methods
private native method match_string : Regex::Match ($string : string, $string_offset : int = 0, $match_offset_ref : int* = undef, $length : int = -1);
}