# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Fn {
  use StringBuffer;
  use StringList;
  use IntList;
  use Hash;
  use Sort;
  use List;
  
  enum {
    GET_CODE_POINT_ERROR_OVER_STRING_RANGE = -1,
    GET_CODE_POINT_ERROR_INVALID_UTF8 = -2,
  }
  
  static method BYTE_MAX : int () { return 127; }
  static method BYTE_MIN : int () { return -128; }
  native static method DBL_MAX : double ();
  native static method DBL_MIN : double ();
  native static method DOUBLE_MAX : double ();
  native static method DOUBLE_MIN : double ();
  native static method FLOAT_MAX : float ();
  native static method FLOAT_MIN : float();
  native static method FLT_MAX : float ();
  native static method FLT_MIN : float();
  static method INT16_MAX : int () { return 32767; }
  static method INT16_MIN : int () { return -32768; }
  static method INT32_MAX : int () { return 2147483647; }
  static method INT32_MIN : int () { return -2147483648; }
  static method INT64_MAX : long () { return 9223372036854775807L; }
  static method INT64_MIN : long () { return -9223372036854775808L; }
  static method INT8_MAX : int () { return 127; }
  static method INT8_MIN : int () { return -128; }
  static method INT_MAX : int () { return 2147483647; }
  static method INT_MIN : int () { return -2147483648; }
  static method LONG_MAX : long () { return 9223372036854775807L; }
  static method LONG_MIN : long () { return -9223372036854775808L; }
  static method RAND_MAX : int () { return 2147483647; }
  static method SHORT_MAX : int () { return 32767; }
  static method SHORT_MIN : int () { return -32768; }
  static method UBYTE_MAX : int () { return (byte)0xFF; }
  static method UINT16_MAX : int () { return (short)0xFFFF; }
  static method UINT32_MAX : int () { return 0xFFFFFFFF; }
  static method UINT64_MAX : long () { return 0xFFFFFFFFFFFFFFFFL; }
  static method UINT8_MAX : int () { return (byte)0xFF; }
  static method UINT_MAX : int () { return 0xFFFFFFFF; }
  static method ULONG_MAX : long () { return 0xFFFFFFFFFFFFFFFFL; }
  static method USHORT_MAX : int () { return (short)0xFFFF; }
  
  static method abs : int ($value : int) {
    
    my $abs = 0;
    if ($value > 0) {
      $abs = $value;
    }
    else {
      $abs = -$value;
    }
    return $abs;
  }
  
  static method chomp : void ($string : mutable string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    if ($length >= 2 && $string->[$length - 2] == '\r' && $string->[$length - 1] == '\n') {
      &shorten($string, $length - 2);
    }
    elsif ($length >= 1 && $string->[$length - 1] == '\n') {
      &shorten($string, $length - 1);
    }
  }
  
  static method chompr : string ($string : string) {
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $new_string = (mutable string)copy $string;
    
    &chomp($new_string);
    
    return $new_string;
  }
  
  static method chr : string ($code_point : int) {
    
    my $is_unicode_scalar_value = &is_unicode_scalar_value($code_point);
    my $utf8_char = (string)undef;
    if ($is_unicode_scalar_value) {
      $utf8_char = &_chr_native($code_point);
    }
    
    return $utf8_char;
  }
  
  static method contains : int ($string : string, $substring : string, $string_offset : int = 0, $string_length : int = -1) {
    return &index($string, $substring, $string_offset, $string_length) >= 0;
  }
  
  static method copy_string : string ($string : string) {
    return copy $string;
  }
  
  native static method crand : int ($seed : int*);

  precompile static method equals_string_range : int ($string1 : string, $string1_offset : int, $string2 : string, $string2_offset : int, $length : int) {
    unless ($string1) {
      die "The \$string1 must be defined.";
    }
    
    unless ($string2) {
      die "The \$string2 must be defined.";
    }
    
    unless ($string1_offset >= 0) {
      die "The \$string1_offset must be greater than or equal to 0.";
    }
    
    unless ($string2_offset >= 0) {
      die "The \$string2_offset must be greater than or equal to 0.";
    }
    
    my $string1_length = length $string1;
    unless ($string1_offset + $length <= $string1_length) {
      return 0;
    }
    
    my $string2_length = length $string2;
    unless ($string1_offset + $length <= $string2_length) {
      return 0;
    }
    
    my $match = 1;
    for (my $i = 0; $i < $length; $i++) {
      my $char1 = $string1->[$string1_offset + $i];
      my $char2 = $string2->[$string2_offset + $i];
      unless ($char1 == $char2) {
        $match = 0;
        last;
      }
    }
    
    return $match;
  }
  
  native static method get_code_point : int ($string : string, $offset_ref : int*);
  
  precompile static method hex : int ($hex_string : string) {
    
    unless ($hex_string) {
      die "The \$hex string must be defined.";
    }
    
    my $hex_value = 0;
    my $digit = 0;
    my $value = 0;
    my $length = length $hex_string;
    unless ($length >= 1 && $length <= 8) {
      die "The length of the \$hex string must be 1 to 8.";
    }
    for (my $i = $length - 1; $i >= 0; $i--) {
      my $ascii_code = $hex_string->[$i];
      
      unless (&is_hex_digit($ascii_code)) {
        die "The \$hex string must contain only hex characters.";
      }
      
      my $digit_value = 0;
      if ($ascii_code >= '0' && $ascii_code <= '9') {
        $digit_value = $ascii_code - 48;
      }
      elsif ($ascii_code >= 'a' && $ascii_code <= 'f') {
        $digit_value = $ascii_code - 87;
      }
      elsif ($ascii_code >= 'A' && $ascii_code <= 'F') {
        $digit_value = $ascii_code - 55;
      }
      $value += $digit_value * &powi(16, $digit);
      
      $digit += 1;
    }
    
    return $value;
  }
  
  precompile static method index : int ($string : string, $substring : string, $begin : int = 0, $end : int = -1) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($substring) {
      die "The \$substring must be defined.";
    }
    
    unless ($begin >= 0 && $begin <= length $string) {
      die "The \$begin must be between 0 and the length of the \$string.";
    }
    
    my $string_length = length $string;
    
    if ($end < 0) {
      $end = $string_length;
    }
    
    unless ($end <= length $string) {
      die "The \$end must be less than or equal to the length of the \$string.";
    }
    
    unless ($end >= $begin) {
      die "The \$end must be greater than or equal to \$begin.";
    }
    
    my $substring_length = length $substring;
    
    if ($substring_length == 0) {
      return $begin;
    }
    
    if ($end == $string_length) {
      $end--;
    }
    
    for (my $i = $begin; $i <= $end; $i++) {
      my $match = 1;
      for (my $k = 0; $k < $substring_length; $k++) {
        
        if ($i + $k > $end) {
          $match = 0;
          last;
        }
        
        unless ($string->[$i + $k] == $substring->[$k]) {
          $match = 0;
          last;
        }
      }
      
      if ($match) {
        return $i;
      }
    }
    
    return -1;
  }
  

  precompile static method init_string : void ($string : mutable string, $ascii_code : int = 0, $offset : int = 0, $length : int = -1) {
    unless ($string) {
      die "The \$string must be defined.";
    }
    my $string_length = length $string;
    
    if ($length < 0) {
      $length = $string_length - $offset;
    }
    
    unless ($offset + $length <= $string_length) {
      die "The \$offset + the \$length must be less than or equal to the length of the \$string.";
    }
    
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $string->[$i] = (byte)$ascii_code;
    }
  }
  
  static method is_alnum : int ($code_point : int) {
    
    if (($code_point >= 'A' && $code_point <= 'Z') || ($code_point >= 'a' && $code_point <= 'z') || ($code_point >= '0' && $code_point <= '9')) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_alpha : int ($code_point : int) {
    
    if (($code_point >= 'A' && $code_point <= 'Z') || ($code_point >= 'a' && $code_point <= 'z')) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  native static method is_array : int ($object : object);
  
  static method is_blank : int ($code_point : int) {
    
    # SP or HT
    if ($code_point >= '\x20' ||  $code_point <= '\x9') {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  native static method is_class : int ($object : object);
  
  static method is_cntrl : int ($code_point : int) {
    
    if (($code_point >= 0x00 && $code_point <= 0x1f) || $code_point == 0x7f) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_digit : int ($code_point : int) {
    
    if ($code_point >= '0' && $code_point <= '9') {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_graph : int ($code_point : int) {
    
    if ($code_point >= 0x21 && $code_point <= 0x7E) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_hex_digit : int ($code_point : int) {
    
    if (($code_point >= '0' && $code_point <= '9') || ($code_point >= 'a' && $code_point <= 'f') || ($code_point >= 'A' && $code_point <= 'F')) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_lower : int ($code_point : int) {
    
    if ($code_point >= 'a' && $code_point <= 'z') {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  native static method is_mulnum_array : int ($object : object);
  
  native static method is_numeric_array : int ($object : object);
  
  native static method is_object_array : int ($object : object);
  
  # This is same as Perl ASCII mode \s
  static method is_perl_space : int ($code_point : int) {
    my $is_perl_space = 0;
    switch ($code_point) {
      case 0x20: # ' '  SP
      case 0x0D: # '\r' CR
      case 0x0A: # '\n' LF
      case 0x09: # '\t' HT
      case 0x0C: # '\f' FF
      {
        $is_perl_space = 1;
        break;
      }
    }
    
    return $is_perl_space;
  }
  
  static method is_perl_word : int ($code_point : int) {
    my $ispword = 0;
    
    if ($code_point >= 'a' && $code_point <= 'z') {
      $ispword = 1;
    }
    elsif ($code_point >= 'A' && $code_point <= 'Z') {
      $ispword = 1;
    }
    elsif ($code_point == '_') {
      $ispword = 1;
    }
    elsif ($code_point >= '0' && $code_point <= '9') {
      $ispword = 1;
    }
    return $ispword;
  }
  
  native static method is_pointer_class : int ($object : object);
  
  static method is_print : int ($code_point : int) {
    
    if ($code_point >= 0x20 && $code_point <= 0x7E) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_punct : int ($code_point : int) {
    
    if (($code_point >= 0x21 && $code_point <= 0x2F) || ($code_point >= 0x3A && $code_point <= 0x40) || ($code_point >= 0x5B && $code_point <= 0x60) || ($code_point >= 0x7B && $code_point <= 0x7E)) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_space : int ($code_point : int) {
    
    if (($code_point >= 0x09 && $code_point <= 0x0D) || $code_point == 0x20) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  private static method is_unicode_scalar_value : int ($code_point: int) {
    my $is_unicode_scalar_value = 0;
    # The range of Unicde code points
    if ($code_point >= 0 && $code_point <= 0x10FFFF) {
      # Not surrogate code points
      unless ($code_point >= 0xD800 && $code_point <= 0xDFFF) {
        $is_unicode_scalar_value = 1;
      }
    }
    
    return $is_unicode_scalar_value;
  }
  
  static method is_upper : int ($code_point : int) {
    
    if ($code_point >= 'A' && $code_point <= 'Z') {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  static method is_xdigit : int ($code_point : int) {
    
    if (($code_point >= 'A' && $code_point <= 'F') || ($code_point >= 'a' && $code_point <= 'f') || ($code_point >= '0' && $code_point <= '9')) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  precompile static method join : string ($separator : string, $strings : string[]) {
    unless ($separator) {
      die "The \$separator must be defined.";
    }
    
    unless ($strings) {
      die "The \$strings must be defined.";
    }
    
    my $join_buffer = StringBuffer->new;
    
    for (my $i = 0; $i < @$strings; $i++) {
      my $string = $strings->[$i];
      if ($string) {
        $join_buffer->push($string);
      }
      else {
        $join_buffer->push("");
      }
      if ($i != @$strings - 1) {
        $join_buffer->push($separator);
      }
    }
    
    my $join = $join_buffer->to_string;
    
    return $join;
  }
  
  static method labs : long ($value : long) {
    
    my $labs = 0L;
    if ($value > 0) {
      $labs = $value;
    }
    else {
      $labs = -$value;
    }
    return $labs;
  }
  
  precompile static method lc : string ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    my $new_string = (mutable string)new_string_len($length);
    for (my $i = 0; $i < $length; $i++) {
      my $char = $string->[$i];
      if ($char >= 'A' && $char <= 'Z') {
        $new_string->[$i] = (byte)($string->[$i] + 32);
      }
      else {
        $new_string->[$i] = $string->[$i];
      }
    }
    return $new_string;
  }
  
  static method lcfirst : string ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    my $new_string = (mutable string)new_string_len($length);
    if ($length > 0) {
      my $char = $string->[0];
      if ($char >= 'A' && $char <= 'Z') {
        $new_string->[0] = (byte)($char + 32);
      }
      else {
        $new_string->[0] = $char;
      }
    }
    Fn->memcpy($new_string, 1, $string, 1, $length - 1);
    
    return $new_string;
  }
  
  static method look_code_point : int ($string : string, $offset_ref : int*) {
    my $save_offset = $$offset_ref;
    my $code_point = &get_code_point($string, $offset_ref);
    $$offset_ref = $save_offset;
    return $code_point;
  }
  native static method memcpy : void ($dest : object, $dest_offset : int, $source : object, $source_offset : int, $length : int);
  
  native static method memmove : void ($dest : object, $dest_offset : int, $source : object, $source_offset : int, $length : int);
  
  static method ord : int ($string : string) {
    my $offset = 0;
    my $code_point = &get_code_point($string, \$offset);
    
    return $code_point;
  }
  
  precompile static method powi : int ($base : int, $exponant : int) {
    
    unless ($exponant >= 0) {
      die "The \$exponant number must be greater than or equal to 0.";
    }
    
    if ($base == 0) {
      unless ($exponant != 0) {
        die "If the \$base number is 0, the \$exponant number cannnot be 0.";
      }
    }
    
    my $ret = 1;
    for (my $i = 0; $i < $exponant; $i++) {
      $ret = $ret * $base;
    }
    
    return $ret;
  }
  
  precompile static method powl : long ($base : long, $exponant : long) {
    unless ($exponant >= 0) {
      die "The \$exponant number must be greater than or equal to 0.";
    }
    
    if ($base == 0) {
      unless ($exponant != 0) {
        die "If the \$base number is 0, the \$exponant number cannnot be 0.";
      }
    }
    
    my $ret = 1L;
    for (my $i = 0; $i < $exponant; $i++) {
      $ret = $ret * $base;
    }
    
    return $ret;
  }
  
  static method rand : double ($seed : int*, $max : int = 1) {
    
    # 0 <= random_number < 1
    my $random_number = (double)&crand($seed) / ((double)&RAND_MAX() + 1);
    
    $random_number *= $max;
    
    return $random_number;
  }
  
  precompile static method repeat : string ($string : string, $count : int) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($count >= 0) {
      die "The \$repeat count must be greater than or equal to 0.";
    }
    
    my $buffer = StringBuffer->new;
    for (my $i = 0; $i < $count; $i++) {
      $buffer->push($string);
    }
    my $repeat_string = $buffer->to_string;
    
    return $repeat_string;
  }

  precompile static method replace_chars : void ($string : mutable string, $from_ch : int, $to_ch : int) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $string_length = length $string;
    for (my $i = 0; $i < $string_length; $i++) {
      if ($string->[$i] == $from_ch) {
        $string->[$i] = (byte)$to_ch;
      }
    }
  }

  precompile static method rindex : int ($string : string, $substring : string, $end : int = -1, $begin : int = 0) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($substring) {
      die "The \$substring must be defined.";
    }
    
    unless ($begin >= 0 && $begin <= length $string) {
      die "The \$begin must be between 0 and the length of the \$string.";
    }
    
    my $string_length = length $string;
    
    if ($end < 0) {
      $end = $string_length;
    }
    
    unless ($end <= length $string) {
      die "The \$end must be less than or equal to the length of the \$string.";
    }
    
    unless ($end >= $begin) {
      die "The \$end must be greater than or equal to \$begin.";
    }
    
    my $substring_length = length $substring;
    
    if ($substring_length == 0) {
      return $end;
    }
    
    if ($end == $string_length) {
      $end--;
    }
    
    my $match_chars_count = 0;
    for (my $i = $end; $i >= $begin; $i--) {
      my $match = 1;
      for (my $k = 0; $k < $substring_length; $k++) {
        if ($i + $k > $end) {
          $match = 0;
          last;
        }
        
        unless ($string->[$i + $k] == $substring->[$k]) {
          $match = 0;
          last;
        }
      }
      
      if ($match) {
        return $i;
      }
    }
    
    return -1;
  }

  native static method sizeof_native_int : int ();
  
  native static method sizeof_native_pointer : int ();

  native static method shorten : void ($string : mutable string, $length : int);

  precompile static method shorten_null_char : void ($string : mutable string) {
    
    if (!$string) {
      die "The \$string must be defined.";
    }
    
    my $null_char_offset = -1;
    for (my $i = 0; $i < length $string; $i++) {
      my $char = $string->[$i];
      if ($char == '\0') {
        $null_char_offset = $i;
        last;
      }
    }
    
    if ($null_char_offset >= 0) {
      Fn->shorten($string, $null_char_offset);
    }
  }
  
  precompile static method split : string[] ($separator : string, $string : string, $limit : int = 0) {
    unless ($separator) {
      die "The \$separator must be defined.";
    }
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $string_length = length $string;
    my $separator_length = length $separator;
    
    unless ($separator_length > 0) {
      die "The length of the \$separator must be greater than 0.";
    }
    
    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 $match_offset = &index($string, $separator, $offset);
      my $match = $match_offset >= 0;
      if ($match) {
        $match_count++;
        
        my $part = &substr($string, $offset, $match_offset - $offset);
        $parts_list->push($part);
        my $match_legnth = $separator_length;
        $offset = $match_offset + $match_legnth;
      }
    }
    if ($offset == $string_length) {
      $parts_list->push("");
    }
    else {
      my $part = &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;
  }
  
  static method substr : string ($string : string, $offset : int, $length : int = -1, $replacement : string = undef) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The \$offset must be greater than or equal to 0.";
    }
    
    my $string_length = length $string;
    if ($length < 0) {
      $length = $string_length - $offset;
    }
    
    unless ($offset + $length <= $string_length) {
      die "The \$offset + the \$length must be less than or equal to the length of the \$string.";
    }
    
    my $substring = (string)undef;
    if ($replacement) {
      my $replacement_length = length $replacement;
      $substring = Fn->substr($string, 0, $offset) . $replacement . Fn->substr($string, $offset + $length);
    }
    else {
      $substring = (mutable string)new_string_len($length);
      Fn->memcpy($substring, 0, $string, $offset, $length);
    }
    
    return $substring;
  }

  precompile static method to_code_points : int[] ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $string_length = length $string;
    my $offset = 0;
    my $utf8_length = 0;
    my $code_points_list = IntList->new;
    while ($offset < $string_length) {
      my $code_point = &get_code_point($string, \$offset);
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The \$string contains a invalid Unicode code point.";
        }
        else {
          die "An unexpected exception is thrown.";
        }
      }
      $code_points_list->push($code_point);
    }
    
    my $code_points = $code_points_list->to_array;
    
    return $code_points;
  }
  
  native static method to_double : double ($string : string);
  
  native static method to_float : float ($string : string);
  
  static method to_int : int ($string : string) {
    return &to_int_with_base($string, 10);
  }
  
  native static method to_int_with_base : int ($string : string, $digit : int);
  
  static method to_long : long ($string : string) {
    return &to_long_with_base($string, 10);
  }
  native static method to_long_with_base : long ($string : string, $digit : int);
  
  static method to_lower : int ($code_point : int) {
    
    if ($code_point >= 'A' && $code_point <= 'Z') {
      $code_point = $code_point + 0x20;
    }
    return $code_point;
  }
  
  static method to_upper : int ($code_point : int) {
    
    if ($code_point >= 'a' && $code_point <= 'z') {
      $code_point = $code_point - 0x20;
    }
    return $code_point;
  }
  
  precompile static method to_utf8_chars : string[] ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $string_length = length $string;
    my $offset = 0;
    my $utf8_length = 0;
    my $utf8_chars_list = StringList->new;
    while ($offset < $string_length) {
      my $code_point = &get_code_point($string, \$offset);
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The \$string contains a invalid Unicode code point.";
        }
        else {
          die "An unexpected exception is thrown.";
        }
      }
      my $utf8_char = &chr($code_point);
    
      $utf8_chars_list->push($utf8_char);
    }
    
    my $utf8_chars = $utf8_chars_list->to_array;
    
    return $utf8_chars;
  }
  
  precompile static method tr : string ($string : string, $pattern : string, $replace : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($pattern) {
      die "The \$pattern must be defined.";
    }
    
    unless ($replace) {
      die "The \$replace must be defined.";
    }
    
    my $replace_length = length $pattern;
    
    my $pattern_range = &_parse_range($pattern, "$pattern");
    
    my $replace_range = &_parse_range($replace, "$replace");
    
    my $string_length = length $string;
    my $offset = 0;
    my $buffer = StringBuffer->new;
    while ($offset < $string_length) {
      
      my $code_point = &get_code_point($string, \$offset);
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The \$string contains a invalid Unicode code point.";
        }
        else {
          die "An unexpected exception is thrown.";
        }
      }
      
      my $pattern_offset = 0;
      my $match = 0;
      my $replace_pos = 0;
      my $match_pattern_index = -1;
      my $match_code_point_offset = -1;
      
      my $min_code_point = $pattern_range->[0];
      my $max_code_point = $pattern_range->[1];
      
      if ($code_point >= $min_code_point && $code_point <= $max_code_point) {
        $match = 1;
        $match_code_point_offset = $code_point - $min_code_point;
      }
      
      if ($match) {
        my $replace_code_point = $replace_range->[0] + $match_code_point_offset;
        my $char = &chr($replace_code_point);
        $buffer->push($char);
      }
      else {
        my $char = &chr($code_point);
        $buffer->push($char);
      }
    }
    
    my $ret = $buffer->to_string;
    
    return $ret;
  }
  
  precompile static method trim : string ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    my $begin_index = -1;
    my $end_index = -1;
    
    for (my $i = 0; $i < $length; $i++) {
      if ($begin_index == -1) {
        if (&is_space($string->[$i])) {
          # Skip
        }
        else {
          $begin_index = $i;
          last;
        }
      }
    }
    
    for (my $i = $length - 1; $i >= 0; $i--) {
      if ($end_index == -1) {
        if (&is_space($string->[$i])) {
          # Skip
        }
        else {
          $end_index = $i;
          last;
        }
      }
    }
    
    my $trimed_string : string;
    if ($begin_index == -1 && $end_index == -1) {
      return "";
    }
    elsif ($end_index == -1) {
      $trimed_string = &substr($string, $begin_index, $length - $begin_index);
    }
    elsif ($end_index == -1) {
      $trimed_string = &substr($string, 0, $end_index + 1);
    }
    else {
      $trimed_string = &substr($string, $begin_index, $end_index - $begin_index + 1);
    }
    
    return $trimed_string;
  }
  
  precompile static method uc : string ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    my $new_string = (mutable string)new_string_len($length);
    for (my $i = 0; $i < $length; $i++) {
      my $char = $string->[$i];
      if ($char >= 'a' && $char <= 'z') {
        $new_string->[$i] = (byte)($string->[$i] - 32);
      }
      else {
        $new_string->[$i] = $string->[$i];
      }
    }
    return $new_string;
  }
  
  static method ucfirst : string ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $length = length $string;
    my $new_string = (mutable string)new_string_len($length);
    if ($length > 0) {
      my $char = $string->[0];
      if ($char >= 'a' && $char <= 'z') {
        $new_string->[0] = (byte)($char - 32);
      }
      else {
        $new_string->[0] = $char;
      }
    }
    Fn->memcpy($new_string, 1, $string, 1, $length - 1);
    
    return $new_string;
  }
  
  precompile static method utf8_length : int ($string : string) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    my $string_length = length $string;
    my $offset = 0;
    my $utf8_length = 0;
    while ($offset < $string_length) {
      my $code_point = &get_code_point($string, \$offset);
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The \$string contains a invalid Unicode code point.";
        }
        else {
          die "An unexpected exception is thrown.";
        }
      }
      $utf8_length++;
    }
    
    return $utf8_length;
  }
  

  precompile static method utf8_substr : string ($string : string, $utf8_offset : int, $utf8_length : int = -1) {
    
    unless ($string) {
      die "The \$string must be defined.";
    }
    
    unless ($utf8_offset >= 0) {
      die "The \$utf8_offset must be greater than or equal to 0.";
    }
    
    my $string_length = length $string;
    my $offset = 0;
    my $current_utf8_offset = 0;
    my $buffer = StringBuffer->new;
    while ($offset < $string_length) {
      my $code_point = &get_code_point($string, \$offset);
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The \$string contains a invalid Unicode code point.";
        }
        else {
          die "An unexpected exception is thrown.";
        }
      }
      
      # -1:before, 0:range, 1:after
      
      my $range = 0;
      if ($current_utf8_offset < $utf8_offset) {
        $range = -1;
      }
      else {
        if ($utf8_length < 0) {
          $range = 0;
        }
        else {
          if ($current_utf8_offset < $utf8_offset + $utf8_length) {
            $range = 0;
          }
          else {
            $range = 1;
          }
        }
      }
      
      if ($range == 0) {
        my $utf8_char = &chr($code_point);
        $buffer->push($utf8_char);
      }
      elsif ($range == 1) {
        last;
      }
      
      $current_utf8_offset++;
    }
    
    if ($utf8_length >= 0) {
      unless ($utf8_offset + $utf8_length <= $current_utf8_offset) {
        die "The \$utf8_offset + the \$utf8_length must be less than or equal to the UTF-8 length of the \$string.";
      }
    }
    
    my $substring = $buffer->to_string;
    
    return $substring;
  }

  native static method _chr_native : string ($uchar : int);
  
  private precompile static method _parse_range : int[] ($range_format : string, $arg_name : string) {
    my $range = IntList->new;
    
    my $range_format_length = length $range_format;
    my $offset = 0;
    my $min_code_point = -1;
    my $max_code_point = -1;
    
    my $code_points_index = 0;
    while ($offset < $range_format_length) {
      
      my $code_point = &get_code_point($range_format, \$offset);
      
      if ($code_point < 0) {
        if ($code_point == &GET_CODE_POINT_ERROR_INVALID_UTF8) {
          die "The range format of the $arg_name cannnot be contain a invalid Unicode code point.";
        }
        else {
          die "Calling the get_code_point method in the Fn class to the $arg_name returns an error code: $code_point.";
        }
      }
      
      if ($code_points_index == 0) {
        $min_code_point = $code_point;
      }
      elsif ($code_points_index == 1) {
        unless ($code_point == '-') {
          die "The second character ot the range format of the $arg_name must be \"-\".";
        }
      }
      elsif ($code_points_index == 2) {
        $max_code_point = $code_point;
      }
      $code_points_index++;
    }
    my $code_points_length = $code_points_index;
    
    unless ($code_points_length == 3) {
      if ($code_points_length == 1) {
        $max_code_point = $min_code_point;
      }
      if ($max_code_point < 0) {
        die "The range format of the $arg_name must be 1 or 3 characters.";
      }
    }
    
    unless ($min_code_point <= $max_code_point) {
      die "The code point of the ending character in the $arg_name must be greater than or equal to the code point of the begining caharater.";
    }
    
    return [$min_code_point, $max_code_point];
  }
  
  precompile static method merge_options : object[] ($options1 : object[], $options2 : object[]) {
    unless ($options1) {
      die "The \$options1 must be defined.";
    }
    
    unless ($options2) {
      die "The \$options2 must be defined.";
    }
    
    my $options1_length = @$options1;
    unless ($options1_length % 2 == 0) {
      die "The length of the \$options1 must be an even number.";
    }
    
    my $options2_length = @$options2;
    unless ($options2_length % 2 == 0) {
      die "The length of the \$options2 must be an even number.";
    }
    
    my $merged_options_h = Hash->new;
    
    for (my $i = 0; $i < $options1_length; $i += 2) {
      unless ($options1->[$i]) {
        die "The key of the \$options1 must be defined.";
      }
      
      unless ($options1->[$i] isa string) {
        die "The key of the \$options1 must be the string type.";
      }
      my $name = (string)$options1->[$i];
      my $value = $options1->[$i + 1];
      $merged_options_h->set($name, $value);
    }
    
    for (my $i = 0; $i < $options2_length; $i += 2) {
      unless ($options2->[$i]) {
        die "The key of the \$options2 must be defined.";
      }
      
      unless ($options2->[$i] isa string) {
        die "The key of the \$options2 must be the string type.";
      }
      my $name = (string)$options2->[$i];
      my $value = $options2->[$i + 1];
      $merged_options_h->set($name, $value);
    }
    
    my $merged_options = $merged_options_h->to_array;
    
    return $merged_options;
  }
  
  native static method object_to_int : int ($object : object);
  
  native static method object_to_long : long ($object : object);
  
  native static method get_spvm_version_string : string ();
  
  native static method get_spvm_version_number : double ();
  
  native static method get_version_string : string ($basic_type_name : string);
  
  native static method get_version_number : double ($basic_type_name : string);
}