Las directivas INPUT:, PREINIT: y CLEANUP:

El módulo Scalar::Util::Numeric proporciona un conjunto de funciones para comprobar si el argumento es un número o si es un número de un tipo dado. Veamos un ejemplo de uso:

lhp@nereida:~/Lperl/src/XSUB/cpanexamples/Scalar-Util-Numeric-0.02$ perl -de 0
main::(-e:1):   0
  DB<1> use Scalar::Util::Numeric qw(:all)
  DB<2> x isint('432')
0  1
  DB<3> x isint('432b')
0  0
  DB<4> x isneg('-53')
0  1
  DB<5> x isneg('53')
0  0
  DB<15> x isbig ((2.0)**32)
0  1
  DB<16> x isbig ((2.0)**31)
0  0
Para proporcionar estas funciones el módulo provee via XS una función is_num la cual utiliza la función de la Perl API looks_like_number para estudiar el tipo del número.
lhp@nereida:~/Lperl/src/perlcompilerssource/perl-5.8.8$ grep 'looks_like_number(' embed.h
#define looks_like_number(a)    Perl_looks_like_number(aTHX_ a)
lhp@nereida:~/Lperl/src/perlcompilerssource/perl-5.8.8$ sed -ne '1918,1932p' sv.c
Perl_looks_like_number(pTHX_ SV *sv)
{
    register const char *sbegin;
    STRLEN len;

    if (SvPOK(sv)) {
        sbegin = SvPVX_const(sv);
        len = SvCUR(sv);
    }
    else if (SvPOKp(sv))
        sbegin = SvPV_const(sv, len);
    else
        return SvFLAGS(sv) & (SVf_NOK|SVp_NOK|SVf_IOK|SVp_IOK);
    return grok_number(sbegin, len, NULL);
}
Las funciones en el código Perl del módulo usan un sistema de flags para determinar el tipo del número. Las banderas funciona según la siguiente tabla:



Valor Nombre Descripción
0x01 IS_NUMBER_IN_UV Número dentro del rango UV. No necesariametne un entero
0x02 IS_NUMBER_GREATER_THAN_UV_MAX El número es mayor que UV_MAX)
0x04 IS_NUMBER_NOT_INT Por ej . o E
0x08 IS_NUMBER_NEG Signo menos
0x10 IS_NUMBER_INFINITY Infinito
0x20 IS_NUMBER_NAN NaN not a number


Veamos el código Perl:

lhp@nereida:~/Lperl/src/XSUB/cpanexamples/Scalar-Util-Numeric-0.02$ cat -n \
                                                lib/Scalar/Util/Numeric.pm
  1  package Scalar::Util::Numeric;
  2
 ..  ....
 24  1;
 25
 26  __END__
 ..  ........
 91  sub isnum ($) {
 92      return 0 unless defined (my $val = shift);
 93      # stringify - ironically, looks_like_number always returns 1 unless
 94      # arg is a string
 95      return is_num($val . '');
 96  }
 97
 98  sub isint ($) {
 99      my $isnum = isnum(shift());
100      return ($isnum == 1) ? 1 : ($isnum == 9) ? -1 : 0;
101  }
102
103  sub isuv ($) {
104      return (isnum(shift()) & 1) ? 1 : 0;
105  }
106
107  sub isbig ($) {
108      return (isnum(shift()) & 2) ? 1 : 0;
109  }
110
111  sub isfloat ($) {
112      return (isnum(shift()) & 4) ? 1 : 0;
113  }
114
115  sub isneg ($) {
116      return (isnum(shift()) & 8) ? 1 : 0;
117  }
118
119  sub isinf ($) {
120      return (isnum(shift()) & 16) ? 1 : 0;
121  }
122
123  sub isnan ($) {
124      return (isnum(shift()) & 32) ? 1 : 0;
125  }
Pasemos a estudiar el código XS:
lhp@nereida:~/Lperl/src/XSUB/cpanexamples/Scalar-Util-Numeric-0.02$ cat -n Numeric.xs
 1  #include "EXTERN.h"
 2  #include "perl.h"
 3  #include "XSUB.h"
 4  #include "ppport.h"
 5
 6  MODULE = Scalar::Util::Numeric          PACKAGE = Scalar::Util::Numeric
 7
 8  void
 9  is_num(sv)
10      SV *sv
11      PROTOTYPE: $
12      PREINIT:
13      I32 num = 0;
14      CODE:
15
16      if (!(SvROK(sv) || (sv == (SV *)&PL_sv_undef))) {
17                  num = looks_like_number(sv);
18      }
19
20      XSRETURN_IV(num);
21
22  void
23  uvmax()
24      PROTOTYPE:
25      CODE:
26      XSRETURN_UV(UV_MAX);

La directiva PREINIT: permite la declaración adicional de variables.

Si una variable es declarada dentro de una sección CODE: su declaración ocurre después de la emisión del código typemap para los parámetros de entrada. Ello podría dar lugar a un error sintáctico. Una sección PREINIT: permite forzar una correcta declaración de variables. Es posible usar mas de una sección PREINIT: dentro de una XSUB. Podemos hacer que las declaraciones e inicializaciones PREINIT: precedan al código typemap para los parámetros de entrada. Podríamos reorganizar el ejemplo anterior como sigue

lhp@nereida:~/Lperl/src/XSUB/cpanexamples/Scalar-Util-Numeric-0.02$ sed -ne '8,21p'  Numeric.xs | cat -n
  1  void
  2  is_num(sv)
  3      PREINIT:
  4      I32 num = 0;
  5      INPUT:
  6      SV *sv
  7      PROTOTYPE: $
  8      CODE:
  9
 10      if (!(SvROK(sv) || (sv == (SV *)&PL_sv_undef))) {
 11                  num = looks_like_number(sv);
 12      }
 13
 14      XSRETURN_IV(num);
El código generado muestra que la declaración I32 num = 0 ocurre antes del código typemap:
lhp@nereida:~/Lperl/src/XSUB/cpanexamples/Scalar-Util-Numeric-0.02$ sed -ne '18,37p' Numeric.c | cat -n
  1  XS(XS_Scalar__Util__Numeric_is_num)
  2  {
  3      dXSARGS;
  4      if (items != 1)
  5          Perl_croak(aTHX_ "Usage: Scalar::Util::Numeric::is_num(sv)");
  6      {
  7  #line 11 "Numeric.xs"
  8      I32 num = 0;
  9  #line 27 "Numeric.c"
 10          SV *    sv = ST(0);
 11  #line 17 "Numeric.xs"
 12      if (!(SvROK(sv) || (sv == (SV *)&PL_sv_undef))) {
 13                  num = looks_like_number(sv);
 14      }
 15
 16      XSRETURN_IV(num);
 17  #line 35 "Numeric.c"
 18      }
 19      XSRETURN_EMPTY;
 20  }
Esta capacidad para añadir declaraciones antes de la ejecución del código typemap -cuando se combina con la directiva CLEANUP: - puede ser útil para salvar el estado de variables globales que sean modificadas por el código typemap (si es el caso):
typetutu 
myfun(x)
  PREINIT:
    saveglob = globalvar;
  INPUT:
    typex x;
  CODE:
    ...
  CLEANUP:
    globalvar = saveglob;

Normalmente los parámetros de una XSUB son evaluados tan pronto como se entra en la XSUB. Como se muestra en el ejemplo, la directiva INPUT: puede usarse conjuntamente con PREINIT: para forzar el retraso en la evaluación de los parámetros.

La directiva CLEANUP: permite la introducción de código que se ejecutará antes que la XSUB termine. Debe colocarse después de cualesquiera de las directivas CODE:, PPCODE: y OUTPUT: que

Casiano Rodríguez León
2012-02-29