#
# $Id: typemap.PL,v 36.2 2011/01/03 15:04:43 anbrown Exp $
#
# (c) 1999-2011 Morgan Stanley & Co. Incorporated
# See ..../src/LICENSE for terms of distribution.
#
# Script to generate a typemap from the MQSeries header files
#
# Whenever it encounters a new type, it starts building up two hashes,
# %input, and %output.  $input{TYPE} is the input section of the
# typemap for the TYPE, and $output{TYPE} is the output section of the
# typemap.
#
# The actual code used to convert a field can be found in:
#
#       &long_output, &long_input               for MQLONG types
#       &string_output, &string_input           for MQCHARn types
#       &byte_output, &byte_input               for MQBYTE types
#       &charv_output, &charv_input             for MQCHARV types
#
# Other types are not yet supported, and those that are not relevant
# are skipped intentionally.  Warnings should be due to new
# structures, and should be reviewed.
#

$input{'MQCHAR48'} = join('',
                          '/*' . '=' x 75 . "*/\n",
                          "T_MQCHAR48\n",
                          "    strncpy(\$var, (char*)SvPV(\$arg, PL_na), 48)\n",
                          );

require "../util/parse_config";
require "../util/parse_headers";

@ARGV = @headers;

my $debug = $ENV{DEBUG_TYPEMAP_PL};

open(STDOUT, '>', "typemap") || die "$0: error writing typemap: $!\n";

my $skipping = 0;
while ( <> ) {

    if (/typedef\s+struct\s+tag(\w+)/) {
        # entry into a new type
        $type = $1;

	#
	# We skip some types not used for MQI calls, or ahndled
	# manually instead of typemaps
	#
	if (grep { $type eq $_ } 
	    qw(MQCFIN MQCFIN64 MQIMPO MQWDR MQWDR2 MQXEPO)) {
	    $skipping = 1;
	    next;
	}
	$skipping = 0;

        print "#we're starting a $type\n" if $debug;
        $input{$type} = &input_header($type);
        $output{$type} = &output_header($type);
	next;
    } 
    next if ($skipping);

    if (/\}\s+(MQ\w+);/) {
        # exit of a type.  Probably should assert that $type2 eq $type
        $type2 = $1;
        print "#we're ending a $type ($type2)\n" if $debug;
        $input{$type} .= &input_footer($type);
        undef $type;
    } elsif ($type && /^\s*\}\s*;\s*$/) {
        # exit of a type in 5.3 headers
        print "#we're ending a $type\n" if $debug;
        $input{$type} .= &input_footer($type);
        undef $type;
    } elsif ($type && ($_ =~ /\s+(MQ\w+)\s+(\w+);/)) {
        # found a new field of type $fieldt and name $field:
        $fieldt = $1;
        $field = $2;

        next if $field eq "StrucId";
        print "#Found a $field of type $fieldt\n" if $debug;

        #
        # A list of known types we don't support.  Ignore them.
        #
        next if (grep { $fieldt eq $_ }
                 qw(MQPTR MQMD1 MQHCONN MQPOINTER MQPID MQTID MQHCONFIG
		    MQAXP MQCFH MQXEPO));

        if ($fieldt eq 'MQLONG' ||
            $fieldt eq 'MQHOBJ' ||
            $fieldt eq 'MQBOOL') {
            $strlen = length($field);

            $input{$type} .= &long_input($field);
            $output{$type} .= &long_output($field);

        } elsif ($fieldt eq 'MQHMSG') {
            # MQHMSG is a MQINT64, not a MQLONG (or MQULONG)
            $input{$type} .= &int64_input($field);
            $output{$type} .= &int64_output($field);

        } elsif ($fieldt =~ /^MQCHAR(\d*)$/) {
            $size = $1;

            if ($size == 0) {
                $size = 1;
            }
            $output{$type} .= &string_output($field, $size);
            $input{$type} .= &string_input($field, $size);
        } elsif ($fieldt =~ /^MQBYTE(\d*)$/) {
            $size = $1;

            if ($size == 0) {
                $size = 1;
            }
            $output{$type} .= &byte_output($field, $size);
            $input{$type} .= &byte_input($field, $size);
	} elsif ($fieldt eq 'MQCHARV') {
            $input{$type} .= &charv_input($field);
            $output{$type} .= &charv_output($field);
        } else {
            warn "$0: warning: type $type.$field ($fieldt) not supported\n";
        }

    }

}


print <<"EOF";
MQBYTE          T_U_CHAR
PMQBYTE         T_PV
MQBYTE16        T_PV
PMQBYTE16       T_PV
MQBYTE24        T_PV
PMQBYTE24       T_PV
MQBYTE32        T_PV
PMQBYTE32       T_PV
# Character Datatypes
MQCHAR          T_CHAR
PMQCHAR         T_PV
MQCHAR4         T_PV
PMQCHAR4        T_PV
MQCHAR8         T_PV
PMQCHAR8        T_PV
MQCHAR12        T_PV
PMQCHAR12       T_PV
MQCHAR28        T_PV
PMQCHAR28       T_PV
MQCHAR32        T_PV
PMQCHAR32       T_PV
MQCHAR48        T_MQCHAR48
PMQCHAR48       T_PV
MQCHAR64        T_PV
PMQCHAR64       T_PV
MQCHAR128       T_PV
PMQCHAR128      T_PV
MQCHAR256       T_PV
PMQCHAR256      T_PV
# Other Datatypes
MQLONG          T_IV
PMQLONG         T_OPAQUEPTR
MQULONG         T_UV
MQHCONN         T_IV
PMQHCONN        T_OPAQUEPTR
MQHOBJ          T_IV
PMQHOBJ         T_OPAQUEPTR
MQHMSG          T_IV
PMQHMSG         T_OPAQUEPTR
MQPTR           T_PV
PMQPTR          T_PV
PMQVOID         T_PV
PPMQVOID        T_PV

# Structures
MQAIR           T_MQAIR
MQBMHO          T_MQBMHO
MQBO            T_MQBO
MQCHARV         T_MQCHARV
MQCMHO          T_MQCMHO
MQCNO           T_MQCNO
MQDH            T_MQDH
MQDLH           T_MQDLH
MQDMHO          T_MQDMHO
MQDMPO          T_MQDMPO
MQGMO           T_MQGMO
MQIIH           T_MQIIH
MQMD            T_MQMD
MQMDE           T_MQMDE
MQMD1           T_MQMD1
MQMHBO          T_MQMHBO
MQOD            T_MQOD
MQOR            T_MQOR
MQPD            T_MQPD
MQPMO           T_MQPMO
MQRFH           T_MQRFH
MQRMH           T_MQRMH
MQRR            T_MQRR
MQSCO           T_MQSCO
MQSMPO          T_MQSMPO
MQSTS           T_MQSTS
MQTM            T_MQTM
MQTMC2          T_MQTMC2
MQXQH           T_MQXQH

MQCFH           T_MQCFH
MQCFIL          T_MQCFIL
MQCFIN          T_MQCFIN
MQCFSL          T_MQCFSL
MQCFST          T_MQCFST

# Input of the above types:
INPUT
EOF

foreach $type (sort keys %input) {
    print $input{$type}, "\n";
}

print "# Output of the above types:\nOUTPUT\n";
foreach $type (sort keys %output) {
    print $output{$type}, "\n";
}


#==============================================================================
sub long_input {
    my($field) = @_;
    my($strlen) = length($field);

    return <<"EOF";
        if (hv_exists((HV*)SvRV(\$arg),\\"$field\\",$strlen))
          \$var.$field = SvIV(*(hv_fetch((HV*)SvRV(\$arg),
                \\"$field\\",$strlen,0)));
EOF
}
#==============================================================================
sub long_output {
    my($field, $hash) = @_;
    my($strlen) = length($field);

    $hash = 0 unless $hash;
    return <<"EOF";
        hv_store((HV*)SvRV(\$arg),\\"$field\\",$strlen,
                 (newSViv(\$var.$field)),$hash);
EOF
}
#==============================================================================
sub int64_input {
    my($field) = @_;
    my($strlen) = length($field);

    return <<"EOF";
        if (hv_exists((HV*)SvRV(\$arg),\\"$field\\",$strlen)) {
          SV *Value = *(hv_fetch((HV*)SvRV(\$arg),\\"$field\\",$strlen,0));
          if (sizeof(IV) >= 8 || SvIOKp(Value)) {
            \$var.$field = (MQINT64)SvIV(Value);
          } else {
            char   *val;
            STRLEN  len;
            val = SvPV(Value, len);
            if (!sscanf(val, \\"%\\" SCNdLEAST64, &\$var.$field)) {
              croak(\\"Cannot turn string '%s' into a 64-bit number\\", val);
            }
          }
        }
EOF
}
#==============================================================================
sub int64_output {
    my($field, $hash) = @_;
    my($strlen) = length($field);

    $hash = 0 unless $hash;
    return <<"EOF";
        if (sizeof(IV) >= 8) {
          hv_store((HV*)SvRV(\$arg),\\"$field\\",$strlen,
                   (newSViv((IV)\$var.$field)),$hash);
        } else {
          char   printed_number[32];
          STRLEN len;
          sprintf(printed_number, \\"%\\" PRIdLEAST64, \$var.$field);
          len = strlen((const char *)printed_number);
          hv_store((HV*)SvRV(\$arg),\\"$field\\",$strlen,
                   (newSVpvn(printed_number,len)),$hash);
        }
EOF
}
#==============================================================================
sub string_output {
    my ($field, $fieldsize, $hash) = @_;
    my $fieldnamelen = length($field);
    my $isptr = '';

    $hash = 0 unless $hash;
    if ($fieldsize == 1) {
        $isptr = "&";
	return <<"EOF";
        hv_store((HV*)SvRV(\$arg),\\"$field\\",$fieldnamelen,
                 (newSVpv($isptr\$var.$field, $fieldsize)),$hash);
EOF
	;
    }

    #
    # Strings over 1 character: trim zeroes (i.e. only return actual
    # size used, which could be the empty string)
    #
    return <<"EOF";
        {
            STRLEN len = mqstrnlen($isptr\$var.$field, $fieldsize);
            hv_store((HV*)SvRV(\$arg),\\"$field\\",$fieldnamelen,
                     (newSVpv($isptr\$var.$field, len)),$hash);
        }
EOF
    ;
}
#==============================================================================
sub string_input {
    my($field, $fieldsize) = @_;
    my($fieldnamelen) = length($field);
    my($isptr);

    if ($fieldsize == 1) {
        $isptr = "&";
    }

    return <<"EOF";
        if (hv_exists((HV*)SvRV(\$arg), \\"$field\\", $fieldnamelen)) {
            SV        **scalar;
            STRLEN      StringLength;
            const char *StringData;

            /* Need to deal with undersized data.  Only copy supplied bytes. */
            scalar = hv_fetch((HV *)SvRV(\$arg), \\"$field\\", $fieldnamelen, 0);
            if (SvOK(*scalar)) { /* Defined */
                StringData = SvPV(*scalar, StringLength);
                strncpy($isptr\$var.$field,
                        StringData,
                        StringLength > $fieldsize ? $fieldsize : StringLength);
            }
        }


EOF
}
#==============================================================================
sub input_header {
    my($type) = @_;

    return <<"EOF";
/*===========================================================================*/
T_$type
    {
        /* input a $type */
        $type default_$type = { ${type}_DEFAULT };

        if (!SvROK(\$arg))
            croak(\\"Reference expected for parameter \$var\\");
        /* copy in the default value of structure... */
        memcpy((char *)&\$var, &default_$type, sizeof($type));

EOF
}
#==============================================================================
sub input_footer { "    }\n"; }
#==============================================================================
sub output_header {
    my($type) = @_;

    return <<"EOF";
/*===========================================================================*/
T_$type
    /* output a $type: */


EOF
}
#==============================================================================
sub byte_output {
    my($field, $fieldsize, $hash) = @_;
    my($fieldnamelen) = length($field);

    $hash = 0 unless $hash;
    return <<"EOF";
        hv_store((HV*)SvRV(\$arg),\\"$field\\",$fieldnamelen,
                 (newSVpv((char *)\$var.$field,$fieldsize)),$hash);
EOF
}
#==============================================================================
sub byte_input {
    my($field, $fieldsize) = @_;
    my($fieldnamelen) = length($field);

    return <<"EOF";
        if (hv_exists((HV*)SvRV(\$arg), \\"$field\\", $fieldnamelen)) {
            SV        **scalar;
            STRLEN      ByteLength;
            const char *ByteData;

            /* Need to deal with undersized data.  Only copy supplied bytes. */
            scalar = hv_fetch((HV *)SvRV(\$arg), \\"$field\\", $fieldnamelen, 0);
            ByteData = SvPV(*scalar, ByteLength);
            memcpy(\$var.$field,
                   ByteData,
                   ByteLength > $fieldsize ? $fieldsize : ByteLength);
        }
EOF
}
#==============================================================================
sub charv_output {
    my ($field) = @_;
    my $fieldnamelen = length($field);

    #
    # NOTE: this function assumes that the return value uses a buffer
    #       on the stack (or a static) in the XS code, not a
    #       dynamically allocated value.  See the MQINQMP XS code for
    #       an example.
    #
    return <<"EOF";
        {
            char    *ptr;
            STRLEN  len;

            if (\$var.$field.VSPtr) {
                ptr = \$var.$field.VSPtr;
            } else {
                ptr = (char *)&\$var.$field;
                ptr += \$var.$field.VSOffset;
            }
            if (\$var.$field.VSLength == MQVS_NULL_TERMINATED) {
                len = strlen(ptr);
            } else {
                len = \$var.$field.VSLength;
            }
            hv_store((HV*)SvRV(\$arg),\\"$field\\",$fieldnamelen,
                     (newSVpv(ptr,len)),0);
        }
EOF
}
#==============================================================================
sub charv_input {
    my ($field) = @_;
    my $fieldnamelen = length($field);

    return <<"EOF";
        if (hv_exists((HV*)SvRV(\$arg), \\"$field\\", $fieldnamelen)) {
            SV     **scalar;
	    char    *val;
            STRLEN   len;

            scalar = hv_fetch((HV *)SvRV(\$arg), \\"$field\\", $fieldnamelen, 0);
	    val = SvPV(*scalar, len);
	    if (len) {
	        \$var.$field.VSPtr = val;
		\$var.$field.VSLength = len;
		\$var.$field.VSOffset = 0;
		\$var.$field.VSBufSize  = 0;
		\$var.$field.VSCCSID = MQCCSI_APPL; /* Maybe UTF-8 */
	    }
        }
EOF
}
