# Copyright (c) 2023 Yuki Kimoto
# MIT License
class StringBuffer {
use Fn;
use Array;
interface Cloneable;
interface Comparable;
interface EqualityCheckable;
# Enumerations
private enum {
DEFAULT_CAPACITY = 4,
}
# Fields
has capacity : ro int;
has length : ro int;
has string : mutable string;
# Class methods
static method new : StringBuffer ($string : string = undef, $capacity : int = -1) {
my $length : int;
if ($string) {
$length = length $string;
}
else {
$length = 0;
}
my $self = &new_len($length, $capacity);
if ($string) {
Fn->memcpy($self->{string}, 0, $string, 0, $length);
}
return $self;
}
static method new_len : StringBuffer ($length : int, $capacity : int = -1) {
unless ($length >= 0) {
die "The length \$length must be greater than or equal to 0.";
}
if ($capacity < 0) {
$capacity = &DEFAULT_CAPACITY;
}
if ($length > $capacity) {
$capacity = $length;
}
my $self = new StringBuffer;
$self->{string} = (mutable string)new_string_len($capacity);
$self->{capacity} = $capacity;
$self->{length} = $length;
return $self;
}
# Instance methods
method push : void ($string : string, $offset : int = 0, $length : int = -1) {
unless ($string) {
die "The string \$string must be defined.";
}
my $string_length = length $string;
unless ($offset >= 0) {
die "The offset \$offset must be greater than or equal to 0.";
}
if ($length == -1) {
$length = length $string - $offset;
}
unless ($offset + $length <= $string_length) {
die "The offset \$offset + the length \$length must be less than or equal to the length of the string $string.";
}
my $buffer_length = $self->{length};
my $new_length = $buffer_length + $length;
$self->_maybe_extend($new_length);
Fn->memcpy($self->{string}, $buffer_length, $string, $offset, $length);
$self->{length} += $length;
}
method push_char : void ($char : int) {
my $length = $self->{length};
my $new_length = $length + 1;
$self->_maybe_extend($new_length);
$self->{string}[$self->{length}++] = (byte)$char;
}
method replace : void ($offset : int, $remove_length : int, $replace : string) {
unless ($offset >= 0) {
die "The offset \$offset must be greater than or equal to 0.";
}
unless ($remove_length >= 0) {
die "The removal length \$remove_length must be greater than or equal to 0.";
}
unless ($offset + $remove_length <= $self->{length}) {
die "\$offset + \$removing length must be less than or equal to the length of \$string buffer.";
}
my $replace_length = 0;
if ($replace) {
$replace_length = length $replace;
}
my $new_length = $self->{length} - $remove_length + $replace_length;
$self->_maybe_extend($new_length);
my $move_length = $self->{length} - $offset - $remove_length;
Fn->memmove($self->{string}, $offset + $replace_length, $self->{string}, $offset + $remove_length, $move_length);
if ($replace) {
Fn->memcpy($self->{string}, $offset, $replace, 0, $replace_length);
}
$self->{length} = $new_length;
}
method reserve : void ($new_capacity : int) {
unless ($new_capacity >= 0) {
die "The new capacity \$new_capacity must be greater than or equal to 0.";
}
my $capacity = $self->{capacity};
if ($new_capacity > $capacity) {
my $length = $self->{length};
my $new_buffer = (mutable string)new_string_len $new_capacity;
Fn->memcpy($new_buffer, 0, $self->{string}, 0, $length);
$self->{string} = $new_buffer;
$self->{capacity} = $new_capacity;
}
}
method to_string : string () {
my $new_string = Fn->substr($self->{string}, 0, $self->{length});
return $new_string;
}
method get_string_unsafe : string () {
return $self->{string};
}
private method _maybe_extend : void ($min_capacity : int) {
my $capacity = $self->{capacity};
unless ($min_capacity > $capacity) {
return;
}
if ($capacity < $min_capacity) {
$capacity = $min_capacity;
}
my $new_capacity = $capacity * 2;
my $new_buffer = (mutable string)new_string_len $new_capacity;
my $length = $self->{length};
my $buffer = $self->{string};
Fn->memcpy($new_buffer, 0, $buffer, 0, $length);
$self->{string} = $new_buffer;
$self->{capacity} = $new_capacity;
}
method set_length : void ($length : int) {
unless ($length >= 0) {
die "The length \$length must be greater than or equal to 0.";
}
$self->_maybe_extend($length);
$self->{length} = $length;
if ($length < $self->{capacity}) {
Fn->memset_char($self->{string}, '\0', $length, $self->{capacity} - $length);
}
}
method set : void ($string : string) {
$self->set_length(0);
$self->push($string);
}
method clone : StringBuffer () {
my $string = $self->to_string;
my $clone = &new($string);
return $clone;
}
method cmp : int ($a : StringBuffer, $b : StringBuffer) {
my $cmp = 0;
if ($a == undef && $b == undef) {
$cmp = 0;
}
elsif ($a != undef && $b == undef) {
$cmp = 1;
}
elsif ($a == undef && $b != undef) {
$cmp = -1;
}
else {
my $a_length = $a->length;
my $b_length = $b->length;
my $a_string = $a->{string};
my $b_string = $b->{string};
my $short_length = -1;
if ($a_length < $b_length) {
$short_length = $a_length;
}
else {
$short_length = $b_length;
}
my $memcmp_ret = Fn->memcmp($a_string, 0, $b_string, 0, $short_length);
if ($memcmp_ret) {
if ($memcmp_ret < 0) {
$cmp = -1;
}
else {
$cmp = 1;
}
}
elsif ($a_length == $b_length) {
$cmp = 0;
}
else {
if ($a_length < $b_length) {
$cmp = -1;
}
else {
$cmp = 1;
}
}
}
return $cmp;
}
method eq : int ($a : StringBuffer, $b : StringBuffer) {
my $eq = 0;
if ($a && $b) {
$eq = $a->cmp($a, $b) == 0;
}
elsif ($a) {
$eq = 0;
}
elsif ($b) {
$eq = 0;
}
else {
$eq = 1;
}
return $eq;
}
}