# autoconf -- create `configure' using m4 macros
# Copyright (C) 2001, 2002, 2006, 2008 Free Software Foundation, Inc.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
# This file is basically Perl 5.6's Class::Struct, but made compatible
# with Perl 5.5. If someday this has to be updated, be sure to rename
# all the occurrences of Class::Struct into Autom4te::Struct, otherwise
# if we `use' a Perl module (e.g., File::stat) that uses Class::Struct,
# we would have two packages defining the same symbols. Boom.
###############################################################
# The main copy of this file is in Automake's CVS repository. #
# Updates should be sent to automake-patches@gnu.org. #
###############################################################
package Autom4te::Struct;
## See POD after __END__
use 5.005_03;
use strict;
use vars qw(@ISA @EXPORT $VERSION);
use Carp;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(struct);
$VERSION = '0.58';
## Tested on 5.002 and 5.003 without class membership tests:
my $CHECK_CLASS_MEMBERSHIP = ($] >= 5.003_95);
my $print = 0;
sub printem {
if (@_) { $print = shift }
else { $print++ }
}
{
package Autom4te::Struct::Tie_ISA;
sub TIEARRAY {
my $class = shift;
return bless [], $class;
}
sub STORE {
my ($self, $index, $value) = @_;
Autom4te::Struct::_subclass_error();
}
sub FETCH {
my ($self, $index) = @_;
$self->[$index];
}
sub FETCHSIZE {
my $self = shift;
return scalar(@$self);
}
sub DESTROY { }
}
sub struct {
# Determine parameter list structure, one of:
# struct( class => [ element-list ])
# struct( class => { element-list })
# struct( element-list )
# Latter form assumes current package name as struct name.
my ($class, @decls);
my $base_type = ref $_[1];
if ( $base_type eq 'HASH' ) {
$class = shift;
@decls = %{shift()};
_usage_error() if @_;
}
elsif ( $base_type eq 'ARRAY' ) {
$class = shift;
@decls = @{shift()};
_usage_error() if @_;
}
else {
$base_type = 'ARRAY';
$class = (caller())[0];
@decls = @_;
}
_usage_error() if @decls % 2 == 1;
# Ensure we are not, and will not be, a subclass.
my $isa = do {
no strict 'refs';
\@{$class . '::ISA'};
};
_subclass_error() if @$isa;
tie @$isa, 'Autom4te::Struct::Tie_ISA';
# Create constructor.
croak "function 'new' already defined in package $class"
if do { no strict 'refs'; defined &{$class . "::new"} };
my @methods = ();
my %refs = ();
my %arrays = ();
my %hashes = ();
my %classes = ();
my $got_class = 0;
my $out = '';
$out = "{\n package $class;\n use Carp;\n sub new {\n";
$out .= " my (\$class, \%init) = \@_;\n";
$out .= " \$class = __PACKAGE__ unless \@_;\n";
my $cnt = 0;
my $idx = 0;
my( $cmt, $name, $type, $elem );
if( $base_type eq 'HASH' ){
$out .= " my(\$r) = {};\n";
$cmt = '';
}
elsif( $base_type eq 'ARRAY' ){
$out .= " my(\$r) = [];\n";
}
while( $idx < @decls ){
$name = $decls[$idx];
$type = $decls[$idx+1];
push( @methods, $name );
if( $base_type eq 'HASH' ){
$elem = "{'${class}::$name'}";
}
elsif( $base_type eq 'ARRAY' ){
$elem = "[$cnt]";
++$cnt;
$cmt = " # $name";
}
if( $type =~ /^\*(.)/ ){
$refs{$name}++;
$type = $1;
}
my $init = "defined(\$init{'$name'}) ? \$init{'$name'} :";
if( $type eq '@' ){
$out .= " croak 'Initializer for $name must be array reference'\n";
$out .= " if defined(\$init{'$name'}) && ref(\$init{'$name'}) ne 'ARRAY';\n";
$out .= " \$r->$elem = $init [];$cmt\n";
$arrays{$name}++;
}
elsif( $type eq '%' ){
$out .= " croak 'Initializer for $name must be hash reference'\n";
$out .= " if defined(\$init{'$name'}) && ref(\$init{'$name'}) ne 'HASH';\n";
$out .= " \$r->$elem = $init {};$cmt\n";
$hashes{$name}++;
}
elsif ( $type eq '$') {
$out .= " \$r->$elem = $init undef;$cmt\n";
}
elsif( $type =~ /^\w+(?:::\w+)*$/ ){
$init = "defined(\$init{'$name'}) ? \%{\$init{'$name'}} : ()";
$out .= " croak 'Initializer for $name must be hash reference'\n";
$out .= " if defined(\$init{'$name'}) && ref(\$init{'$name'}) ne 'HASH';\n";
$out .= " \$r->$elem = '${type}'->new($init);$cmt\n";
$classes{$name} = $type;
$got_class = 1;
}
else{
croak "'$type' is not a valid struct element type";
}
$idx += 2;
}
$out .= " bless \$r, \$class;\n }\n";
# Create accessor methods.
my( $pre, $pst, $sel );
$cnt = 0;
foreach $name (@methods){
if ( do { no strict 'refs'; defined &{$class . "::$name"} } ) {
carp "function '$name' already defined, overrides struct accessor method";
}
else {
$pre = $pst = $cmt = $sel = '';
if( defined $refs{$name} ){
$pre = "\\(";
$pst = ")";
$cmt = " # returns ref";
}
$out .= " sub $name {$cmt\n my \$r = shift;\n";
if( $base_type eq 'ARRAY' ){
$elem = "[$cnt]";
++$cnt;
}
elsif( $base_type eq 'HASH' ){
$elem = "{'${class}::$name'}";
}
if( defined $arrays{$name} ){
$out .= " my \$i;\n";
$out .= " \@_ ? (\$i = shift) : return \$r->$elem;\n";
$sel = "->[\$i]";
}
elsif( defined $hashes{$name} ){
$out .= " my \$i;\n";
$out .= " \@_ ? (\$i = shift) : return \$r->$elem;\n";
$sel = "->{\$i}";
}
elsif( defined $classes{$name} ){
if ( $CHECK_CLASS_MEMBERSHIP ) {
$out .= " croak '$name argument is wrong class' if \@_ && ! UNIVERSAL::isa(\$_[0], '$classes{$name}');\n";
}
}
$out .= " croak 'Too many args to $name' if \@_ > 1;\n";
$out .= " \@_ ? ($pre\$r->$elem$sel = shift$pst) : $pre\$r->$elem$sel$pst;\n";
$out .= " }\n";
}
}
$out .= "}\n1;\n";
print $out if $print;
my $result = eval $out;
carp $@ if $@;
}
sub _usage_error {
confess "struct usage error";
}
sub _subclass_error {
croak 'struct class cannot be a subclass (@ISA not allowed)';
}
1; # for require
__END__
=head1 NAME
Autom4te::Struct - declare struct-like datatypes as Perl classes
=head1 SYNOPSIS
use Autom4te::Struct;
# declare struct, based on array:
struct( CLASS_NAME => [ ELEMENT_NAME => ELEMENT_TYPE, ... ]);
# declare struct, based on hash:
struct( CLASS_NAME => { ELEMENT_NAME => ELEMENT_TYPE, ... });
package CLASS_NAME;
use Autom4te::Struct;
# declare struct, based on array, implicit class name:
struct( ELEMENT_NAME => ELEMENT_TYPE, ... );
package Myobj;
use Autom4te::Struct;
# declare struct with four types of elements:
struct( s => '$', a => '@', h => '%', c => 'My_Other_Class' );
$obj = new Myobj; # constructor
# scalar type accessor:
$element_value = $obj->s; # element value
$obj->s('new value'); # assign to element
# array type accessor:
$ary_ref = $obj->a; # reference to whole array
$ary_element_value = $obj->a(2); # array element value
$obj->a(2, 'new value'); # assign to array element
# hash type accessor:
$hash_ref = $obj->h; # reference to whole hash
$hash_element_value = $obj->h('x'); # hash element value
$obj->h('x', 'new value'); # assign to hash element
# class type accessor:
$element_value = $obj->c; # object reference
$obj->c->method(...); # call method of object
$obj->c(new My_Other_Class); # assign a new object
=head1 DESCRIPTION
C