package SPVM::CORE {
  use SPVM::Complex_2f;
  use SPVM::Complex_2d;
  use SPVM::List;
  use SPVM::Hash;
  use SPVM::Comparator;
  use SPVM::Stringer;
  use SPVM::FileHandle;

  our $O_RDONLY : ro int;
  our $O_WRONLY : ro int;
  our $O_RDWR : ro int;
  our $O_APPEND : ro int;
  our $O_CREAT : ro int;
  our $O_TRUNC : ro int;
  our $SEEK_SET : ro int;
  our $SEEK_CUR : ro int;
  our $SEEK_END : ro int;
  our $EOF : ro int;
  our $STDIN : ro SPVM::FileHandle;
  our $STDOUT : ro SPVM::FileHandle;
  
  BEGIN {
    init_native_constants();
  }
  
  native sub chomp : void ($string : string);
  native sub rand : int ($seed_ref : int&);
  
  precompile sub lc : string($str : string) {
    
    unless ($str) { die "String must be defined"; }
    
    my $offset = 32;
    my $new_str = (byte[])copy_str($str);
    for (my $i = 0; $i < length $str; $i++) {
      my $char = $str->[$i];
      if ($char >= 'A' && $char <= 'Z') {
        $new_str->[$i] = (byte)($str->[$i] + 32);
      }
      else {
        $new_str->[$i] = $str->[$i];
      }
    }
    return (string)$new_str;
  }

  precompile sub lcfirst : string($str : string) {

    unless ($str) { die "String must be defined"; }
    
    my $offset = 32;
    my $new_str = (byte[])copy_str($str);
    if (length $str > 0) {
      my $char = $str->[0];
      if ($char >= 'A' && $char <= 'Z') {
        $new_str->[0] = (byte)($char + 32);
      }
    }
    return (string)$new_str;
  }

  precompile sub uc : string($str : string) {

    unless ($str) { die "String must be defined"; }
    
    my $offset = 32;
    my $new_str = (byte[])copy_str($str);
    for (my $i = 0; $i < length $str; $i++) {
      my $char = $str->[$i];
      if ($char >= 'a' && $char <= 'z') {
        $new_str->[$i] = (byte)($str->[$i] - 32);
      }
      else {
        $new_str->[$i] = $str->[$i];
      }
    }
    return (string)$new_str;
  }

  precompile sub ucfirst : string($str : string) {

    unless ($str) { die "String must be defined"; }
    
    my $offset = 32;
    my $new_str = (byte[])copy_str($str);
    if (length $str > 0) {
      my $char = $str->[0];
      if ($char >= 'a' && $char <= 'z') {
        $new_str->[0] = (byte)($char - 32);
      }
    }
    return (string)$new_str;
  }

  precompile sub replace_all : string ($str : string, $substr : string, $replace : string) {

    unless ($str) { die "String must be defined"; }
    
    my $offset = 0;
    my $found_pos : int;
    my $substr_len = length $substr;
    my $result_str = $str;
    while (1) {
      $result_str = replace($result_str, $substr, $replace, $offset, \$found_pos);
      if ($found_pos >= 0) {
        $offset = $found_pos + $substr_len;
      }
      else {
        return $result_str;
      }
    }
  }

  precompile sub replace : string ($str : string, $substr : string, $replace : string, $from : int, $found_pos_ref : int&) {
    unless ($substr && length $substr > 0) {
      die "Sub string must have length";
    }
    my $found_pos = index($str, $substr, $from);
    $$found_pos_ref = $found_pos;
    if ($found_pos >= 0) {
      my $str_len = length $str;
      my $substr_len = length $substr;
      my $replace_len = length $replace;
      
      my $result_str_len = $str_len + $replace_len - $substr_len;
      my $result_str = new byte[$result_str_len];
      
      if ($found_pos > 0) {
        memmove((byte[])$result_str, 0, (byte[])$str, 0, $found_pos);
      }
      memmove((byte[])$result_str, $found_pos, (byte[])$replace, 0, $replace_len);
      
      my $str_rest_offset = $found_pos + $substr_len;
      my $str_rest_length = $str_len - $str_rest_offset;
      my $result_rest_offset = $found_pos + $replace_len;
      my $result_rest_length = $result_str_len - $result_rest_offset;
      memmove((byte[])$result_str, $result_rest_offset, (byte[])$str, $str_rest_offset, $str_rest_length);
      
      return $result_str;
    }
    else {
      my $copy_str = copy_str($str);
      return $copy_str;
    }
  }

  native sub memcpy : void ($dist_str : byte[], $dist_offset : int, $src_str : byte[], $src_offset : int, $length : int);
  native sub memmove : void ($dist_str : byte[], $dist_offset : int, $src_str : byte[], $src_offset : int, $length : int);
  
  precompile sub index : int($source : string, $target : string, $fromIndex : int) {
    my $sourceCount = length $source;
    my $targetCount = length $target;
    
    if ($fromIndex >= $sourceCount) {
      my $ret : int;
      if ($targetCount == 0) {
        $ret = $sourceCount;
      }
      else {
        $ret = -1;
      }
      return $ret;
    }
    if ($fromIndex < 0) {
      $fromIndex = 0;
    }
    if ($targetCount == 0) {
      return $fromIndex;
    }

    my $first : byte  = $target->[0];
    my $max : int = ($sourceCount - $targetCount);

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

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

        if ($j == $end) {
          return $i;
        }
      }
    }
    return -1;
  }
  
  precompile sub contains : int ($text : string, $pattern : string) {
    if (!$text) {
      die "Target string must be defined";
    }
    if (!$pattern) {
      die "Search string must be defined";
    }

    my $pat_len = length $pattern;
    my $text_len = length $text;
    my $i = 0;
    my $j = 0;
    
    while ($i < $text_len && $j < $pat_len) {
      if ($text->[$i] == $pattern->[$j]) {
        $i++;
        $j++;
      } else {
        $i = $i - $j + 1;
        $j = 0;
      }
    }
    
    if ($j == $pat_len) {
      return 1;
    }
    else {
      return 0;
    }
  }
  
  native sub getenv : string ($name : string);
  
  native sub abs : int ($x : int);
  native sub labs : long ($x : long);

  native sub init_native_constants : void ();

  native sub strerror : string ($errno : int);
  
  precompile sub split : string[] ($sep : string, $string : string, $max : int) {
    my $string_length = length $string;
    my $sep_length = length $sep;
    
    unless ($max < 0) {
      die "Max must be minus value.";
    }
    if ($sep_length == 0) {
      die "Separator must not be empty string";
    }
    
    my $separated_strings_list = list([]);
    
    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 = 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 = substr($string, $string_base, $string_length - $string_base);
      $separated_strings_list->push($separated_string);
    }
    
    my $separated_strings = $separated_strings_list->to_strarray;
    
    return $separated_strings;
  }
  
  native sub new_oarray_proto : oarray ($proto_array : oarray, $length : int);

  precompile sub replaceb : void ($nums : byte[], $offset : int, $replaces : byte[]) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    my $length = @$replaces;
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $nums->[$pos] = $replaces->[$pos];
      $pos++;
    }
  }

  precompile sub 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 = new byte[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $string->[$i];
      $pos++;
    }
    
    return (string)$slice;
  }

  precompile sub sliceb : byte[] ($nums : byte[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new byte[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub slices : short[] ($nums : short[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new short[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub slicei : int[] ($nums : int[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new int[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub slicel : long[] ($nums : long[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new long[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub slicef : float[] ($nums : float[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new float[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub sliced : double[] ($nums : double[], $offset : int, $length : int) {
    
    if ($nums == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$nums;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new double[$length];
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $nums->[$i];
      $pos++;
    }
    
    return $slice;
  }

  precompile sub sliceo : oarray ($elems : oarray, $offset : int, $length : int) {
    
    if ($elems == undef) {
      die "Array must be defined";
    }
    
    my $array_length = @$elems;
    
    if ($offset < 0 || $offset > $array_length - 1) {
      die "Invalid offset $offset";
    }
    
    if ($length < 0) {
      die "Length must be non-zero value";
    }
    
    if ($offset + $length > $array_length) {
      die "Offset + length must not be over array length";
    }
    
    my $slice = new_oarray_proto($elems, $length);
    
    my $pos = 0;
    for (my $i = $offset; $i < $offset + $length; $i++) {
      $slice->[$pos] = $elems->[$i];
      $pos++;
    }
    
    return $slice;
  }

  native sub reverseb : void ($nums : byte[]);
  native sub reverses : void ($nums : short[]);
  native sub reversei : void ($nums : int[]);
  native sub reversel : void ($nums : long[]);
  native sub reversef : void ($nums : float[]);
  native sub reversed : void ($nums : double[]);
  native sub reverseo : void ($nums : oarray);
  
  native sub sortb : void ($nums : byte[]);
  native sub sorts : void ($nums : short[]);
  native sub sorti : void ($nums : int[]);
  native sub sortl : void ($nums : long[]);
  native sub sortf : void ($nums : float[]);
  native sub sortd : void ($nums : double[]);

  precompile sub _merge : void($a : oarray, $b : oarray, $left : int, $mid : int, $right : int, $n : int, $comparator : SPVM::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->compare($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];
      }
  }

  precompile sub _merge_sort : void($a : oarray, $b : oarray, $left : int, $right : int, $n : int, $comparator : SPVM::Comparator){
      if ($left == $right) {
        return;
      }

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

      _merge_sort($a, $b, $left, $mid, $n, $comparator);
      _merge_sort($a, $b, $mid + 1, $right, $n, $comparator);
      _merge($a, $b, $left, $mid, $right, $n, $comparator);
  }
  
  precompile sub sorto : void ($objs : oarray, $comparator : SPVM::Comparator) {
    if ($objs == undef) {
      die "Array must be defined";
    }
    my $length = @$objs;
    my $b = new_oarray_proto($objs, $length);
    _merge_sort($objs, $b, 0, $length - 1, $length, $comparator);
  }
  
  precompile sub equals_barray : int ($nums1 : byte[], $nums2 : byte[]) {
    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;
  }

  precompile sub equals_sarray : int ($nums1 : short[], $nums2 : short[]) {
    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;
  }
  precompile sub equals_iarray : int ($nums1 : int[], $nums2 : int[]) {
    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;
  }
  precompile sub equals_larray : int ($nums1 : long[], $nums2 : long[]) {
    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;
  }
  precompile sub equals_farray : int ($nums1 : float[], $nums2 : float[]) {
    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;
  }
  precompile sub equals_darray : int ($nums1 : double[], $nums2 : double[]) {
    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;
  }

  precompile sub equals_oarray : int ($nums1 : oarray, $nums2 : oarray) {
    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;
  }

  precompile sub equals_strarray : int ($strings1 : string[], $strings2 : string[]) {
    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;
  }
  
  native sub type_name : string ($object : object);
  native sub cadd : SPVM::Complex_2d ($z1 : SPVM::Complex_2d, $z2 : SPVM::Complex_2d);
  native sub csub : SPVM::Complex_2d ($z1 : SPVM::Complex_2d, $z2 : SPVM::Complex_2d);
  native sub cmul : SPVM::Complex_2d ($z1 : SPVM::Complex_2d, $z2 : SPVM::Complex_2d);
  native sub cdiv : SPVM::Complex_2d ($z1 : SPVM::Complex_2d, $z2 : SPVM::Complex_2d);
  native sub caddf : SPVM::Complex_2f ($z1 : SPVM::Complex_2f, $z2 : SPVM::Complex_2f);
  native sub csubf : SPVM::Complex_2f ($z1 : SPVM::Complex_2f, $z2 : SPVM::Complex_2f);
  native sub cmulf : SPVM::Complex_2f ($z1 : SPVM::Complex_2f, $z2 : SPVM::Complex_2f);
  native sub cdivf : SPVM::Complex_2f ($z1 : SPVM::Complex_2f, $z2 : SPVM::Complex_2f);
  
  sub list : SPVM::List ($objects : object[]) {
    return SPVM::List->new_with_array($objects);
  }

  sub hash : SPVM::Hash ($objects : object[]) {
    return SPVM::Hash->new_with_array($objects);
  }
  
  sub copy_str : string ($string : string) {
    my $bytes = (byte[])$string;
    
    return (string)$bytes;
  }
  
  sub copy_barray : byte[] ($nums : byte[]) {
    my $length = @$nums;
    
    my $new_nums = new byte[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }

  sub copy_sarray : short[] ($nums : short[]) {
    my $length = @$nums;
    
    my $new_nums = new short[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }
  
  sub copy_iarray : int[] ($nums : int[]) {
    my $length = @$nums;
    
    my $new_nums = new int[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }

  sub copy_larray : long[] ($nums : long[]) {
    my $length = @$nums;
    
    my $new_nums = new long[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }

  sub copy_farray : float[] ($nums : float[]) {
    my $length = @$nums;
    
    my $new_nums = new float[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }

  sub copy_darray : double[] ($nums : double[]) {
    my $length = @$nums;
    
    my $new_nums = new double[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_nums->[$i] = $nums->[$i];
    }
    
    return $new_nums;
  }

  sub copy_oarray : object[] ($objects : object[]) {
    my $length = @$objects;
    
    my $new_objects = new object[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_objects->[$i] = $objects->[$i];
    }
    
    return $new_objects;
  }

  sub copy_strarray : string[] ($strings : string[]) {
    my $length = @$strings;
    
    my $new_strings = new string[$length];
    
    for (my $i = 0; $i < $length; $i++) {
      $new_strings->[$i] = copy_str($strings->[$i]);
    }
    
    return $new_strings;
  }

  sub new_fcomplex_array : SPVM::Complex_2f[] ($re_values : float[], $im_values : float[]) {
    if (@$re_values != @$im_values) {
      die "Real values length is different from imaginary values";
    }
    
    my $z_array = new SPVM::Complex_2f[@$re_values];
    for (my $i = 0; $i < @$re_values; $i++) {
      $z_array->[$i]{re} = $re_values->[$i];
      $z_array->[$i]{im} = $im_values->[$i];
    }
    
    return $z_array;
  }
  
  sub new_dcomplex_array : SPVM::Complex_2d[] ($re_values : double[], $im_values : double[]) {
    if (@$re_values != @$im_values) {
      die "Real values length is different from imaginary values";
    }
    
    my $z_array = new SPVM::Complex_2d[@$re_values];
    for (my $i = 0; $i < @$re_values; $i++) {
      $z_array->[$i]{re} = $re_values->[$i];
      $z_array->[$i]{im} = $im_values->[$i];
    }
    
    return $z_array;
  }

  native sub new_fcomplex : SPVM::Complex_2f ($re : float, $im : float);
  native sub new_dcomplex : SPVM::Complex_2d ($re : double, $im : double);
  sub 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;
  }
  sub joinb : string ($sep : string, $nums : byte[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joins : string ($sep : string, $nums : short[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joini : string ($sep : string, $nums : int[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joinl : string ($sep : string, $nums : long[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joinf : string ($sep : string, $nums : float[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joind : string ($sep : string, $nums : double[]) {
    my $join = "";
    
    for (my $i = 0; $i < @$nums; $i++) {
      my $string = (string)$nums->[$i];
      $join .= $string;
      if ($i != @$nums - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  sub joino : string ($sep : string, $objects : oarray) {
    my $join = "";
    
    for (my $i = 0; $i < @$objects; $i++) {
      my $stringer = (SPVM::Stringer)$objects->[$i];
      my $string = $stringer->to_str();
      $join .= $string;
      if ($i != @$objects - 1) {
        $join .= $sep;
      }
    }
    
    return $join;
  }
  native sub print : void ($value : string);
  native sub warn : void ($value : string);

  native sub PI : double ();
  native sub E : double ();

  native sub INT8_MIN : byte ();
  native sub INT8_MAX : byte ();

  native sub INT16_MIN : short ();
  native sub INT16_MAX : short ();

  native sub INT32_MIN : int ();
  native sub INT32_MAX : int ();

  native sub INT64_MIN : long ();
  native sub INT64_MAX : long ();

  native sub FLT_MAX : float ();
  native sub FLT_MIN : float();
  
  native sub DBL_MAX : double ();
  native sub DBL_MIN : double ();

  # hasher function
  native sub murmur_hash : long ($string : string, $seed : int);
}
