blob: 5548cca8cd311f64b366fe291aa223bd6c0d2b9d [file] [log] [blame]
# please insert nothing before this line: -*- mode: cperl; cperl-indent-level: 4; cperl-continued-statement-offset: 4; indent-tabs-mode: nil -*-
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
package ModPerl::TypeMap;
use strict;
use warnings FATAL => 'all';
use ModPerl::FunctionMap ();
use ModPerl::StructureMap ();
use ModPerl::MapUtil qw(list_first);
our @ISA = qw(ModPerl::MapBase);
sub new {
my $class = shift;
my $self = bless {
INCLUDE => [],
struct => [],
typedef => [],
}, $class;
$self->{function_map} = ModPerl::FunctionMap->new,
$self->{structure_map} = ModPerl::StructureMap->new,
$self->get;
$self;
}
my %special = map { $_, 1 } qw(UNDEFINED NOTIMPL CALLBACK);
sub special {
my ($self, $class) = @_;
return $special{$class};
}
sub function_map { shift->{function_map}->get }
sub structure_map { shift->{structure_map}->get }
sub parse {
my ($self, $fh, $map) = @_;
while ($fh->readline) {
if (/E=/) {
my %args = $self->parse_keywords($_);
while (my ($key,$val) = each %args) {
push @{ $self->{$key} }, $val;
}
next;
}
my @aliases;
my ($type, $class) = (split /\s*\|\s*/, $_)[0,1];
$class ||= 'UNDEFINED';
if ($type =~ s/^(struct|typedef)\s+(.*)/$2/) {
my $typemap = $1;
push @aliases, $type;
if ($typemap eq 'struct') {
push @aliases, "const $type", "$type *", "const $type *",
"struct $type *", "const struct $type *",
"$type **";
}
my $cname = $class;
if ($cname =~ s/::/__/g) {
push @{ $self->{$typemap} }, [$type, $cname];
}
}
elsif ($type =~ /_t$/) {
push @aliases, $type, "$type *", "const $type *";
}
else {
push @aliases, $type;
}
for (@aliases) {
$map->{$_} = $class;
}
}
}
sub get {
my $self = shift;
$self->{map} ||= $self->parse_map_files;
}
my $ignore = join '|', qw{
ap_LINK ap_HOOK _ UINT union._
union.block_hdr cleanup process_chain
iovec struct.rlimit Sigfunc in_addr_t
};
sub should_ignore {
my ($self, $type) = @_;
return 1 if $type =~ /^($ignore)/o;
}
sub is_callback {
my ($self, $type) = @_;
return 1 if $type =~ /\(/ and $type =~ /\)/; #XXX: callback
}
sub exists {
my ($self, $type) = @_;
return 1 if $self->is_callback($type) || $self->should_ignore($type);
$type =~ s/\[\d+\]$//; #char foo[64]
return exists $self->get->{$type};
}
sub map_type {
my ($self, $type) = @_;
my $class = $self->get->{$type};
return unless $class and ! $self->special($class);
# return if $type =~ /\*\*$/; #XXX
if ($class =~ /::/) {
return $class;
}
else {
return $type;
}
}
sub null_type {
my ($self, $type) = @_;
my $class = $self->get->{$type};
if ($class =~ /^[INU]V/) {
return '0';
}
else {
return 'NULL';
}
}
sub can_map {
my $self = shift;
my $map = shift;
return 1 if $map->{argspec};
for (@_) {
return (0, $_) unless $self->map_type($_);
}
return 1;
}
sub map_arg {
my ($self, $arg) = @_;
my $map_type = $self->map_type($arg->{type});
die "unknown typemap: '$arg->{type}'" unless defined $map_type;
return {
name => $arg->{name},
default => $arg->{default},
type => $map_type,
rtype => $arg->{type},
}
}
sub map_args {
my ($self, $func) = @_;
my $entry = $self->function_map->{ $func->{name} };
my $argspec = $entry->{argspec};
my $args = [];
if ($argspec) {
$entry->{orig_args} = [ map $_->{name}, @{ $func->{args} } ];
for my $arg (@$argspec) {
my $default;
($arg, $default) = split /=/, $arg, 2;
my ($type, $name) = split ':', $arg, 2;
if ($type and $name) {
push @$args, {
name => $name,
type => $type,
default => $default,
};
}
else {
my $e = list_first { $_->{name} eq $arg } @{ $func->{args} };
if ($e) {
push @$args, { %$e, default => $default };
}
elsif ($arg eq '...') {
push @$args, { name => '...', type => 'SV *' };
}
else {
warn "bad argspec: $func->{name} ($arg)\n";
}
}
}
}
else {
$args = $func->{args};
}
return [ map $self->map_arg($_), @$args ]
}
#this is needed for modperl-only functions
#unlike apache/apr functions which are remapped to a mpxs_ function
sub thx_fixup {
my ($self, $func) = @_;
my $first = $func->{args}->[0];
return unless $first;
if ($first->{type} =~ /PerlInterpreter/) {
shift @{ $func->{args} };
$func->{thx} = 1;
}
}
sub map_function {
my ($self, $func) = @_;
my $map = $self->function_map->{ $func->{name} };
return unless $map;
$self->thx_fixup($func);
my ($status, $failed_type) =
$self->can_map($map, $func->{return_type},
map $_->{type}, @{ $func->{args} });
unless ($status) {
warn "unknown typemap: '$failed_type' (skipping $func->{name})\n";
return;
}
my $type = $map->{return_type} || $func->{return_type} || 'void';
my $map_type = $self->map_type($type);
die "unknown typemap: '$type'" unless defined $map_type;
my $mf = {
name => $func->{name},
return_type => $map_type,
args => $self->map_args($func),
perl_name => $map->{name},
thx => $func->{thx},
};
for (qw(dispatch argspec orig_args prefix)) {
$mf->{$_} = $map->{$_};
}
unless ($mf->{class}) {
$mf->{class} = $map->{class} || $self->first_class($mf);
#print "GUESS class=$mf->{class} for $mf->{name}\n";
}
$mf->{prefix} ||= ModPerl::FunctionMap::guess_prefix($mf);
$mf->{module} = $map->{module} || $mf->{class};
$mf;
}
sub map_structure {
my ($self, $struct) = @_;
my ($class, @elts);
my $stype = $struct->{type};
return unless $class = $self->map_type($stype);
for my $e (@{ $struct->{elts} }) {
my ($name, $type) = ($e->{name}, $e->{type});
my $rtype;
# ro/rw/r+w_startup/undef(disabled)
my $access_mode = $self->structure_map->{$stype}->{$name};
next unless $access_mode;
next unless $rtype = $self->map_type($type);
push @elts, {
name => $name,
type => $rtype,
default => $self->null_type($type),
pool => $self->class_pool($class),
class => $self->{map}->{$type} || "",
access_mode => $access_mode,
};
}
return {
module => $self->{structure_map}->{MODULES}->{$stype} || $class,
class => $class,
type => $stype,
elts => \@elts,
};
}
sub destructor {
my ($self, $prefix) = @_;
$self->function_map->{$prefix . 'DESTROY'};
}
sub first_class {
my ($self, $func) = @_;
for my $e (@{ $func->{args} }) {
next unless $e->{type} =~ /::/;
#there are alot of util functions that take an APR::Pool
#that do not belong in the APR::Pool class
next if $e->{type} eq 'APR::Pool' and $func->{name} !~ /^apr_pool/;
return $e->{type};
}
return $func->{name} =~ /^apr_/ ? 'APR' : 'Apache2';
}
sub check {
my $self = shift;
my (@types, @missing, %seen);
require Apache2::StructureTable;
for my $entry (@$Apache2::StructureTable) {
push @types, map $_->{type}, @{ $entry->{elts} };
}
for my $entry (@$Apache2::FunctionTable) {
push @types, grep { not $seen{$_}++ }
($entry->{return_type},
map $_->{type}, @{ $entry->{args} })
}
#printf "%d types\n", scalar @types;
for my $type (@types) {
push @missing, $type unless $self->exists($type);
}
return @missing ? \@missing : undef;
}
#look for Apache/APR structures that do not exist in structure.map
my %ignore_check = map { $_,1 } qw{
module_struct cmd_how kill_conditions
regex_t regmatch_t pthread_mutex_t
unsigned void va_list ... iovec char int long const
gid_t uid_t time_t pid_t size_t
sockaddr hostent
SV
};
sub check_exists {
my $self = shift;
my %structures = map { $_->{type}, 1 } @{ $self->structure_table() };
my @missing = ();
my %seen;
for my $name (keys %{ $self->{map} }) {
1 while $name =~ s/^\w+\s+(\w+)/$1/;
$name =~ s/\s+\**.*$//;
next if $seen{$name}++ or $structures{$name} or $ignore_check{$name};
push @missing, $name;
}
return @missing ? \@missing : undef;
}
#XXX: generate this
my %class_pools = map {
(my $f = "mpxs_${_}_pool") =~ s/:/_/g;
$_, $f;
} qw{
Apache2::RequestRec Apache2::Connection Apache2::URI APR::URI
};
sub class_pool : lvalue {
my ($self, $class) = @_;
$class_pools{$class};
}
#anything needed that mod_perl.h does not already include
#XXX: .maps should INCLUDE= these
my @includes = qw{
apr_uuid.h
apr_sha1.h
apr_md5.h
apr_base64.h
apr_getopt.h
apr_hash.h
apr_lib.h
apr_general.h
apr_signal.h
apr_thread_rwlock.h
util_script.h
};
sub h_wrap {
my ($self, $file, $code) = @_;
$file = 'modperl_xs_' . $file;
my $h_def = uc "${file}_h";
my $preamble = "\#ifndef $h_def\n\#define $h_def\n\n";
my $postamble = "\n\#endif /* $h_def */\n";
return ("$file.h", $preamble . $code . $postamble);
}
sub typedefs_code {
my $self = shift;
my $map = $self->get;
my %seen;
my $file = 'modperl_xs_typedefs';
my $h_def = uc "${file}_h";
my $code = "";
for (@includes, @{ $self->{INCLUDE} }) {
$code .= qq{\#include "$_"\n}
}
for my $t (sort {$a->[1] cmp $b->[1]} @{ $self->{struct} }) {
next if $seen{ $t->[1] }++;
$code .= "typedef $t->[0] * $t->[1];\n";
}
for my $t (sort {$a->[1] cmp $b->[1]} @{ $self->{typedef} }) {
next if $seen{ $t->[1] }++;
$code .= "typedef $t->[0] $t->[1];\n";
}
$self->h_wrap('typedefs', $code);
}
my %convert_alias = (
Apache2__RequestRec => 'r',
Apache2__Server => 'server',
Apache2__Connection => 'connection',
APR__Table => 'table',
APR__UUID => 'uuid',
apr_status_t => 'status',
);
sub sv_convert_code {
my $self = shift;
my $map = $self->get;
my %seen;
my $code = "";
for my $ctype (sort keys %$map) {
my $ptype = $map->{$ctype};
next if $self->special($ptype);
next if $ctype =~ /\s/;
my $class = $ptype;
if ($ptype =~ s/:/_/g) {
next if $seen{$ptype}++;
my $alias;
my $expect = "expecting an $class derived object";
my $croak = "argument is not a blessed reference";
#Perl -> C
my $define = "mp_xs_sv2_$ptype";
$code .= <<EOF;
#define $define(sv) \\
((SvROK(sv) && (SvTYPE(SvRV(sv)) == SVt_PVMG)) \\
|| (Perl_croak(aTHX_ "$croak ($expect)"),0) ? \\
INT2PTR($ctype *, SvIV((SV*)SvRV(sv))) : ($ctype *)NULL)
EOF
if ($alias = $convert_alias{$ptype}) {
$code .= "#define mp_xs_sv2_$alias $define\n\n";
}
#C -> Perl
$define = "mp_xs_${ptype}_2obj";
$code .= <<EOF;
#define $define(ptr) \\
sv_setref_pv(sv_newmortal(), "$class", (void*)ptr)
EOF
if ($alias) {
$code .= "#define mp_xs_${alias}_2obj $define\n\n";
}
}
else {
if ($ptype =~ /^(\wV)$/) {
my $class = $1;
my $define = "mp_xs_sv2_$ctype";
$code .= "#define $define(sv) ($ctype)Sv$class(sv)\n\n";
if (my $alias = $convert_alias{$ctype}) {
$code .= "#define mp_xs_sv2_$alias $define\n\n";
}
}
}
}
$self->h_wrap('sv_convert', $code);
}
1;
__END__