# Copyright (c) 2023 Yuki Kimoto
# MIT License

class Fn {
  version_from SPVM;

  use StringBuffer;
  use StringList;
  use IntList;
  use Hash;
  use Sort;
  use List;
  use Scope::Guard;
  use Callback;
  use Format;
  use Error::Unicode::InvalidUTF8;
  use Callback::Grep;
  use Callback::Map;
  use Callback::MapExpand;
  use Cloneable;
  use Packer;
  use Fn::Resource;
  
  # Do not use Native and Native::XXX classes because dependencies can have serious negative effects.
  
  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 \$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 \$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, $begin : int = 0, $end : int = -1) {
    return &index($string, $substring, $begin, $end) >= 0;
  }
  
  static method copy_string : string ($string : string) {
    return copy $string;
  }
  
  native static method crand : int ($seed_ref : 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 \$string1 must be defined.";
    }
    
    unless ($string2) {
      die "The string2 \$string2 must be defined.";
    }
    
    unless ($string1_offset >= 0) {
      die "The string1 offset \$string1_offset must be greater than or equal to 0.";
    }
    
    unless ($string2_offset >= 0) {
      die "The string2 offset \$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 \$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 \$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 \$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 \$string must be defined.";
    }
    
    unless ($substring) {
      die "The substring \$substring must be defined.";
    }
    
    unless ($begin >= 0 && $begin <= length $string) {
      die "The begin \$begin must be between 0 and the length of the string \$string.";
    }
    
    my $string_length = length $string;
    
    if ($end < 0) {
      $end = $string_length;
    }
    
    unless ($end <= length $string) {
      die "The end \$end must be less than or equal to the length of the string \$string.";
    }
    
    unless ($end >= $begin) {
      die "The end \$end must be greater than or equal to the begin \$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 \$string must be defined.";
    }
    my $string_length = length $string;
    
    if ($length < 0) {
      $length = $string_length - $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.";
    }
    
    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 \$separator must be defined.";
    }
    
    unless ($strings) {
      die "The strings \$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 \$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 \$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 exponent number \$exponant must be greater than or equal to 0.";
    }
    
    if ($base == 0) {
      unless ($exponant != 0) {
        die "If the base number \$base is 0, the exponent number \$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 exponent number \$exponant number must be greater than or equal to 0.";
    }
    
    if ($base == 0) {
      unless ($exponant != 0) {
        die "If the base number \$base number is 0, the exponent number \$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) {
    
    unless ($max > 0) {
      die "The max number \$max must be greater than 0.";
    }
    
    # 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 \$string must be defined.";
    }
    
    unless ($count >= 0) {
      die "The repeat count \$count must be a non-negative integer.";
    }
    
    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 \$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 \$string must be defined.";
    }
    
    unless ($substring) {
      die "The substring \$substring must be defined.";
    }
    
    unless ($begin >= 0 && $begin <= length $string) {
      die "The begin \$begin must be between 0 and the length of the string \$string.";
    }
    
    my $string_length = length $string;
    
    if ($end < 0) {
      $end = $string_length;
    }
    
    unless ($end <= length $string) {
      die "The end \$end must be less than or equal to the length of the string \$string.";
    }
    
    unless ($end >= $begin) {
      die "The end \$end must be greater than or equal to the begin \$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 \$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 \$separator must be defined.";
    }
    
    unless ($string) {
      die "The string \$string must be defined.";
    }
    
    my $string_length = length $string;
    my $separator_length = length $separator;
    
    unless ($separator_length > 0) {
      die "The length of the separator \$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 \$string must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$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 \$offset + the length \$length must be less than or equal to the length of the string \$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 \$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);
      $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 \$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);
      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 \$string must be defined.";
    }
    
    unless ($pattern) {
      die "The pattern \$pattern must be defined.";
    }
    
    unless ($replace) {
      die "The replace \$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);
      
      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 \$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 \$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 \$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 \$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);
      $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 \$string must be defined.";
    }
    
    unless ($utf8_offset >= 0) {
      die "The offset \$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);
      
      # -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 offset \$utf8_offset + the length \$utf8_length must be less than or equal to the UTF-8 length of the string \$string.";
      }
    }
    
    my $substring = $buffer->to_string;
    
    return $substring;
  }
  
  static method defer : Scope::Guard ($callback : Callback) {
    
    my $guard = Scope::Guard->new($callback);
    
    return $guard;
  }
  
  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_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 argument name $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 argument name $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 argument name $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) {
      $options1 = {};
    }
    
    unless ($options2) {
      $options2 = {};
    }
    
    my $options1_length = @$options1;
    unless ($options1_length % 2 == 0) {
      die "The length of the options1 \$options1 must be an even number.";
    }
    
    my $options2_length = @$options2;
    unless ($options2_length % 2 == 0) {
      die "The length of the options2 \$options2 must be an even number.";
    }
    
    my $merged_options = Array->merge_object($options1, $options2);
    
    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);
  
  native static method get_memory_blocks_count : int ();
  
  static method to_address : string ($object : object) {
    my $address = Format->sprintf("%p", [$object]);
    
    return $address;
  }
  
  static method check_option_names : void ($options : object[], $available_option_names : string[]) {
    
    unless ($options) {
      return;
    }
    
    my $available_option_names_h = Hash->new;
    for my $available_option_name (@$available_option_names) {
     $available_option_names_h->set_int($available_option_name, 1);
    }
    
    for (my $i = 0; $i < @$options; $i += 2) {
      my $option_name = (string)$options->[$i];
      
      unless ($available_option_names_h->get($option_name)) {
        die "The \"$option_name\" option is not available.";
      }
    }
  }
  
  native static method get_basic_type_id : int ($basic_type_name : string);
  
  precompile static method memset_char : void ($string : mutable string, $char : int, $offset : int = 0, $length : int = -1) {
    
    unless ($string) {
      die "The string \$string must be defined.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$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 \$offset + the length \$length must be less than or equal to the length of the string \$string.";
    }
    
    for (my $i = 0; $i < $length; $i++) {
      $string->[$offset + $i] = (byte)$char;
    }
  }
  
  static method or : object ($left : object, $right : object) {
    
    if ($left) {
      return $left;
    }
    else {
      return $right;
    }
  }
  
  
  static method if : object ($condition : int, $left : object, $right : object) {
    
    if ($condition) {
      return $left;
    }
    else {
      return $right;
    }
  }
  
  precompile static method grep : object[] ($callback : Callback::Grep, $array : object[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($callback) {
      die "The callback \$callback must be defined.";
    }
    
    my $proto_array = Array->new_proto($array, 0);
    
    my $list = List->new($proto_array);
    
    for my $element (@$array) {
      my $ok = $callback->($element);
      
      if ($ok) {
        $list->push($element);
      }
    }
    
    my $new_array = $list->to_array;
    
    return $new_array;
  }
  
  precompile static method map : object[] ($callback : Callback::Map, $array : object[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($callback) {
      die "The callback \$callback must be defined.";
    }
    
    my $proto_array = Array->new_proto($array, 0);
    
    my $list = List->new($proto_array);
    
    for my $element (@$array) {
      my $new_element = $callback->($element);
      
      $list->push($new_element);
    }
    
    my $new_array = $list->to_array;
    
    return $new_array;
  }
  
  precompile static method map_expand : object[] ($callback : Callback::MapExpand, $array : object[]) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless ($callback) {
      die "The callback \$callback must be defined.";
    }
    
    my $proto_array = Array->new_proto($array, 0);
    
    my $list = List->new($proto_array);
    
    for my $element (@$array) {
      my $new_elements = $callback->($element);
      
      for my $new_element (@$new_elements) {
        $list->push($new_element);
      }
    }
    
    my $new_array = $list->to_array;
    
    return $new_array;
  }
  
  native static method get_compile_type_name : string ($basic_type_name : string, $type_dimension : int, $type_flag : int);
  
  static method is_any_numeric_array : int ($object : object) {
    
    my $is_numeric_array = &is_numeric_array($object);
    
    my $is_mulnum_array = &is_mulnum_array($object);
    
    my $is_any_numeric_array = $is_numeric_array || $is_mulnum_array;
    
    return $is_any_numeric_array;
  }
  
  native static method array_length : int ($array : object);
  
  native static method get_elem_size : int ($array : object);
  
  static method get_elem_type_name : string ($array : object) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_array($array)) {
      die "The type of the array \$array must be an array type.";
    }
    
    my $type_name = type_name $array;
    
    my $elem_type_name = Fn->substr($type_name, 0, length $type_name - 2);
    
    return $elem_type_name;
  }
  
  native static method print_stderr : void ($string : string);
  
  native static method say_stderr : void ($string : string);
  
  native static method memcmp : int ($data1 : object, $data1_offset : int, $data2 : object, $data2_offset : int, $length : int);
  
  static method reverse_inplace : void ($array_or_string : object) {
    
    unless ($array_or_string) {
      die "\$array_or_string must be defined.";
    }
    
    unless (&is_array($array_or_string ) || $array_or_string  is_type string) {
      die "The type of \$array_or_string must be an array type or string type.";
    }
    
    if ($array_or_string is_type string) {
      
      my $string = (mutable string)$array_or_string;
      
      if (is_read_only $string) {
        die "The string \$array_or_string must not be read-only.";
      }
      
      my $length = length $string;
      
      for (my $i = 0; $i < $length / 2; $i++) {
        my $temp = $string->[$i];
        $string->[$i] = $string->[$length - $i - 1];
        $string->[$length - $i - 1] = $temp;
      }
    }
    elsif (&is_object_array($array_or_string)) {
      
      my $object_array = (object[])$array_or_string;
      
      my $length = @$object_array;
      
      for (my $i = 0; $i < $length / 2; $i++) {
        my $temp = $object_array->[$i];
        $object_array->[$i] = $object_array->[$length - $i - 1];
        $object_array->[$length - $i - 1] = $temp;
      }
    }
    elsif (&is_any_numeric_array($array_or_string) || $array_or_string is_type string) {
      
      my $array = $array_or_string;
      
      my $length = &array_length($array);
      
      my $elem_size = &get_elem_size($array);
      
      my $temp = new byte[$elem_size];
      
      for (my $i = 0; $i < $length / 2; $i++) {
        
        &memcpy($temp, 0, $array, $elem_size * $i, $elem_size);
        
        &memcpy($array, $elem_size * $i, $array, $elem_size * ($length - $i - 1), $elem_size);
        
        &memcpy($array, $elem_size * ($length - $i - 1), $temp, 0, $elem_size);
      }
    }
    else {
      die "[Unpexpected Error]Invalid array type.";
    }
  }
  
  static method is_string_array : int ($object : object) {
    
    my $is_string_array = 0;
    
    if ($object) {
      my $type_name = type_name $object;
      
      if ($type_name eq "string[]") {
        $is_string_array = 1;
      }
    }
    
    return $is_string_array;
  }
  
  static method length : int ($array_or_string : object) {
    
    unless ($array_or_string) {
      die "\$array_or_string must be defined.";
    }
    
    my $length = -1;
    if (&is_array($array_or_string)) {
      $length = &array_length($array_or_string);
    }
    elsif ($array_or_string is_type string) {
      $length = length (string)$array_or_string;
    }
    else {
      die "The type of \$array_or_string must be an array type or string type.";
    }
    
    return $length;
  }
  
  static method get_elem_or_char_size : int ($array_or_string : object) {
    
    unless ($array_or_string) {
      die "\$array_or_string must be defined.";
    }
    
    my $size = -1;
    if (&is_array($array_or_string)) {
      $size = &get_elem_size($array_or_string);
    }
    elsif ($array_or_string is_type string) {
      $size = 1;
    }
    else {
      die "The type of \$array_or_string must be an array type or string type.";
    }
    
    return $size;
  }
  
  static method copy : object ($array_or_string : object, $shallow : int = 0) {
    
    unless ($array_or_string) {
      return undef;
    }
    
    my $copy = (object)undef;
    if (&is_string_array($array_or_string)) {
      if ($shallow) {
        $copy = Array->copy_string_address((string[])$array_or_string);
      }
      else {
        $copy = Array->copy_string((string[])$array_or_string);
      }
    }
    elsif (&is_object_array($array_or_string)) {
      if ($shallow) {
        $copy = Array->copy_object_address((object[])$array_or_string);
      }
      else {
        $copy = Array->copy_object((object[])$array_or_string, Cloner->default_cloner);
      }
    }
    elsif (&is_any_numeric_array($array_or_string)) {
      $copy = Array->copy_any_numeric($array_or_string);
    }
    elsif ($array_or_string is_type string) {
      $copy = copy (string)$array_or_string;
    }
    else {
      die "The type of \$array_or_string must be an array type or string type.";
    }
    
    return $copy;
  }
  
  static method reverse : object ($array_or_string : object) {
    
    my $ret = Fn->copy($array_or_string);
    
    &reverse_inplace($ret);
    
    return $ret;
  }
  
  static method slice : object ($array : object, $offset : int, $length :int) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_any_numeric_array($array) || Fn->is_object_array($array)) {
      die "The type of the array \$array must be an object array type, a numeric array type or a multi-numeric array type.";
    }
    
    my $sliced_array = Array->new_proto_any($array, $length);
    
    if (&is_object_array($array)) {
      Array->memcpy_object_address((object[])$sliced_array, 0, (object[])$array, $offset, $length);
    }
    elsif (&is_any_numeric_array($array)) {
      my $elem_size = Fn->get_elem_size($array);
      
      Fn->memcpy($sliced_array, 0, $array, $elem_size * $offset, $elem_size * $length);
    }
    else {
      die "[Unexpected Error]Invalid type";
    }
    
    return $sliced_array;
  }
  
  native static method system_is_little_endian : int ();
  
  static method sprintf : string ($format : string, $args : object[]) {
    return Format->sprintf($format, $args);
  }
  
  static method sort_asc : object ($array : object) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_numeric_array($array)) {
      die "The type of the array \$array must be a numeric array type.";
    }
    
    my $sorted_array = Fn->copy($array);
    
    if ($array is_type byte[]) {
      Sort->sort_byte_asc((byte[])$sorted_array);
    }
    elsif ($array is_type short[]) {
      Sort->sort_short_asc((short[])$sorted_array);
    }
    elsif ($array is_type int[]) {
      Sort->sort_int_asc((int[])$sorted_array);
    }
    elsif ($array is_type long[]) {
      Sort->sort_long_asc((long[])$sorted_array);
    }
    elsif ($array is_type float[]) {
      Sort->sort_float_asc((float[])$sorted_array);
    }
    elsif ($array is_type double[]) {
      Sort->sort_double_asc((double[])$sorted_array);
    }
    
    return $sorted_array;
  }
  
  static method sort_desc : object ($array : object) {
    
    unless ($array) {
      die "The array \$array must be defined.";
    }
    
    unless (Fn->is_numeric_array($array)) {
      die "The type of the array \$array must be a numeric array type.";
    }
    
    my $sorted_array = Fn->copy($array);
    
    if ($array is_type byte[]) {
      Sort->sort_byte_desc((byte[])$sorted_array);
    }
    elsif ($array is_type short[]) {
      Sort->sort_short_desc((short[])$sorted_array);
    }
    elsif ($array is_type int[]) {
      Sort->sort_int_desc((int[])$sorted_array);
    }
    elsif ($array is_type long[]) {
      Sort->sort_long_desc((long[])$sorted_array);
    }
    elsif ($array is_type float[]) {
      Sort->sort_float_desc((float[])$sorted_array);
    }
    elsif ($array is_type double[]) {
      Sort->sort_double_desc((double[])$sorted_array);
    }
    
    return $sorted_array;
  }
  
  static method sort : object[] ($array : object[], $comparator : Comparator) {
    
    my $sorted_array = Array->copy_object_address($array);
    
    Sort->sort_object($array, $comparator);
    
    return $sorted_array;
  }
  
  static method change_endian : void ($binary : mutable string, $size : int, $offset : int = 0) {
    
    unless ($binary) {
      die "The binary data \$binary must be defined.";
    }
    
    unless ($size > 0) {
      die "The byte size \$size must be greater than 0.";
    }
    
    unless ($offset >= 0) {
      die "The offset \$offset must be greater than or equal to 0.";
    }
    
    my $binary_length = length $binary;
    
    unless ($offset + $size <= $binary_length) {
      die "The byte size \$size must be less than or equal to the offset \$offset plus the length of the binary data \$binary.";
    }
    
    if ($size == 1) {
      return;
    }
    
    my $buffer = new_string_len $size;
    
    Fn->memcpy($buffer, 0, $binary, $offset, $size);
    
    Fn->reverse_inplace($buffer);
    
    Fn->memcpy($binary, $offset, $buffer, 0, $size);
  }
  
  static method big_endian_to_system_endian : void ($binary : mutable string, $size : int, $offset : int = 0) {
    
    if (Fn->system_is_little_endian) {
      &change_endian($binary, $size, $offset);
    }
    
  }
  
  static method system_endian_to_big_endian : void ($binary : mutable string, $size : int, $offset : int = 0) {
    
    if (Fn->system_is_little_endian) {
      &change_endian($binary, $size, $offset);
    }
    
  }
  
  static method little_endian_to_system_endian : void ($binary : mutable string, $size : int, $offset : int = 0) {
    
    unless (Fn->system_is_little_endian) {
      &change_endian($binary, $size, $offset);
    }
    
  }
  
  static method system_endian_to_little_endian : void ($binary : mutable string, $size : int, $offset : int = 0) {
    
    unless (Fn->system_is_little_endian) {
      &change_endian($binary, $size, $offset);
    }
    
  }
  
  static method pack : string ($template : string, $objects : object[]) {
    return Packer->new->pack($template, $objects);
  }
  
  static method unpack : object[] ($template : string, $binary : string) {
    return Packer->new->unpack($template, $binary);
  }
  
  native static method no_free : int ($object : object);
  
  native static method set_no_free : void ($object : object, $no_free : int);
  
  native static method get_pointer : Address ($object : object);
  
  native static method set_pointer : void ($object : object, $address : Address);
  
  native static method has_null_pointer : int ($object : object);
  
  native static method eq_pointer : int ($object1 : object, $object2 : object);
  
  native static method pointer_to_string : string ($object : object);
  
  native static method dump_object_internal : string ($object : object);
  
  native static method get_seed : int ();
  
  native static method set_seed : void ($seed : int);
  
  native static method seed_initialized : int ();
  
  native static method get_basic_type_name_in_version_from : string ($basic_type_name : string);
  
  native static method destroy_cache_class_vars : void ();
  
  static method destroy_runtime_permanent_vars : void () {
    
    &destroy_cache_class_vars;
    
    $@ = undef;
    
  }
  
}