# Copyright (c) 2023 Yuki Kimoto
# MIT License
class Regex {
version "0.247";
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_or_buffer : object of string|StringBuffer, $offset_ref : int* = undef, $length : int = -1) {
my $string = (string)undef;
if ($string_or_buffer isa string) {
$string = (string)$string_or_buffer;
}
elsif ($string_or_buffer isa StringBuffer) {
my $string_buffer = (StringBuffer)$string_or_buffer;
my $offset = 0;
if ($offset_ref) {
$offset = $$offset_ref;
}
if ($length < 0) {
$length = $string_buffer->length - $offset;
}
unless ($offset + $length <= $string_buffer->length) {
die "\$\$offset_ref + \$length must be less than or equalt to the lenght of \$string_or_buffer.";
}
$string = $string_buffer->get_string_unsafe;
}
else {
die "The type of \$string_ref_or_buffer must be string or StringBuffer.";
}
my $regex_match = $self->match_string($string, $offset_ref, $length);
return $regex_match;
}
method replace : Regex::ReplaceInfo ($string_ref_or_buffer : object of string[]|StringBuffer, $replace : object of string|Regex::Replacer, $offset_ref : int* = undef, $length : int = -1, $options : object[] = undef) {
my $replace_info_ref = new Regex::ReplaceInfo[1];
my $string_ref = (string)undef;
if ($string_ref_or_buffer isa string[]) {
my $string_ref = (string[])$string_ref_or_buffer;
unless (@$string_ref == 1) {
die "\$string_ref_or_buffer must be 1-length array if the type is string[].";
}
my $string = $string_ref->[0];
$options = Fn->merge_options($options, {info => $replace_info_ref});
my $replaced_string = $self->replace_string($string, $replace, $offset_ref, $length, $options);
$string_ref->[0] = $replaced_string;
}
elsif ($string_ref_or_buffer isa StringBuffer) {
my $string_buffer = (StringBuffer)$string_ref_or_buffer;
my $offset = 0;
if ($offset_ref) {
$offset = $$offset_ref;
}
if ($length < 0) {
$length = $string_buffer->length - $offset;
}
unless ($offset + $length <= $string_buffer->length) {
die "\$\$offset_ref + \$length must be less than or equalt to the lenght of \$string_ref_or_buffer.";
}
$options = Fn->merge_options($options, {info => $replace_info_ref});
$self->replace_buffer_common($string_buffer, $replace, $offset_ref, $length, $options);
}
else {
die "The type of \$string_ref_or_buffer must be string[] or StringBuffer.";
}
my $replace_info = $replace_info_ref->[0];
if ($replace_info->replaced_count > 0) {
return $replace_info;
}
else {
return undef;
}
}
method replace_g : Regex::ReplaceInfo ($string_ref_or_buffer : object of string[]|StringBuffer, $replace : object of string|Regex::Replacer, $offset_ref : int* = undef, $length : int = -1, $options : object[] = undef) {
return $self->replace($string_ref_or_buffer, $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_list = StringList->new_len(0);
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, \$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_list->push($part);
$offset = $match_start + $match_length;
}
}
if ($offset == $string_length) {
$parts_list->push("");
}
else {
my $part = Fn->substr($string, $offset, $string_length - $offset);
$parts_list->push($part);
}
if ($limit == 0) {
while ($parts_list->length > 0) {
if ($parts_list->get($parts_list->length - 1) eq "") {
$parts_list->pop;
}
else {
last;
}
}
}
my $parts = $parts_list->to_array;
return $parts;
}
native method DESTROY : void ();
# Private Instance Methods
private native method match_string : Regex::Match ($string : string, $offset_ref : int*, $length : int = -1);
private method buffer_match : Regex::Match ($string_buffer : StringBuffer, $offset : int = 0, $length : int = -1) {
if ($length < 0) {
$length = $string_buffer->length - $offset;
}
my $regex_match = $self->match($string_buffer->get_string_unsafe, \$offset, $length);
return $regex_match;
}
private method match_buffer : Regex::Match ($string_buffer : StringBuffer, $offset_ref : int*, $length : int = -1) {
if ($length < 0) {
$length = $string_buffer->length - $$offset_ref;
}
my $regex_match = $self->match_string($string_buffer->get_string_unsafe, $offset_ref, $length);
return $regex_match;
}
private method replace_string : string ($string : string, $replace : object of string|Regex::Replacer, $offset_ref : int*, $length : int = -1, $options : object[] = undef) {
my $string_buffer = StringBuffer->new;
$string_buffer->push($string);
$self->replace_buffer_common($string_buffer, $replace, $offset_ref, $length, $options);
my $result_string = $string_buffer->to_string;
return $result_string;
}
private precompile method replace_buffer_common : void ($string_buffer : StringBuffer, $replace : object of string|Regex::Replacer, $offset_ref : int*, $length : int = -1, $options : object[] = undef) {
my $optiton_h = Hash->new($options);
my $offset = 0;
if ($offset_ref) {
$offset = $$offset_ref;
}
my $original_offset = $offset;
my $global = 0;
if (my $global_obj = $optiton_h->get("global")) {
$global = (int)$global_obj;
}
my $string_length = $string_buffer->length;
if ($length == -1) {
$length = $string_length - $offset;
}
my $regex_match = (Regex::Match)undef;
my $match_count = 0;
while (1) {
$regex_match = $self->match_buffer($string_buffer, \$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->replace($match_start, $match_length, $replace_string);
unless ($global) {
last;
}
my $next_offset = $match_start + $replace_string_length;
$offset = $next_offset;
$length = $string_buffer->length - $offset;
}
my $regex_replace_info = Regex::ReplaceInfo->new({replaced_count => $match_count, match => $regex_match});
my $option_regex_replace_info = (Regex::ReplaceInfo[])$optiton_h->get("info");
if ($option_regex_replace_info) {
$option_regex_replace_info->[0] = $regex_replace_info;
}
if ($offset_ref) {
$$offset_ref = $offset;
}
}
}