class Fn : precompile {
  use Cloner;
  use EqualityChecker;
  use Comparator::Byte;
  use Comparator::Short;
  use Comparator::Int;
  use Comparator::Long;
  use Comparator::Float;
  use Comparator::Double;
  use Comparator::String;
  use Comparator;
  use Stringer;
  use Cloner;
  use EqualityChecker;
  use StringBuffer;
  use StringList;
  use Unicode;
  use Regex;
  
  native static method DBL_MAX : double ();
  native static method DBL_MIN : double ();
  native static method FLT_MAX : float ();
  native static method FLT_MIN : float();
  static method INT16_MAX : short () { return 32767; }
  static method INT16_MIN : short () { 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 : byte () { return 127; }
  static method INT8_MIN : byte () { return -128; }
  native static method RAND_MAX : int ();
  static method UINT16_MAX : short () { return (short)0xFFFF; }
  static method UINT32_MAX : int () { return 0xFFFFFFFF; }
  static method UINT64_MAX : long () { return 0xFFFFFFFFFFFFFFFFL; }
  static method UINT8_MAX : byte () { return (byte)0xFF; }

  static method abs : int ($x : int) {
    
    my $ret = 0;
    if ($x > 0) {
      $ret = $x;
    }
    else {
      $ret = -$x;
    }
    return $ret;
  }

  static method chomp : void ($string : mutable string) {
    
    unless ($string) {
      return;
    }

    my $length = length $string;
    if ($length > 0) {
      if ($string->[$length - 1] == '\n') {
        &shorten($string, $length - 1);
      }
    }
  }

  static method chompr : string ($string : string) {
    unless ($string) {
      return undef;
    }

    my $new_string : string;
    my $length = length $string;
    if ($length == 0) {
      $new_string = copy $string;
    }
    else {
      if ($string->[$length - 1] == '\n') {
        $new_string = &substr($string, 0, $length - 1);
      }
      else {
        $new_string = copy $string;
      }
    }

    return $new_string;
  }

  static method chr : string ($unicode_code_point : int) {
    
    my $is_unicode_scalar_value = Unicode->is_unicode_scalar_value($unicode_code_point);
    my $utf8_char = (string)undef;
    if ($is_unicode_scalar_value) {
      $utf8_char = Unicode->uchar_to_utf8($unicode_code_point);
    }

    return $utf8_char;
  }

  static method copy_array_byte : byte[] ($nums : byte[]) {
    return copy $nums;
  }

  static method copy_array_double : double[] ($nums : double[]) {
    return copy $nums;
  }

  static method copy_array_float : float[] ($nums : float[]) {
    return copy $nums;
  }

  static method copy_array_int : int[] ($nums : int[]) {
    return copy $nums;
  }

  static method copy_array_long : long[] ($nums : long[]) {
    return copy $nums;
  }

  static method copy_array_object : object[] ($objects : object[], $cloner : Cloner) {
    if ($objects == undef) {
      return undef;
    }
    
    my $length = @$objects;

    my $new_objects = new object[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_objects->[$i] = $cloner->($objects->[$i]);
    }

    return $new_objects;
  }

  static method copy_array_range_byte : byte[] ($nums : byte[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must be in the array range";
    }

    my $range = new byte[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_double : double[] ($nums : double[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be in the array range";
    }

    my $range = new double[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_float : float[] ($nums : float[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be in the array range";
    }

    my $range = new float[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_int : int[] ($nums : int[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must be in the array range";
    }

    my $range = new int[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_long : long[] ($nums : long[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be in the array range";
    }

    my $range = new long[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_object : object[] ($objects : object[], $offset : int, $length : int) {

    if ($objects == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$objects;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be in the array range";
    }

    my $range = Fn->new_array_proto($objects, $length);

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $objects->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_range_short : short[] ($nums : short[], $offset : int, $length : int) {

    if ($nums == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$nums;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must be in the array range";
    }

    my $range = new short[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $nums->[$i];
      $pos++;
    }

    return $range;
  }


  static method copy_array_range_string : string[] ($strings : string[], $offset : int, $length : int) {

    if ($strings == undef) {
      die "The argument array must be defined";
    }

    my $array_length = @$strings;

    if ($offset < 0 || $offset > $array_length - 1) {
      die "Offset must be in the array range";
    }

    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }

    if ($offset + $length > $array_length) {
      die "Offset + length must not be in the array range";
    }

    my $range = new string[$length];

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $range->[$pos] = $strings->[$i];
      $pos++;
    }

    return $range;
  }

  static method copy_array_short : short[] ($nums : short[]) {
    return copy $nums;
  }

  static method copy_array_string : string[] ($strings : string[]) {
    if ($strings == undef) {
      return undef;
    }
    
    my $length = @$strings;

    my $new_strings = new string[$length];

    for (my $i = 0; $i < $length; $i++) {
      $new_strings->[$i] = copy $strings->[$i];
    }

    return $new_strings;
  }
  
  static method copy_string : string ($string : string) {
    return copy $string;
  }

  native static method crand : int ($seed : int*);

  static method dump_array_byte : string ($nums : byte[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_double : string ($nums : double[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_float : string ($nums : float[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_int : string ($nums : int[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_long : string ($nums : long[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_object : string ($objects : object[], $stringer : Stringer) {
    
    if ($objects == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$objects; $i++) {
      my $string = $stringer->($objects->[$i]);
      $dump .= "  $string";
      if ($i != @$objects - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_short : string ($nums : short[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_string : string ($strings : string[]) {
    
    if ($strings == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$strings; $i++) {
      my $string = $strings->[$i];
      $dump .= "  $string";
      if ($i != @$strings - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_unsigned_byte : string ($nums : byte[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = Fn->sprintf("%u", (int)$nums->[$i] & 0xFF);
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_unsigned_int : string ($nums : int[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = Fn->sprintf("%u", $nums->[$i]);
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_unsigned_long : string ($nums : long[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = Fn->sprintf("%lu", $nums->[$i]);
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method dump_array_unsigned_short : string ($nums : short[]) {
    
    if ($nums == undef) {
      return undef;
    }
    
    my $dump = "[\n";

    for (my $i = 0; $i < @$nums; $i++) {
      my $string = Fn->sprintf("%u", (int)$nums->[$i] & 0xFFFF);
      $dump .= "  $string";
      if ($i != @$nums - 1) {
        $dump .= ",\n";
      }
      else {
        $dump .= "\n";
      }
    }
    
    $dump .= "]";

    return $dump;
  }

  static method equals_array_byte : int ($nums1 : byte[], $nums2 : byte[]) {
    
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_double : int ($nums1 : double[], $nums2 : double[]) {
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_float : int ($nums1 : float[], $nums2 : float[]) {
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_int : int ($nums1 : int[], $nums2 : int[]) {
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }
  static method equals_array_long : int ($nums1 : long[], $nums2 : long[]) {
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_object : int ($objects1 : object[], $objects2 : object[], $equality_checker : EqualityChecker) {
    if ($objects1 == undef && $objects2 == undef) {
      return 1;
    }
    elsif ($objects1 != undef && $objects2 == undef) {
      return 0;
    }
    elsif ($objects1 == undef && $objects2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$objects1 == @$objects2) {
      for (my $i = 0; $i < @$objects1; $i++) {
        if (!$equality_checker->($objects1->[$i], $objects2->[$i])) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_short : int ($nums1 : short[], $nums2 : short[]) {
    if ($nums1 == undef && $nums2 == undef) {
      return 1;
    }
    elsif ($nums1 != undef && $nums2 == undef) {
      return 0;
    }
    elsif ($nums1 == undef && $nums2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$nums1 == @$nums2) {
      for (my $i = 0; $i < @$nums1; $i++) {
        if ($nums1->[$i] != $nums2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method equals_array_string : int ($strings1 : string[], $strings2 : string[]) {
    if ($strings1 == undef && $strings2 == undef) {
      return 1;
    }
    elsif ($strings1 != undef && $strings2 == undef) {
      return 0;
    }
    elsif ($strings1 == undef && $strings2 != undef) {
      return 0;
    }
    
    my $is_equals = 1;
    if (@$strings1 == @$strings2) {
      for (my $i = 0; $i < @$strings1; $i++) {
        if ($strings1->[$i] ne $strings2->[$i]) {
          $is_equals = 0;
          last;
        }
      }
    }
    else {
      $is_equals = 0;
    }

    return $is_equals;
  }

  static method hex : int ($hex_string : string) {
    
    unless ($hex_string) {
      die "the hex string must be defined";
    }
    
    my $re = Regex->new("^([0-9a-fA-F]{1,8})$");
    
    my $hex_value = 0;
    my $digit = 0;
    my $value = 0;
    if ($re->match($hex_string, 0)) {
      my $length = length $hex_string;
      for (my $i = $length - 1; $i >= 0; $i--) {
        my $ascii_code = $hex_string->[$i];
        
        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 * Fn->powi(16, $digit);
        
        $digit += 1;
      }
    }
    else {
      die "the hex string must be a valid expression";
    }
    
    return $value;
  }

  static method index : int ($string : string, $sub_string : string, $start_pos : int) {
    return &index_len($string, $sub_string, $start_pos, length $string);
  }

  static method index_len : int ($string : string, $sub_string : string, $start_pos : int, $max_string_length : int) {
    my $sub_string_length = length $sub_string;
    
    if ($max_string_length > length $string) {
      $max_string_length = length $string;
    }

    if ($start_pos >= $max_string_length) {
      my $ret : int;
      if ($sub_string_length == 0) {
        $ret = $max_string_length;
      }
      else {
        $ret = -1;
      }
      return $ret;
    }
    if ($start_pos < 0) {
      $start_pos = 0;
    }
    if ($sub_string_length == 0) {
      return $start_pos;
    }

    my $first : byte  = $sub_string->[0];
    my $max : int = ($max_string_length - $sub_string_length);

    for (my $i = $start_pos; $i <= $max; $i++) {
      if ($string->[$i] != $first) {
        while (++$i <= $max && $string->[$i] != $first) {}
      }

      if ($i <= $max) {
        my $j = $i + 1;
        my $end = $j + $sub_string_length - 1;
        for (my $k = 01; $j < $end && $string->[$j] == $sub_string->[$k]; ($j++, $k++)) {}

        if ($j == $end) {
          return $i;
        }
      }
    }
    return -1;
  }

  # The argument type of following charcter checking functions are int, not byte. This is intentinal.
  # ASCII code is represented by byte type.
  # On the other hand, Unicode Code Point can be represented by int tyep.
  # Unicode Code Point is upper compatibility of ASCII code.
  # So the following character check functions receive int type argument.
  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) {

    if ($code_point >= ' ' ||  $code_point <= '\t') {
      return 1;
    }
    else {
      return 0;
    }
  }

  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_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 \s
  static method is_perl_space : int ($code_point : int) {
    my $ispspace = 0;
    switch ($code_point) {
      case ' ':
      case '\r':
      case '\n':
      case '\t':
      case '\f':
      {
        $ispspace = 1;
        break;
      }
    }
    return $ispspace;
  }

  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;
  }

  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;
    }
  }

  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;
    }
  }

  static method join : string ($sep : string, $strings : string[]) {
    my $join = "";

    for (my $i = 0; $i < @$strings; $i++) {
      my $string = $strings->[$i];
      $join .= $string;
      if ($i != @$strings - 1) {
        $join .= $sep;
      }
    }

    return $join;
  }

  static method labs : long ($x : long) {
    
    my $ret = 0L;
    if ($x > 0) {
      $ret = $x;
    }
    else {
      $ret = -$x;
    }
    return $ret;
  }

  static method lc : string ($string : string) {

    unless ($string) { die "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 (string)$new_string;
  }

  static method lcfirst : string ($string : string) {

    unless ($string) { die "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;
      }
    }
    for (my $i = 1; $i < $length; ++$i) {
      $new_string->[$i] = $string->[$i];
    }
    return (string)$new_string;
  }

  static method match : Regex ($string : string, $regex_string : string, $regex_options_opt : string[]...) {
    
    my $regex_options = (string)undef;
    if (@$regex_options_opt > 0) {
      $regex_options = $regex_options_opt->[0];
    }
    
    my $regex = (Regex)undef;
    if ($regex_options) {
      $regex = Regex->new_with_options($regex_string, $regex_options);
    }
    else {
      $regex = Regex->new($regex_string);
    }
        
    my $match = $regex->match($string, 0);
    
    if ($match) {
      return $regex;
    }
    else {
      return undef;
    }
  }
  
  native static method memcpy : void ($dest : object, $dest_byte_offset : int, $source : object, $source_byte_offset : int, $byte_length : int);
  native static method memcpy_byte : void ($dest : byte[], $dest_offset : int, $source : byte[], $source_offset : int, $length : int);
  native static method memcpy_double : void ($dest : double[], $dest_offset : int, $source : double[], $source_offset : int, $length : int);
  native static method memcpy_float : void ($dest : float[], $dest_offset : int, $source : float[], $source_offset : int, $length : int);
  native static method memcpy_int : void ($dest : int[], $dest_offset : int, $source : int[], $source_offset : int, $length : int);
  native static method memcpy_long : void ($dest : long[], $dest_offset : int, $source : long[], $source_offset : int, $length : int);
  native static method memcpy_short : void ($dest : short[], $dest_offset : int, $source : short[], $source_offset : int, $length : int);

  native static method memmove : void ($dest : object, $dest_byte_offset : int, $source : object, $source_byte_offset : int, $byte_length : int);
  native static method memmove_byte : void ($dest : byte[], $dest_offset : int, $source : byte[], $source_offset : int, $length : int);
  native static method memmove_double : void ($dest : double[], $dest_offset : int, $source : double[], $source_offset : int, $length : int);
  native static method memmove_float : void ($dest : float[], $dest_offset : int, $source : float[], $source_offset : int, $length : int);
  native static method memmove_int : void ($dest : int[], $dest_offset : int, $source : int[], $source_offset : int, $length : int);
  native static method memmove_long : void ($dest : long[], $dest_offset : int, $source : long[], $source_offset : int, $length : int);
  native static method memmove_short : void ($dest : short[], $dest_offset : int, $source : short[], $source_offset : int, $length : int);

  native static method new_array_proto : object[] ($proto_array : object[], $length : int);

  static method ord : int ($utf8_character : string) {
    unless ($utf8_character) {
      return -1;
    }

    if (length $utf8_character == 0) {
      return -1;
    }
    
    my $offset = 0;
    my $unicode_code_point = Unicode->uchar($utf8_character, \$offset);
    
    return $unicode_code_point;
  }

  static method powi : int ($x : int, $y : int) {
    my $p = 1;
    
    for (my $i = 0; $i < $y; $i++) {
      $p = $p * $x;
    }
    
    return $p;
  }

  static method powl : long ($x : long, $y : long) {
    my $p = 1L;
    
    for (my $i = 0; $i < $y; $i++) {
      $p = $p * $x;
    }
    
    return $p;
  }

  static method rand : double ($seed : int*) {
    
    # 0 <= random_number < 1
    my $random_number = (double)Fn->crand($seed) / ((double)Fn->RAND_MAX() + 1);
    
    return $random_number;
  }

  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 more than 0";
    }
    
    my $buffer = StringBuffer->new;
    for (my $i = 0; $i < $count; $i++) {
      $buffer->push($string);
    }
    my $repeat_string = $buffer->to_string;
    
    return $repeat_string;
  }

  static method replace : string ($string : string, $regex_string : string, $replace_string_or_replacer : object, $regex_options_opt : string[]...) {

    my $regex_options = (string)undef;
    if (@$regex_options_opt > 0) {
      $regex_options = $regex_options_opt->[0];
    }
    
    my $regex = (Regex)undef;
    my $replace_all = 0;
    if ($regex_options) {
      $regex = Regex->new_with_options($regex_string, $regex_options);
      if (Fn->index($regex_options, "g", 0) > -1) {
        $replace_all = 1;
      }
    }
    else {
      $regex = Regex->new($regex_string);
    }
    
    my $replace_string = (string)undef;
    my $replacer = (Regex::Replacer)undef;
    if ($replace_string_or_replacer isa string) {
      $replace_string = (string)$replace_string_or_replacer;
    }
    elsif ($replace_string_or_replacer isa Regex::Replacer) {
      $replacer = (Regex::Replacer)$replace_string_or_replacer;
    }
    else {
      die "The sencond argument must be a object that type is the string type or Regex::Replacer";
    }
    
    my $result = (string)undef;
    if ($replace_all) {
      if ($replace_string) {
        $result = $regex->replace_all($string, 0, $replace_string);
      }
      else {
        $result = $regex->replace_all_cb($string, 0, $replacer);
      }
    }
    else {
      if ($replace_string) {
        $result = $regex->replace($string, 0, $replace_string);
      }
      else {
        $result = $regex->replace_cb($string, 0, $replacer);
      }
    }
    
    return $result;
  }
  
  static method rindex : int ($string : string, $sub_string : string, $start_pos : int) {
    return &rindex_len($string, $sub_string, $start_pos, length $string);
  }

  static method rindex_len : int ($string : string, $sub_string : string, $start_pos : int, $max_string_length : int) {
    my $sub_string_length = length $sub_string;

    if ($max_string_length > length $string) {
      $max_string_length = length $string;
    }

    my $sub_string_len = length $sub_string;
    if ($start_pos > $max_string_length - $sub_string_length) {
      $start_pos = $max_string_length - $sub_string_len;
    }
    for (my $i = $start_pos; $i >= 0; --$i) {
      my $match = 1;
      for (my $j = 0; $j < $sub_string_len; ++$j) {
        if ($string->[$i + $j] != $sub_string->[$j]) {
          $match = 0;
          last;
        }
      }
      if ($match) {
        return $i;
      }
    }
    return -1;
  }

  static method sort_byte : void ($nums : byte[], $offset : int, $length : int, $comparator : Comparator::Byte) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new byte[$length];
    Fn->_sort_byte_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }

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

  static method sort_double : void ($nums : double[], $offset : int, $length : int, $comparator : Comparator::Double) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new double[$length];
    Fn->_sort_double_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }

  static method sort_float : void ($nums : float[], $offset : int, $length : int, $comparator : Comparator::Float) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new float[$length];
    Fn->_sort_float_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }
  
  static method sort_int : void ($nums : int[], $offset : int, $length : int, $comparator : Comparator::Int) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new int[$length];
    Fn->_sort_int_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }
  
  static method sort_long : void ($nums : long[], $offset : int, $length : int, $comparator : Comparator::Long) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new long[$length];
    Fn->_sort_long_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }
  
  static method sort_object : void ($objects : object[], $offset : int, $length : int, $comparator : Comparator) {
    if ($objects == undef) {
      die "Array must be not undef";
    }
    
    my $objects_length = @$objects;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $objects_length) {
      die "Offset + Length must be in the array range";
    }
    
    my $b = Fn->new_array_proto($objects, $length);
    
    Fn->_sort_object_merge_sort($objects, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }

  static method sort_short : void ($nums : short[], $offset : int, $length : int, $comparator : Comparator::Short) {
    if ($nums == undef) {
      die "Array must be not undef";
    }
    
    my $nums_length = @$nums;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $nums_length) {
      die "Offset + Length must be in the array range";
    }
    
    if ($length == 0) {
      return;
    }
    
    my $b = new short[$length];
    Fn->_sort_short_merge_sort($nums, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }

  static method sort_string : void ($strings : string[], $offset : int, $length : int, $comparator : Comparator::String) {
    if ($strings == undef) {
      die "Array must be not undef";
    }
    
    my $strings_length = @$strings;
    
    if ($offset < 0) {
      die "Offset must be more than or equals to 0";
    }
    
    if ($length < 0) {
      die "Length must be more than or equals to 0";
    }
    
    if ($offset + $length > $strings_length) {
      die "Offset + Length must be in the array range";
    }
    
    my $b = new string[$length];
    Fn->_sort_string_merge_sort($strings, $b, $offset, $offset + $length - 1, $length - 1, $comparator);
  }

  static method split : string[] ($sep : string, $string : string) {
    my $string_length = length $string;
    my $sep_length = length $sep;


    if ($sep_length == 0) {
      die "Separator must not be empty string";
    }

    my $separated_strings_list = StringList->new_len(0);

    my $match_start = 0;
    my $string_base = 0;
    for (my $string_index = 0; $string_index < $string_length; $string_index++) {
      if ($string->[$string_index] == $sep->[0]) {
        $match_start = 1;
      }

      if ($match_start) {
        my $match = 1;
        for (my $sep_index = 0; $sep_index < $sep_length; $sep_index++) {
          if ($string_index + $sep_index > $string_length - 1) {
            $match = 0;
            last;
          }
          else {
            unless ($string->[$string_index + $sep_index] == $sep->[$sep_index]) {
              $match = 0;
              last;
            }
          }
        }
        if ($match) {
          my $separated_string = Fn->substr($string, $string_base, $string_index - $string_base);
          $separated_strings_list->push($separated_string);
          $string_base = $string_index + $sep_length;
          $string_index += $sep_length - 1;
        }
      }
    }
    if ($string_base == $string_length) {
      $separated_strings_list->push("");
    }
    else {
      my $separated_string = Fn->substr($string, $string_base, $string_length - $string_base);
      $separated_strings_list->push($separated_string);
    }

    my $separated_strings = $separated_strings_list->to_array;

    return $separated_strings;
  }

  static method sprintf : string ($format : string, $args : object[]...) {
    my $format_length = length $format;
    my $index = 0;

    my $buffer = StringBuffer->new;
    my $arg_count = 0;
    my $constant_string_length = 0;

    while ($index + $constant_string_length < $format_length) {

      if ($format->[$index + $constant_string_length] != '%') {
        # Read constant string
        ++$constant_string_length;
      }
      elsif ($index + $constant_string_length + 1 < $format_length &&
          $format->[$index + $constant_string_length + 1] == '%') {
        # Read %%
        ++$constant_string_length;

        # Add constant string
        if ($constant_string_length > 0) {
          my $format_part = Fn->substr($format, $index, $constant_string_length);
          
          $buffer->push($format_part);
          $index += $constant_string_length;
          $constant_string_length = 0;
        }

        # Skip second %
        ++$index;
      }
      elsif ($index + $constant_string_length + 1 >= $format_length) {
        die "Invalid conversion in sprintf: end of string";
      }
      else {
        # Add constant string
        if ($constant_string_length > 0) {
          my $format_part = Fn->substr($format, $index, $constant_string_length);
          $buffer->push($format_part);
          $index += $constant_string_length;
          $constant_string_length = 0;
        }

        # Read format string

        # Check the next element of @$args corresponding to the specifier
        unless ($arg_count < @$args) {
          die "Missing argument in sprintf";
        }

        # Read specifier %[flags][width][.precision][length]type

        my $specifier_base_index = $index;
        ++$index; # '%'

        # Read `flags`
        my $pad_char = ' ';
        my $plus_sign = 0;
        my $left_justified = 0;

        while ($index < $format_length) {
          my $flag = (int)($format->[$index]);
          switch($flag) {
            case '0': {
              ++$index;
              $pad_char = '0';
              break;
            }
            case '+': {
              ++$index;
              $plus_sign = 1;
              break;
            }
            case '-': {
              ++$index;
              $left_justified = 1;
              break;
            }
            default: {
              last;
              break;
            }
          }
        }
        
        if ($left_justified) {
          $pad_char = ' ';
        }

        # Width
        my $width = 0;
        while ($index < $format_length) {
          my $c = $format->[$index];
          if ($c < '0' || '9' < $c) {
            last;
          }
          $width = $width * 10 + $c - '0';
          ++$index;
        }

        # Precision
        my $precision = 0;
        my $has_precision = 0;
        if ($index < $format_length && $format->[$index] == '.') {
          ++$index;
          while ($index < $format_length) {
            my $c = $format->[$index];
            if ($c < '0' || '9' < $c) {
              last;
            }
            $has_precision = 1;
            $precision = $precision * 10 + $c - '0';
            ++$index;
          }
        }
        unless ($has_precision) {
          $precision = -1;
        }

        unless ($index < $format_length) {
          die "Invalid conversion in sprintf: \""
              . Fn->substr($format, $specifier_base_index, $index - $specifier_base_index) . "\"";
        }
        
        my $specifier_first_char = $format->[$index];
        $index++;
        
        my $specifier = (string)"";
        switch ((int) $specifier_first_char) {
          case 'c' : {
            $specifier = "c";
            break;
          }
          case 's' : {
            $specifier = "s";
            break;
          }
          case 'd' : {
            $specifier = "d";
            break;
          }
          case 'u' : {
            $specifier = "u";
            break;
          }
          case 'l' : {
            if ($index < length $format) {
              if ($format->[$index] == 'd') {
                $index++;
                $specifier = "ld";
              }
              elsif ($format->[$index] == 'u') {
                $index++;
                $specifier = "lu";
              }
              elsif ($format->[$index] == 'x') {
                $index++;
                $specifier = "lx";
              }
              elsif ($format->[$index] == 'X') {
                $index++;
                $specifier = "lX";
              }
            }
            break;
          }
          case 'f' : {
            $specifier = "f";
            break;
          }
          case 'g' : {
            $specifier = "g";
            break;
          }
          case 'U' : {
            $specifier = "U";
            break;
          }
          case 'x' : {
            $specifier = "x";
            break;
          }
          case 'X' : {
            $specifier = "X";
            break;
          }
          default: {

          }
        }
        
        if ($specifier eq "c") {
          my $arg_value : int;
          my $arg = $args->[$arg_count];
          if ($arg isa Byte) {
            $arg_value = $arg->(Byte)->value;
          }
          else {
            $arg_value = $arg->(Int)->value;
          }
          my $formatted_value = Fn->chr($arg_value);
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        elsif ($specifier eq "s") {
          my $arg = (string)$args->[$arg_count];
          my $formatted_value = $arg;
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        elsif ($specifier eq "d") {
          my $arg = (Int)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_d($arg);
          &_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
        }
        elsif ($specifier eq "u") {
          my $arg = (Int)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_u($arg);
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        elsif ($specifier eq "ld") {
          my $arg = (Long)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_ld($arg);
          &_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
        }
        elsif ($specifier eq "lu") {
          my $arg = (Long)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_lu($arg);
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        elsif ($specifier eq "f") {
          my $arg_value : double;
          my $arg = $args->[$arg_count];
          if ($arg isa Float) {
            $arg_value = $arg->(Float)->value;
          }
          else {
            $arg_value = $arg->(Double)->value;
          }
          my $formatted_value = &_native_snprintf_f($arg_value, $precision);
          &_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
        }
        elsif ($specifier eq "g") {
          my $arg_value : double;
          my $arg = $args->[$arg_count];
          if ($arg isa Float) {
            $arg_value = $arg->(Float)->value;
          }
          else {
            $arg_value = $arg->(Double)->value;
          }
          my $formatted_value = &_native_snprintf_g($arg_value, $precision);
          &_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
        }
        elsif ($specifier eq "x" || $specifier eq "X") {
          my $arg = (Int)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_x($arg);

          if ($specifier eq "X") {
            $formatted_value = Fn->uc($formatted_value);
          }
          
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        elsif ($specifier eq "lx" || $specifier eq "lX") {
          my $arg = (Long)$args->[$arg_count];
          my $formatted_value = &_native_snprintf_lx($arg);

          if ($specifier eq "lX") {
            $formatted_value = Fn->uc($formatted_value);
          }
          
          &_push_formatted_string_unsigned($buffer, $formatted_value, $width, $pad_char, $left_justified);
        }
        else {
          die "Invalid specifier \"%$specifier\"";
        }

        ++$arg_count;
      }
    }

    # Add constant string
    if ($constant_string_length > 0) {
      my $format_part = Fn->substr($format, $index, $constant_string_length);
      $buffer->push($format_part);
      $index += $constant_string_length;
      $constant_string_length = 0;
    }

    my $result = $buffer->to_string;
    return $result;
  }
  
  native static method _native_snprintf_d : string ($value : int);
  native static method _native_snprintf_ld : string ($value : long);
  native static method _native_snprintf_lu : string ($value : long);
  native static method _native_snprintf_u : string ($value : int);
  native static method _native_snprintf_x : string ($value : int);
  native static method _native_snprintf_lx : string ($value : long);
  native static method _native_snprintf_f : string ($value : double, $precision : int);
  native static method _native_snprintf_g : string ($value : double, $precision : int);
  
  static method _push_formatted_string_unsigned : void($buffer : StringBuffer, $formatted_value : string, $width : int, $pad_char : byte, $left_justified : int) {
    
    # "+" sign is always ignored.
    my $plus_sign = 0;
    
    &_push_formatted_string_signed($buffer, $formatted_value, $width, $pad_char, $left_justified, $plus_sign);
  }

  static method _push_formatted_string_signed : void($buffer : StringBuffer, $formatted_value : string, $width : int, $pad_char : byte, $left_justified : int, $plus_sign : int) {
    my $is_minus = 0;
    if ($formatted_value->[0] == '-') {
       $is_minus = 1;
    }
    
    my $space_count = $width - length $formatted_value;
    if (!$is_minus && $plus_sign) {
      --$space_count;
    }

    if ($left_justified) {
      if (!$is_minus && $plus_sign) {
        $buffer->push_char('+');
      }

      $buffer->push($formatted_value);

      if ($space_count > 0) {
        for (; $space_count > 0; --$space_count) {
          $buffer->push_char($pad_char);
        }
      }
    }
    else {
      if ($space_count > 0) {
        for (; $space_count > 0; --$space_count) {
          $buffer->push_char($pad_char);
        }
      }

      if (!$is_minus && $plus_sign) {
        $buffer->push_char('+');
      }

      $buffer->push($formatted_value);
    }
  }
  
  static method substr : string ($string : string, $offset : int, $length : int) {

    if ($string == undef) {
      die "String must be defined";
    }

    my $string_length = length $string;

    if ($offset < 0 || $offset > $string_length - 1) {
      die "Invalid offset $offset";
    }

    if ($length < 0) {
      die "Length must be non-zero value";
    }

    if ($offset + $length > $string_length) {
      die "Offset + length must not be over array length";
    }

    my $slice = (mutable string)new_string_len($length);

    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $string->[$i];
      $pos++;
    }

    return $slice;
  }

  native static method to_double : double ($string : string);

  native static method to_float : float ($string : string);

  static method to_int : int ($string : string) {
    return Fn->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 Fn->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;
  }

  static method trim_ascii_space : string ($string : string) {
    
    unless ($string) {
      return undef;
    }
    
    my $length = length $string;
    my $start_string_index = -1;
    my $end_string_index = -1;
    
    for (my $i = 0; $i < $length; $i++) {
      if ($start_string_index == -1) {
        if (Fn->is_space($string->[$i])) {
          # Skip
        }
        else {
          $start_string_index = $i;
          last;
        }
      }
    }

    for (my $i = $length - 1; $i >= 0; $i--) {
      if ($end_string_index == -1) {
        if (Fn->is_space($string->[$i])) {
          # Skip
        }
        else {
          $end_string_index = $i;
          last;
        }
      }
    }
    
    my $trimed_string : string;
    if ($start_string_index == -1 && $end_string_index == -1) {
      return "";
    }
    elsif ($end_string_index == -1) {
      $trimed_string = Fn->substr($string, $start_string_index, $length - $start_string_index);
    }
    elsif ($end_string_index == -1) {
      $trimed_string = Fn->substr($string, 0, $end_string_index + 1);
    }
    else {
      $trimed_string = Fn->substr($string, $start_string_index, $end_string_index - $start_string_index + 1);
    }
    
    return $trimed_string;
  }


  static method uc : string ($string : string) {

    unless ($string) { die "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 "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;
      }
    }
    for (my $i = 1; $i < $length; ++$i) {
      $new_string->[$i] = $string->[$i];
    }
    return $new_string;
  }


  native static method _snsprintf_double : string ($format : string, $value : double);

  private static method _sort_byte_merge : void($a : byte[], $b : byte[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Byte) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_byte_merge_sort : void($a : byte[], $b : byte[], $left : int, $right : int, $n : int, $comparator : Comparator::Byte){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_byte_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_byte_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_byte_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_short_merge : void($a : short[], $b : short[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Short) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_short_merge_sort : void($a : short[], $b : short[], $left : int, $right : int, $n : int, $comparator : Comparator::Short){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_short_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_short_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_short_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }


  private static method _sort_int_merge : void($a : int[], $b : int[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Int) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_int_merge_sort : void($a : int[], $b : int[], $left : int, $right : int, $n : int, $comparator : Comparator::Int){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_int_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_int_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_int_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_long_merge : void($a : long[], $b : long[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Long) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_long_merge_sort : void($a : long[], $b : long[], $left : int, $right : int, $n : int, $comparator : Comparator::Long){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_long_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_long_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_long_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_float_merge : void($a : float[], $b : float[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Float) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_float_merge_sort : void($a : float[], $b : float[], $left : int, $right : int, $n : int, $comparator : Comparator::Float){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_float_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_float_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_float_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_double_merge : void($a : double[], $b : double[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::Double) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }
  private static method _sort_double_merge_sort : void($a : double[], $b : double[], $left : int, $right : int, $n : int, $comparator : Comparator::Double){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_double_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_double_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_double_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_string_merge : void($a : string[], $b : string[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator::String) {
    my $i = $left;
    my $j = $mid + 1;
    my $k = 0;
    while ($i <= $mid && $j <= $right) {
      $i = $left;
      $j = $mid + 1;
      $k = 0;
      while ($i <= $mid && $j <= $right) {
        if ($comparator->($a->[$i], $a->[$j]) <= 0) {
          $b->[$k] = $a->[$i];
          $i++;
        } else {
          $b->[$k] = $a->[$j];
          $j++;
        }
        $k++;
      }
      if ($i == $mid + 1) {
        while ($j <= $right) {
          $b->[$k] = $a->[$j]; 
          $j++;
          $k++;
        }
      } else {
        while($i <= $mid) {
          $b->[$k] = $a->[$i];
          $i++;
          $k++;
        }
      }
    }
    for ($i = 0; $i < $k; $i++) {
      $a->[$i + $left] = $b->[$i];
    }
  }

  private static method _sort_string_merge_sort : void($a : string[], $b : string[], $left : int, $right : int, $n : int, $comparator : Comparator::String){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_string_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_string_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_string_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }

  private static method _sort_object_merge : void($a : object[], $b : object[], $left : int, $mid : int, $right : int, $n : int, $comparator : Comparator) {
      my $i = $left;
      my $j = $mid + 1;
      my $k = 0;
      while ($i <= $mid && $j <= $right) {
        $i = $left;
        $j = $mid + 1;
        $k = 0;
        while ($i <= $mid && $j <= $right) {
          if ($comparator->($a->[$i], $a->[$j]) <= 0) {
            $b->[$k] = $a->[$i];
            $i++;
          } else {
            $b->[$k] = $a->[$j];
            $j++;
          }
          $k++;
        }
        if ($i == $mid + 1) {
          while ($j <= $right) {
            $b->[$k] = $a->[$j]; 
            $j++;
            $k++;
          }
        } else {
          while($i <= $mid) {
            $b->[$k] = $a->[$i];
            $i++;
            $k++;
          }
        }
      }
      for ($i = 0; $i < $k; $i++) {
        $a->[$i + $left] = $b->[$i];
      }
  }

  private static method _sort_object_merge_sort : void($a : object[], $b : object[], $left : int, $right : int, $n : int, $comparator : Comparator){
    if ($left >= $right) {
      return;
    }

    my $mid = ($left + $right) / 2;

    Fn->_sort_object_merge_sort($a, $b, $left, $mid, $n, $comparator);
    Fn->_sort_object_merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
    Fn->_sort_object_merge($a, $b, $left, $mid, $right, $n, $comparator);
  }
}