
(********************************************************************)
(*                                                                  *)
(*  bytedata.s7i  Convert byte data to and from strings             *)
(*  Copyright (C) 2009, 2013 - 2015  Thomas Mertes                  *)
(*                                                                  *)
(*  This file is part of the Seed7 Runtime Library.                 *)
(*                                                                  *)
(*  The Seed7 Runtime Library is free software; you can             *)
(*  redistribute it and/or modify it under the terms of the GNU     *)
(*  Lesser General Public License as published by the Free Software *)
(*  Foundation; either version 2.1 of the License, or (at your      *)
(*  option) any later version.                                      *)
(*                                                                  *)
(*  The Seed7 Runtime Library is distributed in the hope that it    *)
(*  will be useful, but WITHOUT ANY WARRANTY; without even the      *)
(*  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR *)
(*  PURPOSE.  See the GNU Lesser General Public License for more    *)
(*  details.                                                        *)
(*                                                                  *)
(*  You should have received a copy of the GNU Lesser General       *)
(*  Public License along with this program; if not, write to the    *)
(*  Free Software Foundation, Inc., 51 Franklin Street,             *)
(*  Fifth Floor, Boston, MA  02110-1301, USA.                       *)
(*                                                                  *)
(********************************************************************)


include "file.s7i";
include "bigint.s7i";
include "bstring.s7i";


(**
 *  Enumeration of signedness values.
 *  Signedness defines if negative values can be represented.
 *  Defines: UNSIGNED and SIGNED.
 *  Signed values use the twos-complement representation.
 *)
const type: signedness is new enum
    UNSIGNED, SIGNED
  end enum;


(**
 *  Enumeration of endianess values.
 *  Endianess defines how the bytes of a data are ordered within memory.
 *  Defines: LE (little-endian) and BE (big-endian).
 *  A little-endian representation starts with the least significant byte.
 *  A big-endian representation starts with the most significant byte.
 *)
const type: endianess is new enum
    LE, BE
  end enum;


(**
 *  Get a zero terminated string from ''stri'' starting from ''currPos''.
 *  CurrPos is advanced after the zero ('\0;') character.
 *  @return the zero terminated string without the zero ('\0;') character.
 *)
const func string: getAsciiz (in string: stri, inout integer: currPos) is func
  result
    var string: resultStri is "";
  local
    var integer: startPos is 0;
  begin
    startPos := currPos;
    currPos := pos(stri, '\0;', startPos);
    if currPos = 0 then
      resultStri := stri[startPos ..];
      currPos := succ(length(stri));
    else
      resultStri := stri[startPos .. pred(currPos)];
      incr(currPos);
    end if;
  end func;


(**
 *  Get a zero terminated string from ''inFile''.
 *  The file position is advanced after the zero ('\0;') character.
 *  @return the zero terminated string without the zero ('\0;') character.
 *)
const func string: getAsciiz (inout file: inFile) is func
  result
    var string: resultStri is "";
  local
    var char: ch is ' ';
  begin
    ch := getc(inFile);
    while ch <> '\0;' do
      resultStri &:= ch;
      ch := getc(inFile);
    end while;
  end func;


(**
 *  Convert a [[string]] of bytes to its hexadecimal representation.
 *  Each byte is represented by two hexadecimal digits.
 *   hex("!;Mn")  returns "213b4d6e"
 *  @return the hexadecimal representation of the given [[string]].
 *  @exception RANGE_ERROR When characters beyond '\255;' are present.
 *)
const func string: hex (in string: stri) is func
  result
    var string: hex is "";
  local
    var char: ch is ' ';
  begin
    for ch range stri do
      if ch > '\255;' then
        raise RANGE_ERROR;
      end if;
      hex &:= ord(ch) radix 16 lpad0 2;
    end for;
  end func;


(**
 *  Convert 16 lower bits of an integer into two little-endian chars.
 *  @return a string of length two with the little-endian representation.
 *)
const func string: int16AsTwoBytesLe (in integer: number) is func
  result
    var string: twoBytes is "\0;" mult 2;
  begin
    twoBytes @:= [1] chr( number        mod 256);
    twoBytes @:= [2] chr((number >>  8) mod 256);
  end func;


(**
 *  Convert 32 lower bits of an integer into four little-endian chars.
 *  @return a string of length four with the little-endian representation.
 *)
const func string: int32AsFourBytesLe (in integer: number) is func
  result
    var string: fourBytes is "\0;" mult 4;
  begin
    fourBytes @:= [1] chr( number        mod 256);
    fourBytes @:= [2] chr((number >>  8) mod 256);
    fourBytes @:= [3] chr((number >> 16) mod 256);
    fourBytes @:= [4] chr((number >> 24) mod 256);
  end func;


(**
 *  Convert 64 lower bits of an integer into eight little-endian chars.
 *  @return a string of length eight with the little-endian representation.
 *)
const func string: int64AsEightBytesLe (in integer: number) is func
  result
    var string: eightBytes is "\0;" mult 8;
  begin
    eightBytes @:= [1] chr( number        mod 256);
    eightBytes @:= [2] chr((number >>  8) mod 256);
    eightBytes @:= [3] chr((number >> 16) mod 256);
    eightBytes @:= [4] chr((number >> 24) mod 256);
    eightBytes @:= [5] chr((number >> 32) mod 256);
    eightBytes @:= [6] chr((number >> 40) mod 256);
    eightBytes @:= [7] chr((number >> 48) mod 256);
    eightBytes @:= [8] chr((number >> 56) mod 256);
  end func;


(**
 *  Convert 16 lower bits of an integer into two big-endian chars.
 *  @return a string of length two with the big-endian representation.
 *)
const func string: int16AsTwoBytesBe (in integer: number) is func
  result
    var string: twoBytes is "\0;" mult 2;
  begin
    twoBytes @:= [1] chr((number >>  8) mod 256);
    twoBytes @:= [2] chr( number        mod 256);
  end func;


(**
 *  Convert 32 lower bits of an integer into four big-endian chars.
 *  @return a string of length four with the big-endian representation.
 *)
const func string: int32AsFourBytesBe (in integer: number) is func
  result
    var string: fourBytes is "\0;" mult 4;
  begin
    fourBytes @:= [1] chr((number >> 24) mod 256);
    fourBytes @:= [2] chr((number >> 16) mod 256);
    fourBytes @:= [3] chr((number >>  8) mod 256);
    fourBytes @:= [4] chr( number        mod 256);
  end func;


(**
 *  Convert 64 lower bits of an integer into eight big-endian chars.
 *  @return a string of length eight with the big-endian representation.
 *)
const func string: int64AsEightBytesBe (in integer: number) is func
  result
    var string: eightBytes is "\0;" mult 8;
  begin
    eightBytes @:= [1] chr((number >> 56) mod 256);
    eightBytes @:= [2] chr((number >> 48) mod 256);
    eightBytes @:= [3] chr((number >> 40) mod 256);
    eightBytes @:= [4] chr((number >> 32) mod 256);
    eightBytes @:= [5] chr((number >> 24) mod 256);
    eightBytes @:= [6] chr((number >> 16) mod 256);
    eightBytes @:= [7] chr((number >>  8) mod 256);
    eightBytes @:= [8] chr( number        mod 256);
  end func;


(**
 *  Convert 64 lower bits of a bigInteger into eight little-endian chars.
 *  @return a string of length eight with the little-endian representation.
 *)
const func string: bigAsEightBytesLe (in bigInteger: number) is func
  result
    var string: eightBytes is "\0;" mult 8;
  begin
    eightBytes @:= [1] chr(ord( number        mod 256_));
    eightBytes @:= [2] chr(ord((number >>  8) mod 256_));
    eightBytes @:= [3] chr(ord((number >> 16) mod 256_));
    eightBytes @:= [4] chr(ord((number >> 24) mod 256_));
    eightBytes @:= [5] chr(ord((number >> 32) mod 256_));
    eightBytes @:= [6] chr(ord((number >> 40) mod 256_));
    eightBytes @:= [7] chr(ord((number >> 48) mod 256_));
    eightBytes @:= [8] chr(ord((number >> 56) mod 256_));
  end func;


(**
 *  Convert 64 lower bits of a bigInteger into eight big-endian chars.
 *  @return a string of length eight with the big-endian representation.
 *)
const func string: bigAsEightBytesBe (in bigInteger: number) is func
  result
    var string: eightBytes is "\0;" mult 8;
  begin
    eightBytes @:= [1] chr(ord((number >> 56) mod 256_));
    eightBytes @:= [2] chr(ord((number >> 48) mod 256_));
    eightBytes @:= [3] chr(ord((number >> 40) mod 256_));
    eightBytes @:= [4] chr(ord((number >> 32) mod 256_));
    eightBytes @:= [5] chr(ord((number >> 24) mod 256_));
    eightBytes @:= [6] chr(ord((number >> 16) mod 256_));
    eightBytes @:= [7] chr(ord((number >>  8) mod 256_));
    eightBytes @:= [8] chr(ord( number        mod 256_));
  end func;


const func string: bytes (in integer: number, UNSIGNED, BE) is action "INT_BYTES_BE_UNSIGNED";

const func string: bytes (in integer: number, UNSIGNED, LE) is action "INT_BYTES_LE_UNSIGNED";

const func string: bytes (in integer: number, SIGNED, BE) is action "INT_BYTES_BE_SIGNED";

const func string: bytes (in integer: number, SIGNED, LE) is action "INT_BYTES_LE_SIGNED";


(**
 *  Convert an [[integer]] into a [[string]] of bytes.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460, SIGNED, BE)  returns "TEST"
 *   bytes(1497451343, SIGNED, LE)  returns "OKAY"
 *  @param number Integer number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. When ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianess|endianess]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a string with the shortest binary representation of ''number''.
 *  @exception RANGE_ERROR When ''number'' is negative and ''signed'' is UNSIGNED.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in integer: number,
    in signedness: signed, in endianess: endian) is DYNAMIC;


const func string: bytes (in bigInteger: number, UNSIGNED, BE) is
  return str(bStriBe(number, FALSE));

const func string: bytes (in bigInteger: number, UNSIGNED, LE) is
  return str(bStriLe(number, FALSE));

const func string: bytes (in bigInteger: number, SIGNED, BE) is
  return str(bStriBe(number, TRUE));

const func string: bytes (in bigInteger: number, SIGNED, LE) is
  return str(bStriLe(number, TRUE));


(**
 *  Convert a [[bigint|bigInteger]] into a [[string]] of bytes.
 *  The result uses binary representation with a base of 256.
 *  The result contains chars (bytes) with an ordinal <= 255.
 *   bytes(1413829460_, SIGNED, BE)                        returns "TEST"
 *   bytes(1497451343_, SIGNED, LE)                        returns "OKAY"
 *   bytes(8316866959935304777_, UNSIGNED, LE)             returns "It works"
 *   bytes(54818063270363344731475178867_, UNSIGNED, BE)   returns "± plus-minus"
 *   bytes(54818063270363344731475178867_, SIGNED, BE)     returns "\0;± plus-minus"
 *   bytes(-24410099243900992862068771469_, SIGNED, BE)    returns "± plus-minus"
 *   bytes(-24410099243900992862068771469_, UNSIGNED, LE)  raises RANGE_ERROR
 *  @param number BigInteger number to be converted.
 *  @param signed Determines the [[#signedness|signedness]] of the result.
 *         Possible values are UNSIGNED and SIGNED. When ''signed'' is SIGNED
 *         the result is encoded with the twos-complement representation.
 *         In this case a negative ''number'' is converted to a result
 *         where the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianess|endianess]] of the result.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a string with the shortest binary representation of ''number''.
 *  @exception RANGE_ERROR When ''number'' is negative and ''signed'' is UNSIGNED.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func string: bytes (in bigInteger: number,
    in signedness: signed, in endianess: endian) is DYNAMIC;


const func integer: bytes2Int (in string: byteStri, UNSIGNED, BE) is action "INT_BYTES_BE_2_UINT";

const func integer: bytes2Int (in string: byteStri, UNSIGNED, LE) is action "INT_BYTES_LE_2_UINT";

const func integer: bytes2Int (in string: byteStri, SIGNED, BE) is action "INT_BYTES_BE_2_INT";

const func integer: bytes2Int (in string: byteStri, SIGNED, LE) is action "INT_BYTES_LE_2_INT";


(**
 *  Convert a [[string]] of bytes to an [[integer]].
 *   bytes2Int("I\150;\2;\210;", UNSIGNED, BE)  returns  1234567890
 *   bytes2Int("\210;\2;\150;I", UNSIGNED, LE)  returns  1234567890
 *   bytes2Int(":\222;h\177;", UNSIGNED, BE)    returns   987654321
 *  @param byteStri String of bytes to be converted. The bytes are
 *         interpreted as binary representation with a base of 256.
 *  @param signed Determines the [[#signedness|signedness]] of ''byteStri''.
 *         Possible values are UNSIGNED and SIGNED. When ''signed'' is SIGNED
 *         ''bstri'' is interpreted as signed value in the twos-complement
 *         representation. In this case the result is negative when
 *         the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianess|endianess]] of ''byteStri''.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return an integer created from ''byteStri''.
 *  @exception RANGE_ERROR When characters beyond '\255;' are present or
 *             when the result value cannot be represented with an integer.
 *)
const func integer: bytes2Int (in string: byteStri,
    in signedness: signed, in endianess: endian) is DYNAMIC;


const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, BE) is
  return bStriBe2BigInt(bstring parse byteStri, FALSE);

const func bigInteger: bytes2BigInt (in string: byteStri, UNSIGNED, LE) is
  return bStriLe2BigInt(bstring parse byteStri, FALSE);

const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, BE) is
  return bStriBe2BigInt(bstring parse byteStri, TRUE);

const func bigInteger: bytes2BigInt (in string: byteStri, SIGNED, LE) is
  return bStriLe2BigInt(bstring parse byteStri, TRUE);


(**
 *  Convert a [[string]] of bytes to a [[bigint|bigInteger]].
 *   bytes2BigInt("I\150;\2;\210;", SIGNED, BE)        returns  1234567890_
 *   bytes2BigInt("\210;\2;\150;I", UNSIGNED, LE)      returns  1234567890_
 *   bytes2BigInt(":\222;h\177;", SIGNED, BE)          returns   987654321_
 *   bytes2BigInt("\139;\208;\3;\152;", UNSIGNED, BE)  returns  2345665432_
 *   bytes2BigInt("\139;\208;\3;\152;", SIGNED, BE)    returns -1949301864_
 *   bytes2BigInt("\152;\3;\208;\139;", UNSIGNED, LE)  returns  2345665432_
 *  @param byteStri String of bytes to be converted. The bytes are
 *         interpreted as binary representation with a base of 256.
 *  @param signed Determines the [[#signedness|signedness]] of ''byteStri''.
 *         Possible values are UNSIGNED and SIGNED. When ''signed'' is SIGNED
 *         ''bstri'' is interpreted as signed value in the twos-complement
 *         representation. In this case the result is negative when
 *         the most significant byte has an ordinal >= 128.
 *  @param endian Determines the [[#endianess|endianess]] of ''byteStri''.
 *         Possible values are LE for little-endian and BE for big-endian.
 *  @return a bigInteger created from ''byteStri''.
 *  @exception RANGE_ERROR When characters beyond '\255;' are present.
 *  @exception MEMORY_ERROR Not enough memory to represent the result.
 *)
const func bigInteger: bytes2BigInt (in string: byteStri,
    in signedness: signed, in endianess: endian) is DYNAMIC;


(**
 *  Read two bytes from a file and return their little-endian value.
 *)
const func integer: getUInt16Le (inout file: in_file) is func
  result
    var integer: number is 0;
  local
    var string: stri is "";
  begin
    stri := gets(in_file, 2);
    number := ord(stri[1]) +
              ord(stri[2]) * 256;
  end func;


(**
 *  Read four bytes from a file and return their little-endian value.
 *)
const func integer: getUInt32Le (inout file: in_file) is func
  result
    var integer: number is 0;
  local
    var string: stri is "";
  begin
    stri := gets(in_file, 4);
    number := ord(stri[1]) +
              ord(stri[2]) * 256 +
              ord(stri[3]) * 65536 +
              ord(stri[4]) * 16777216;
  end func;


(**
 *  Read two bytes from a file and return their big-endian value.
 *)
const func integer: getUInt16Be (inout file: in_file) is func
  result
    var integer: number is 0;
  local
    var string: stri is "";
  begin
    stri := gets(in_file, 2);
    number := ord(stri[1]) * 256 +
              ord(stri[2]);
  end func;


(**
 *  Read four bytes from a file and return their big-endian value.
 *)
const func integer: getUInt32Be (inout file: in_file) is func
  result
    var integer: number is 0;
  local
    var string: stri is "";
  begin
    stri := gets(in_file, 4);
    number := ord(stri[1]) * 16777216 +
              ord(stri[2]) * 65536 +
              ord(stri[3]) * 256 +
              ord(stri[4]);
  end func;
