#!/bin/bash

# Copyright 2010 Google Inc.
# 
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# 
#      http://www.apache.org/licenses/LICENSE-2.0
# 
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------------------

# Script to generate arrayconv.szl and mapconv.szl.

##############################################################################
# Constants and utility functions

# Output file names
arraytestfile=arrayconv.szl
maptestfile=mapconv.szl


# Type names and abbreviations.
type_names=(bool bytes fingerprint float int string time uint)
type_abbrevs=(l b p f i s t u)


# Matrix of allowed basic conversions.  See "convtab" in ir.cc.
allowed=(\
# to:
# bool   bytes  fpr    float  int    string time   uint                 \
    1      0      0      0      1      1      0      1   # from: bool   \
    0      1      1      0      1      1      0      1   # from: bytes  \
    0      1      1      0      1      1      0      0   # from: fpr    \
    0      0      0      1      1      1      0      1   # from: float  \
    0      1      1      1      1      1      1      1   # from: int    \
    1      1      1      1      1      1      1      1   # from: string \
    0      0      0      0      1      1      1      1   # from: time   \
    0      1      1      1      1      1      1      1   # from: uint   \
)


# Returns value indicating if the conversion $1 to $2 is supported.
function is_allowed {
  local n
  (( n = $1 * 8 + $2))
  echo ${allowed[$n]}
}


# Returns any extra parameters required for the conversion.
# Currently only bytes <=> [u]int require an extra parameter,
# but we provide one on string to [u]int to suppress the warning.

kDefaultRadix=", 10"
kDefaultBytesIntEncoding=", \"saw\""

function extra_params {
  from=${type_names[$1]}
  to=${type_names[$2]}
  if [[ ( $from == "bytes" && ( $to == "int"   || $to == "uint"   ) ) ||
        ( $to == "bytes"   && ( $from == "int" || $from == "uint" ) ) ]] ; then
    echo "$kDefaultBytesIntEncoding"
  elif [[ $from == "string" && ( $to == "int" || $to == "uint" ) ]] ; then
    echo "$kDefaultRadix"
  else
    echo ""
  fi
}


# For each pair of map conversions (string => t1), (string => t2) indicate
# an input value prefix name that will work for both conversions or use a
# null string to indicate that there is no such value.
common_input=(\
# t1: 
# bool   bytes  fpr    float  int    string time   uint                \
  "sl"   "sl"   ""     ""     ""     "sl"   ""     ""     # t2: bool   \
  "sl"   "sb"   "sp"   "sf"   "si"   "sb"   "st"   "su"   # t2: bytes  \
  ""     "sp"   "sp"   "si"   "si"   "sp"   ""     "si"   # t2: fpr    \
  ""     "sf"   "si"   "sf"   "si"   "sf"   ""     "si"   # t2: float  \
  ""     "si"   "si"   "si"   "si"   "si"   ""     "si"   # t2: int    \
  "sl"   "sb"   "sp"   "sf"   "si"   "ss"   "st"   "su"   # t2: string \
  ""     "st"   ""     ""     ""     "st"   "st"   ""     # t2: time   \
  ""     "su"   "si"   "si"   "si"   "su"   ""     "su"   # t2: uint   \
)


# Returns an input value prefix that will work for converting to both of the
# specified output types.  Returns an empty string if there is no such value.
function get_common_input {
  local n
  (( n = $1 * 8 + $2))
  echo ${common_input[$n]}
}


##############################################################################
# Emit the test code.

# Banner

function emit_banner {
echo "\
# This test is generated by arraymapconv.sh; do not modify this file.
# Any changes should be made to the script.
#
# Test the $1 conversions.

"
}

emit_banner "array to array" > $arraytestfile
emit_banner "array to map" > $maptestfile


# Constants.
# It is not practical to test every combination of optional parameters
# and value lengths.  Pick values that should work for all conversions,
# except that there is no universal "string" input value (e.g. no string
# can be converted to both bool and int).  So the input values for string
# conversions also depend on the target type.

function emit_constants {
echo '
# General-purpose input values for each type.
l1: bool = false;
l2: bool = true;
l3: bool = true;
l4: bool = false;
b1: bytes = B"abcdefgh";  # exactly 8 bytes so can convert to fpr, int, uint
b2: bytes = B"ijklmnop";
b3: bytes = B"qrstuvwx";
b4: bytes = B"yzabcdef";
p1: fingerprint = 1234567890p;
p2: fingerprint = 2345678901p;
p3: fingerprint = 3456789012p;
p4: fingerprint = 4567890123p;
f1: float = 1.1;
f2: float = 2.3;
f3: float = 3.5;
f4: float = 4.7;
i1: int = 5678901234;
i2: int = 6789012345;
i3: int = 7890123456;
i4: int = 8901234567;
s1: string = "abc";
s2: string = "def";
s3: string = "ghi";
s4: string = "jkl";
t1: time = 9876543210;
t2: time = 8765432109;
t3: time = 7654321098;
t4: time = 6543210987;
u1: uint = 5432109876u;
u2: uint = 4321098765u;
u3: uint = 3210987654u;
u4: uint = 2109876543u;

# Input values for string to xxx depend on the target type.
sl1: string = "F";
sl2: string = "T";
sl3: string = "T";
sl4: string = "F";
sb1: string = s1;
sb2: string = s2;
sb3: string = s3;
sb4: string = s4;
sp1: string = "1234567890";
sp2: string = "2345678901";
sp3: string = "3456789012";
sp4: string = "4567890123";
sf1: string = "1.1";
sf2: string = "2.3";
sf3: string = "3.5";
sf4: string = "4.7";
si1: string = "5678901234";
si2: string = "6789012345";
si3: string = "7890123456";
si4: string = "8901234567";
st1: string = "Mar  4 15:25:48 PST 2009";
st2: string = "Mar  5 15:25:48 PST 2009";
st3: string = "Mar  6 15:25:48 PST 2009";
st4: string = "Mar  7 15:25:48 PST 2009";
su1: string = "5432109876";
su2: string = "4321098765";
su3: string = "3210987654";
su4: string = "2109876543";

'
}

emit_constants >> $arraytestfile
emit_constants >> $maptestfile


# Generate all array to array conversions.
# When the input type is string, customize the value to the output type.

for ((i = 0; i < ${#type_names[@]} ; i++)) ; do
  from_type=${type_names[$i]}
  from=${type_abbrevs[$i]}
  for ((j = 0; j < ${#type_names[@]} ; j++)) ; do
    if (( $(is_allowed $i $j) && $i != $j)) ; then
      to_type=${type_names[$j]}
      to=${type_abbrevs[$j]}
      input="$from"
      if [[ "$from_type" == "string" ]] ; then
        input="${from}${to}"
      fi
      extra=$(extra_params $i $j)
      echo
      echo "# array of $from_type to array of $to_type"
      echo
      a=arr_${to}${from}
      echo -n "$a: array of $to_type = "
      echo -n "convert(array of $to_type, "
      echo    "{ ${input}1, ${input}2, ${input}3 }$extra);"
      echo "assert(len($a) == 3);"
      echo "assert($a[0] == $to_type(${input}1$extra));"
      echo "assert($a[1] == $to_type(${input}2$extra));"
      echo "assert($a[2] == $to_type(${input}3$extra));"
    fi
  done
done >> $arraytestfile


# Generate all array to map conversions.
# When the input type is string, check if we have an input value that will
# work with both output types, and only emit the case if we do.

for ((i = 0; i < ${#type_names[@]} ; i++)) ; do
  from_type=${type_names[$i]}
  from=${type_abbrevs[$i]}
  for ((j = 0; j < ${#type_names[@]} ; j++)) ; do
    if (( $(is_allowed $i $j) )) ; then
      index_type=${type_names[$j]}
      index=${type_abbrevs[$j]}
      for ((k = 0; k < ${#type_names[@]} ; k++)) ; do
        if (( $(is_allowed $i $k) && ( $i != $j || $i != $k ) )) ; then
          elem_type=${type_names[$k]}
          elem=${type_abbrevs[$k]}
          input="$from"
          if [[ "$from_type" == "string" ]] ; then
            input=$(get_common_input $j $k)
            if [[ "$input" == "" ]] ; then
              continue
            fi
          fi
          # Suppress conversions in the asserts when the types match.
	  # Supply default extra parameters needed to suppress warnings.
	  # For bytes <=> [u]int, an extra parameter would be needed to
	  # avoid an error.  Currently these conversions are not allowed.
          if (( $i == $j )) ; then
            index1=${input}1
            index2=${input}3
          else
            extra=$(extra_params $i $j)
	    if [[ "$extra" == "$kDefaultBytesIntEncoding" ]] ; then
	      continue  # we currently do not accept these in array to map
	    fi
            index1="$index_type(${input}1$extra)"
            index2="$index_type(${input}3$extra)"
          fi
          if (( $i == $k )) ; then
            elem1=${input}2
            elem2=${input}4
          else
            extra=$(extra_params $i $k)
	    if [[ "$extra" == "$kDefaultBytesIntEncoding" ]] ; then
	      continue  # we currently do not accept these in array to map
	    fi
            elem1="$elem_type(${input}2$extra)"
            elem2="$elem_type(${input}4$extra)"
          fi
          echo
          echo "# array of $from_type to map[$index_type] of $elem_type"
          echo
          m=map${index}${elem}${from}
          echo -n "$m: map[$index_type] of $elem_type = "
          echo -n "convert(map[$index_type] of $elem_type, "
          echo    "{ ${input}1, ${input}2, ${input}3, ${input}4 });"
          echo "assert(len($m) == 2);"
          echo "assert($m[$index1] == $elem1);"
          echo "assert($m[$index2] == $elem2);"
        fi
      done
    fi
  done
done >> $maptestfile
