xref: /openbsd-src/gnu/usr.bin/perl/dist/ExtUtils-ParseXS/lib/ExtUtils/ParseXS/Utilities.pm (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1package ExtUtils::ParseXS::Utilities;
2use strict;
3use warnings;
4use Exporter;
5use File::Spec;
6use ExtUtils::ParseXS::Constants ();
7
8our $VERSION = '3.19';
9
10our (@ISA, @EXPORT_OK);
11@ISA = qw(Exporter);
12@EXPORT_OK = qw(
13  standard_typemap_locations
14  trim_whitespace
15  tidy_type
16  C_string
17  valid_proto_string
18  process_typemaps
19  make_targetable
20  map_type
21  standard_XS_defs
22  assign_func_args
23  analyze_preprocessor_statements
24  set_cond
25  Warn
26  current_line_number
27  blurt
28  death
29  check_conditional_preprocessor_statements
30  escape_file_for_line_directive
31  report_typemap_failure
32);
33
34=head1 NAME
35
36ExtUtils::ParseXS::Utilities - Subroutines used with ExtUtils::ParseXS
37
38=head1 SYNOPSIS
39
40  use ExtUtils::ParseXS::Utilities qw(
41    standard_typemap_locations
42    trim_whitespace
43    tidy_type
44    C_string
45    valid_proto_string
46    process_typemaps
47    make_targetable
48    map_type
49    standard_XS_defs
50    assign_func_args
51    analyze_preprocessor_statements
52    set_cond
53    Warn
54    blurt
55    death
56    check_conditional_preprocessor_statements
57    escape_file_for_line_directive
58    report_typemap_failure
59  );
60
61=head1 SUBROUTINES
62
63The following functions are not considered to be part of the public interface.
64They are documented here for the benefit of future maintainers of this module.
65
66=head2 C<standard_typemap_locations()>
67
68=over 4
69
70=item * Purpose
71
72Provide a list of filepaths where F<typemap> files may be found.  The
73filepaths -- relative paths to files (not just directory paths) -- appear in this list in lowest-to-highest priority.
74
75The highest priority is to look in the current directory.
76
77  'typemap'
78
79The second and third highest priorities are to look in the parent of the
80current directory and a directory called F<lib/ExtUtils> underneath the parent
81directory.
82
83  '../typemap',
84  '../lib/ExtUtils/typemap',
85
86The fourth through ninth highest priorities are to look in the corresponding
87grandparent, great-grandparent and great-great-grandparent directories.
88
89  '../../typemap',
90  '../../lib/ExtUtils/typemap',
91  '../../../typemap',
92  '../../../lib/ExtUtils/typemap',
93  '../../../../typemap',
94  '../../../../lib/ExtUtils/typemap',
95
96The tenth and subsequent priorities are to look in directories named
97F<ExtUtils> which are subdirectories of directories found in C<@INC> --
98I<provided> a file named F<typemap> actually exists in such a directory.
99Example:
100
101  '/usr/local/lib/perl5/5.10.1/ExtUtils/typemap',
102
103However, these filepaths appear in the list returned by
104C<standard_typemap_locations()> in reverse order, I<i.e.>, lowest-to-highest.
105
106  '/usr/local/lib/perl5/5.10.1/ExtUtils/typemap',
107  '../../../../lib/ExtUtils/typemap',
108  '../../../../typemap',
109  '../../../lib/ExtUtils/typemap',
110  '../../../typemap',
111  '../../lib/ExtUtils/typemap',
112  '../../typemap',
113  '../lib/ExtUtils/typemap',
114  '../typemap',
115  'typemap'
116
117=item * Arguments
118
119  my @stl = standard_typemap_locations( \@INC );
120
121Reference to C<@INC>.
122
123=item * Return Value
124
125Array holding list of directories to be searched for F<typemap> files.
126
127=back
128
129=cut
130
131sub standard_typemap_locations {
132  my $include_ref = shift;
133  my @tm = qw(typemap);
134
135  my $updir = File::Spec->updir();
136  foreach my $dir (
137      File::Spec->catdir(($updir) x 1),
138      File::Spec->catdir(($updir) x 2),
139      File::Spec->catdir(($updir) x 3),
140      File::Spec->catdir(($updir) x 4),
141  ) {
142    unshift @tm, File::Spec->catfile($dir, 'typemap');
143    unshift @tm, File::Spec->catfile($dir, lib => ExtUtils => 'typemap');
144  }
145  foreach my $dir (@{ $include_ref}) {
146    my $file = File::Spec->catfile($dir, ExtUtils => 'typemap');
147    unshift @tm, $file if -e $file;
148  }
149  return @tm;
150}
151
152=head2 C<trim_whitespace()>
153
154=over 4
155
156=item * Purpose
157
158Perform an in-place trimming of leading and trailing whitespace from the
159first argument provided to the function.
160
161=item * Argument
162
163  trim_whitespace($arg);
164
165=item * Return Value
166
167None.  Remember:  this is an I<in-place> modification of the argument.
168
169=back
170
171=cut
172
173sub trim_whitespace {
174  $_[0] =~ s/^\s+|\s+$//go;
175}
176
177=head2 C<tidy_type()>
178
179=over 4
180
181=item * Purpose
182
183Rationalize any asterisks (C<*>) by joining them into bunches, removing
184interior whitespace, then trimming leading and trailing whitespace.
185
186=item * Arguments
187
188    ($ret_type) = tidy_type($_);
189
190String to be cleaned up.
191
192=item * Return Value
193
194String cleaned up.
195
196=back
197
198=cut
199
200sub tidy_type {
201  local ($_) = @_;
202
203  # rationalise any '*' by joining them into bunches and removing whitespace
204  s#\s*(\*+)\s*#$1#g;
205  s#(\*+)# $1 #g;
206
207  # change multiple whitespace into a single space
208  s/\s+/ /g;
209
210  # trim leading & trailing whitespace
211  trim_whitespace($_);
212
213  $_;
214}
215
216=head2 C<C_string()>
217
218=over 4
219
220=item * Purpose
221
222Escape backslashes (C<\>) in prototype strings.
223
224=item * Arguments
225
226      $ProtoThisXSUB = C_string($_);
227
228String needing escaping.
229
230=item * Return Value
231
232Properly escaped string.
233
234=back
235
236=cut
237
238sub C_string {
239  my($string) = @_;
240
241  $string =~ s[\\][\\\\]g;
242  $string;
243}
244
245=head2 C<valid_proto_string()>
246
247=over 4
248
249=item * Purpose
250
251Validate prototype string.
252
253=item * Arguments
254
255String needing checking.
256
257=item * Return Value
258
259Upon success, returns the same string passed as argument.
260
261Upon failure, returns C<0>.
262
263=back
264
265=cut
266
267sub valid_proto_string {
268  my($string) = @_;
269
270  if ( $string =~ /^$ExtUtils::ParseXS::Constants::PrototypeRegexp+$/ ) {
271    return $string;
272  }
273
274  return 0;
275}
276
277=head2 C<process_typemaps()>
278
279=over 4
280
281=item * Purpose
282
283Process all typemap files.
284
285=item * Arguments
286
287  my $typemaps_object = process_typemaps( $args{typemap}, $pwd );
288
289List of two elements:  C<typemap> element from C<%args>; current working
290directory.
291
292=item * Return Value
293
294Upon success, returns an L<ExtUtils::Typemaps> object.
295
296=back
297
298=cut
299
300sub process_typemaps {
301  my ($tmap, $pwd) = @_;
302
303  my @tm = ref $tmap ? @{$tmap} : ($tmap);
304
305  foreach my $typemap (@tm) {
306    die "Can't find $typemap in $pwd\n" unless -r $typemap;
307  }
308
309  push @tm, standard_typemap_locations( \@INC );
310
311  require ExtUtils::Typemaps;
312  my $typemap = ExtUtils::Typemaps->new;
313  foreach my $typemap_loc (@tm) {
314    next unless -f $typemap_loc;
315    # skip directories, binary files etc.
316    warn("Warning: ignoring non-text typemap file '$typemap_loc'\n"), next
317      unless -T $typemap_loc;
318
319    $typemap->merge(file => $typemap_loc, replace => 1);
320  }
321
322  return $typemap;
323}
324
325=head2 C<make_targetable()>
326
327=over 4
328
329=item * Purpose
330
331Populate C<%targetable>.  This constitutes a refinement of the output of
332C<process_typemaps()> with respect to its fourth output, C<$output_expr_ref>.
333
334=item * Arguments
335
336  %targetable = make_targetable($output_expr_ref);
337
338Single hash reference:  the fourth such ref returned by C<process_typemaps()>.
339
340=item * Return Value
341
342Hash.
343
344=back
345
346=cut
347
348sub make_targetable {
349  my $output_expr_ref = shift;
350
351  our $bal; # ()-balanced
352  $bal = qr[
353    (?:
354      (?>[^()]+)
355      |
356      \( (??{ $bal }) \)
357    )*
358  ]x;
359
360  # matches variations on (SV*)
361  my $sv_cast = qr[
362    (?:
363      \( \s* SV \s* \* \s* \) \s*
364    )?
365  ]x;
366
367  my $size = qr[ # Third arg (to setpvn)
368    , \s* (??{ $bal })
369  ]x;
370
371  my %targetable;
372  foreach my $key (keys %{ $output_expr_ref }) {
373    # We can still bootstrap compile 're', because in code re.pm is
374    # available to miniperl, and does not attempt to load the XS code.
375    use re 'eval';
376
377    my ($type, $with_size, $arg, $sarg) =
378      ($output_expr_ref->{$key} =~
379        m[^
380          \s+
381          sv_set([iunp])v(n)?    # Type, is_setpvn
382          \s*
383          \( \s*
384            $sv_cast \$arg \s* , \s*
385            ( (??{ $bal }) )    # Set from
386          ( (??{ $size }) )?    # Possible sizeof set-from
387          \) \s* ; \s* $
388        ]x
389    );
390    $targetable{$key} = [$type, $with_size, $arg, $sarg] if $type;
391  }
392  return %targetable;
393}
394
395=head2 C<map_type()>
396
397=over 4
398
399=item * Purpose
400
401Performs a mapping at several places inside C<PARAGRAPH> loop.
402
403=item * Arguments
404
405  $type = map_type($self, $type, $varname);
406
407List of three arguments.
408
409=item * Return Value
410
411String holding augmented version of second argument.
412
413=back
414
415=cut
416
417sub map_type {
418  my ($self, $type, $varname) = @_;
419
420  # C++ has :: in types too so skip this
421  $type =~ tr/:/_/ unless $self->{hiertype};
422  $type =~ s/^array\(([^,]*),(.*)\).*/$1 */s;
423  if ($varname) {
424    if ($type =~ / \( \s* \* (?= \s* \) ) /xg) {
425      (substr $type, pos $type, 0) = " $varname ";
426    }
427    else {
428      $type .= "\t$varname";
429    }
430  }
431  return $type;
432}
433
434=head2 C<standard_XS_defs()>
435
436=over 4
437
438=item * Purpose
439
440Writes to the C<.c> output file certain preprocessor directives and function
441headers needed in all such files.
442
443=item * Arguments
444
445None.
446
447=item * Return Value
448
449Returns true.
450
451=back
452
453=cut
454
455sub standard_XS_defs {
456  print <<"EOF";
457#ifndef PERL_UNUSED_VAR
458#  define PERL_UNUSED_VAR(var) if (0) var = var
459#endif
460
461#ifndef dVAR
462#  define dVAR		dNOOP
463#endif
464
465
466/* This stuff is not part of the API! You have been warned. */
467#ifndef PERL_VERSION_DECIMAL
468#  define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
469#endif
470#ifndef PERL_DECIMAL_VERSION
471#  define PERL_DECIMAL_VERSION \\
472	  PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
473#endif
474#ifndef PERL_VERSION_GE
475#  define PERL_VERSION_GE(r,v,s) \\
476	  (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
477#endif
478#ifndef PERL_VERSION_LE
479#  define PERL_VERSION_LE(r,v,s) \\
480	  (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
481#endif
482
483/* XS_INTERNAL is the explicit static-linkage variant of the default
484 * XS macro.
485 *
486 * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
487 * "STATIC", ie. it exports XSUB symbols. You probably don't want that
488 * for anything but the BOOT XSUB.
489 *
490 * See XSUB.h in core!
491 */
492
493
494/* TODO: This might be compatible further back than 5.10.0. */
495#if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
496#  undef XS_EXTERNAL
497#  undef XS_INTERNAL
498#  if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
499#    define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
500#    define XS_INTERNAL(name) STATIC XSPROTO(name)
501#  endif
502#  if defined(__SYMBIAN32__)
503#    define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
504#    define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
505#  endif
506#  ifndef XS_EXTERNAL
507#    if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
508#      define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
509#      define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
510#    else
511#      ifdef __cplusplus
512#        define XS_EXTERNAL(name) extern "C" XSPROTO(name)
513#        define XS_INTERNAL(name) static XSPROTO(name)
514#      else
515#        define XS_EXTERNAL(name) XSPROTO(name)
516#        define XS_INTERNAL(name) STATIC XSPROTO(name)
517#      endif
518#    endif
519#  endif
520#endif
521
522/* perl >= 5.10.0 && perl <= 5.15.1 */
523
524
525/* The XS_EXTERNAL macro is used for functions that must not be static
526 * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
527 * macro defined, the best we can do is assume XS is the same.
528 * Dito for XS_INTERNAL.
529 */
530#ifndef XS_EXTERNAL
531#  define XS_EXTERNAL(name) XS(name)
532#endif
533#ifndef XS_INTERNAL
534#  define XS_INTERNAL(name) XS(name)
535#endif
536
537/* Now, finally, after all this mess, we want an ExtUtils::ParseXS
538 * internal macro that we're free to redefine for varying linkage due
539 * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
540 * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
541 */
542
543#undef XS_EUPXS
544#if defined(PERL_EUPXS_ALWAYS_EXPORT)
545#  define XS_EUPXS(name) XS_EXTERNAL(name)
546#else
547   /* default to internal */
548#  define XS_EUPXS(name) XS_INTERNAL(name)
549#endif
550
551EOF
552
553  print <<"EOF";
554#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
555#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
556
557/* prototype to pass -Wmissing-prototypes */
558STATIC void
559S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params);
560
561STATIC void
562S_croak_xs_usage(pTHX_ const CV *const cv, const char *const params)
563{
564    const GV *const gv = CvGV(cv);
565
566    PERL_ARGS_ASSERT_CROAK_XS_USAGE;
567
568    if (gv) {
569        const char *const gvname = GvNAME(gv);
570        const HV *const stash = GvSTASH(gv);
571        const char *const hvname = stash ? HvNAME(stash) : NULL;
572
573        if (hvname)
574            Perl_croak(aTHX_ "Usage: %s::%s(%s)", hvname, gvname, params);
575        else
576            Perl_croak(aTHX_ "Usage: %s(%s)", gvname, params);
577    } else {
578        /* Pants. I don't think that it should be possible to get here. */
579        Perl_croak(aTHX_ "Usage: CODE(0x%"UVxf")(%s)", PTR2UV(cv), params);
580    }
581}
582#undef  PERL_ARGS_ASSERT_CROAK_XS_USAGE
583
584#ifdef PERL_IMPLICIT_CONTEXT
585#define croak_xs_usage(a,b)    S_croak_xs_usage(aTHX_ a,b)
586#else
587#define croak_xs_usage        S_croak_xs_usage
588#endif
589
590#endif
591
592/* NOTE: the prototype of newXSproto() is different in versions of perls,
593 * so we define a portable version of newXSproto()
594 */
595#ifdef newXS_flags
596#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
597#else
598#define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
599#endif /* !defined(newXS_flags) */
600
601EOF
602  return 1;
603}
604
605=head2 C<assign_func_args()>
606
607=over 4
608
609=item * Purpose
610
611Perform assignment to the C<func_args> attribute.
612
613=item * Arguments
614
615  $string = assign_func_args($self, $argsref, $class);
616
617List of three elements.  Second is an array reference; third is a string.
618
619=item * Return Value
620
621String.
622
623=back
624
625=cut
626
627sub assign_func_args {
628  my ($self, $argsref, $class) = @_;
629  my @func_args = @{$argsref};
630  shift @func_args if defined($class);
631
632  for my $arg (@func_args) {
633    $arg =~ s/^/&/ if $self->{in_out}->{$arg};
634  }
635  return join(", ", @func_args);
636}
637
638=head2 C<analyze_preprocessor_statements()>
639
640=over 4
641
642=item * Purpose
643
644Within each function inside each Xsub, print to the F<.c> output file certain
645preprocessor statements.
646
647=item * Arguments
648
649      ( $self, $XSS_work_idx, $BootCode_ref ) =
650        analyze_preprocessor_statements(
651          $self, $statement, $XSS_work_idx, $BootCode_ref
652        );
653
654List of four elements.
655
656=item * Return Value
657
658Modifed values of three of the arguments passed to the function.  In
659particular, the C<XSStack> and C<InitFileCode> attributes are modified.
660
661=back
662
663=cut
664
665sub analyze_preprocessor_statements {
666  my ($self, $statement, $XSS_work_idx, $BootCode_ref) = @_;
667
668  if ($statement eq 'if') {
669    $XSS_work_idx = @{ $self->{XSStack} };
670    push(@{ $self->{XSStack} }, {type => 'if'});
671  }
672  else {
673    $self->death("Error: '$statement' with no matching 'if'")
674      if $self->{XSStack}->[-1]{type} ne 'if';
675    if ($self->{XSStack}->[-1]{varname}) {
676      push(@{ $self->{InitFileCode} }, "#endif\n");
677      push(@{ $BootCode_ref },     "#endif");
678    }
679
680    my(@fns) = keys %{$self->{XSStack}->[-1]{functions}};
681    if ($statement ne 'endif') {
682      # Hide the functions defined in other #if branches, and reset.
683      @{$self->{XSStack}->[-1]{other_functions}}{@fns} = (1) x @fns;
684      @{$self->{XSStack}->[-1]}{qw(varname functions)} = ('', {});
685    }
686    else {
687      my($tmp) = pop(@{ $self->{XSStack} });
688      0 while (--$XSS_work_idx
689           && $self->{XSStack}->[$XSS_work_idx]{type} ne 'if');
690      # Keep all new defined functions
691      push(@fns, keys %{$tmp->{other_functions}});
692      @{$self->{XSStack}->[$XSS_work_idx]{functions}}{@fns} = (1) x @fns;
693    }
694  }
695  return ($self, $XSS_work_idx, $BootCode_ref);
696}
697
698=head2 C<set_cond()>
699
700=over 4
701
702=item * Purpose
703
704=item * Arguments
705
706=item * Return Value
707
708=back
709
710=cut
711
712sub set_cond {
713  my ($ellipsis, $min_args, $num_args) = @_;
714  my $cond;
715  if ($ellipsis) {
716    $cond = ($min_args ? qq(items < $min_args) : 0);
717  }
718  elsif ($min_args == $num_args) {
719    $cond = qq(items != $min_args);
720  }
721  else {
722    $cond = qq(items < $min_args || items > $num_args);
723  }
724  return $cond;
725}
726
727=head2 C<current_line_number()>
728
729=over 4
730
731=item * Purpose
732
733Figures out the current line number in the XS file.
734
735=item * Arguments
736
737C<$self>
738
739=item * Return Value
740
741The current line number.
742
743=back
744
745=cut
746
747sub current_line_number {
748  my $self = shift;
749  my $line_number = $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} } -1];
750  return $line_number;
751}
752
753=head2 C<Warn()>
754
755=over 4
756
757=item * Purpose
758
759=item * Arguments
760
761=item * Return Value
762
763=back
764
765=cut
766
767sub Warn {
768  my $self = shift;
769  my $warn_line_number = $self->current_line_number();
770  print STDERR "@_ in $self->{filename}, line $warn_line_number\n";
771}
772
773=head2 C<blurt()>
774
775=over 4
776
777=item * Purpose
778
779=item * Arguments
780
781=item * Return Value
782
783=back
784
785=cut
786
787sub blurt {
788  my $self = shift;
789  $self->Warn(@_);
790  $self->{errors}++
791}
792
793=head2 C<death()>
794
795=over 4
796
797=item * Purpose
798
799=item * Arguments
800
801=item * Return Value
802
803=back
804
805=cut
806
807sub death {
808  my $self = shift;
809  $self->Warn(@_);
810  exit 1;
811}
812
813=head2 C<check_conditional_preprocessor_statements()>
814
815=over 4
816
817=item * Purpose
818
819=item * Arguments
820
821=item * Return Value
822
823=back
824
825=cut
826
827sub check_conditional_preprocessor_statements {
828  my ($self) = @_;
829  my @cpp = grep(/^\#\s*(?:if|e\w+)/, @{ $self->{line} });
830  if (@cpp) {
831    my $cpplevel;
832    for my $cpp (@cpp) {
833      if ($cpp =~ /^\#\s*if/) {
834        $cpplevel++;
835      }
836      elsif (!$cpplevel) {
837        $self->Warn("Warning: #else/elif/endif without #if in this function");
838        print STDERR "    (precede it with a blank line if the matching #if is outside the function)\n"
839          if $self->{XSStack}->[-1]{type} eq 'if';
840        return;
841      }
842      elsif ($cpp =~ /^\#\s*endif/) {
843        $cpplevel--;
844      }
845    }
846    $self->Warn("Warning: #if without #endif in this function") if $cpplevel;
847  }
848}
849
850=head2 C<escape_file_for_line_directive()>
851
852=over 4
853
854=item * Purpose
855
856Escapes a given code source name (typically a file name but can also
857be a command that was read from) so that double-quotes and backslashes are escaped.
858
859=item * Arguments
860
861A string.
862
863=item * Return Value
864
865A string with escapes for double-quotes and backslashes.
866
867=back
868
869=cut
870
871sub escape_file_for_line_directive {
872  my $string = shift;
873  $string =~ s/\\/\\\\/g;
874  $string =~ s/"/\\"/g;
875  return $string;
876}
877
878=head2 C<report_typemap_failure>
879
880=over 4
881
882=item * Purpose
883
884Do error reporting for missing typemaps.
885
886=item * Arguments
887
888The C<ExtUtils::ParseXS> object.
889
890An C<ExtUtils::Typemaps> object.
891
892The string that represents the C type that was not found in the typemap.
893
894Optionally, the string C<death> or C<blurt> to choose
895whether the error is immediately fatal or not. Default: C<blurt>
896
897=item * Return Value
898
899Returns nothing. Depending on the arguments, this
900may call C<death> or C<blurt>, the former of which is
901fatal.
902
903=back
904
905=cut
906
907sub report_typemap_failure {
908  my ($self, $tm, $ctype, $error_method) = @_;
909  $error_method ||= 'blurt';
910
911  my @avail_ctypes = $tm->list_mapped_ctypes;
912
913  my $err = "Could not find a typemap for C type '$ctype'.\n"
914            . "The following C types are mapped by the current typemap:\n'"
915            . join("', '", @avail_ctypes) . "'\n";
916
917  $self->$error_method($err);
918  return();
919}
920
9211;
922
923# vim: ts=2 sw=2 et:
924