The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

########################################################################
# generic pass-through intializer generator.
# supplies "init" subroutine to merge metadata via EVERY::LAST call
# in constructor.
########################################################################

########################################################################
# housekeeping
########################################################################

package NEXT::init;

our $VERSION = '0.99.2';

use strict;
use warnings;
use Carp;

use Symbol;
use Scalar::Util qw( reftype );

# debug output

use Data::Dumper;

########################################################################
# package variables
########################################################################

########################################################################
# subroutines
########################################################################

########################################################################
# utility subs to handle data on @_.
# re-used in import and init.
#
# lexicals avoids unintended inheritence.

########################################################################
# package variables
########################################################################

# switches that get reset each time they are processed.
# using package variables makes it a bit easier to 
# debug some nasty situations from the caller.

our $verbose = 0;
our $debug   = 0;

my %defaultz =
qw(
	export	1
	type	0
	debug	0
	verbose	0
);

########################################################################
# utility subs to handle data on @_.
# re-used in import and init.

my $switches =
sub 
{
	$DB::single = 1 if $debug;

	# set the switches.
	# this purges them from @_, leaving only
	# the object's data on the stack.
	#
	# the check on $_[0] implies the switches
	# lead any data on the stack: trailing 
	# switches will be included as data.

	my %argz = ();

	while( @_ && $_[0] =~ /^:(\S+)$/ )
	{
		my ( $name, $value ) = split /=/, $1;

		$value = 1 unless defined $value;

		if( exists $defaultz{$name} )
		{
			$argz{$name} = $value 
		}
		else
		{
			carp "\nBogus argument: $name=$value\n";
		}

		shift;
	}

	$verbose = $argz{verbose} if defined $argz{verbose};
	$debug   = $argz{debug}   if defined $argz{debug};

	exists $argz{$_} or $argz{$_} = $defaultz{$_}
		for keys %defaultz;

	\%argz
};

# first time around it takes a bit more work to 
# determine the proper type for an object. after
# the metadata and init sub's have been installed
# this doesn't need to be done again.

my $objtype =
sub
{
	$DB::single = 1 if $debug;

	# if the arguments are a referent then figure out what
	# they are and use that as a default for the type if
	# one was not passed in via ":type=blah".
	#
	# caller gets back the object's data type: hash, queue,
	# or stack.
	#
	# eval's handle blessed referents.

	my $item = shift;

	my $class = ref $item || $item;

	my $basetype =  
	do
	{
		if( my $sub = $class->can('meta') )
		{
			# if this is derived from another N::i-based
            # class then whatever its metadata type is 
            # will be re-used here. $meta is extraneous
            # but reftype $sub->() seems likely to leave
            # anyone else crosseyed...

			my $meta = $sub->();

			reftype $meta;
		}
		elsif( my $type = reftype $_[0] )
		{
            # if the caller passed in referent for the 
            # first argument use its type if there isn't
            # a base class to get it from.

            $type
		}
        else
        {
            # passed a simple list: use whatever the list
            # defines for itself via the :type (i.e., no 
            # further sanity check on the basename).

            ''
        }
	};

    # now figure out the appropriate type for this new
    # class: either it has passed a ref in $_[0], which
    # means to use that type as the current metadata,
    # or a simple list has been passed, which requires
    # checking for :type argument or defaulting to HASH.

	my $type = 
	do
	{
		if( my $a = reftype $_[0] )
		{
			if( @_ == 1 )
			{
				# single argument: figure out what it is and
				# use that type for the object. catch is that
				# ref $item may return a blessed classname 
				# instead of the base type. simplest way around
				# this -- only done once per class anyway -- 
				# is to eval expanding the object various ways.

				if( $a =~ /^(HASH|ARRAY)$/ )
				{
					$1
				}
                else
				{
					die "\nUnusable referent: '$a' not hash or array.\n";
				}
			}
			else
			{
				# can't use ref's as keys, must be an array.

				'ARRAY'
			}
		}
		elsif( $basetype )
		{
			# nothing on the argument list for figuring it out,
			# punt to the base classes: one of them will have
			# some data already defined that can be used to 
			# determine a consistent value.

			$basetype
		}
		else
		{
			die<<'END'
Unable to determine base data type:
The data arguments are not a referent and this 
class is not derived from another type.
END
		}
	};

	$basetype ||= $type;

	if( $basetype ne $type )
	{
		# second issue: is this the same type as whatever
		# the base class defines? easier to deal with 
		# mismatches here than at runtime where it's harder
		# to figure out where things came from...

		croak<<'END'
Mismatched datatypes: $class is derived from $basetype
but the data provided seems to be $type. Probably best
to either use a simple list for derived types.
END
	}

	$type
};

########################################################################
# catch: you can't check for ref $x eq 'HASH' since the reference
# may be blessed already...
#
# fix: eval the expansion in various formats and then give up.
#
# note: this has to be called AFTER switches, above, so that
# the stack has real data on it...
#
# all this does is expand a referent into a flat list.
#
# caller gets back whatever the argument list expands to.

my $expandvaluz =
sub
{
	if( @_ == 1 )
	{
        my $type = reftype $_[0];

        if( 'HASH' eq $type )
        {
            %{ $_[0] }
        }
        elsif( 'ARRAY' eq $type )
        {
            @{ $_[0] }
        }
        else
        {
            die "Bogus data type: '$type' neither hash nor array";
        }
	}
	else
	{
		@_
	}
};

########################################################################
# three cases: hashes are layered via slice going up the 
# inheritence tree, queues are pushed going down, and 
# stacks are unshifted going up -- with stack being the
# default array type.
#
# queues are built by pushing the class data going down
# the inheritence tree; stacks push the data going up.
#
# queues start out with the arguments on the data; stacks
# push the arguments on last.
#
# queues shift off the most derived data, stacks pop it off.
#
# adding successive mungers => stack.
# overriding pre-emptive handler => queue.
#
# hash-based arguments have to be expanded if they are
# passed as a referent; placing referents onto an array
# is prefectly reasonable.

my %constructorz =
(
	hash =>
	sub
	{
		use NEXT;

		my $item = shift;

		my %argz = ref $_[0] ? &$expandvaluz : @_;

		my $obj = bless {}, ref $item || $item;

		$obj->EVERY::LAST::init;

		@{ $obj }{keys %argz} = values %argz;

		$obj
	},

	queue =>
	sub
	{
		use NEXT;

		my $item = shift;

		my $obj = bless [ @_ ], ref $item || $item;

		$obj->EVERY::init;

		$obj
	},

	stack =>
	sub
	{
		use NEXT;

		my $item = shift;

		my $obj = bless [], ref $item || $item;

		$obj->EVERY::LAST::init;

		push @$obj, @_;

		$obj
	},
);

###########################################################
# passed class meta-data as a list or referent. lists 
# become hashes; others are left as-is (only arrays and
# hashes are handled gracefully at this point).
#
# the metadata is installed into the package as %meta or 
# @meta, along with a closure as &init to merge the 
# metadata with an exsiting object's contents.
#
# note: mixing metdata types (e.g., arrays with hashes) is
# fatal.

sub import
{
	local $\ = "\n";
	local $, = ", ";

	# discard the current package name.

	shift;

	my $caller = caller;

	print STDERR "\nProcessing: $caller\n" . Dumper \@_
		if $verbose;

	# pass the caller to the switch processor, object
	# type handler. 
	#
	# there is no reason to export a meta referent into
	# the main namespace.

	unshift @_, ':export=0' if $caller eq 'main';

	my $argz = &$switches;

	$DB::single = 1 if $debug;

	# if things are getting exported to the caller class
	# (vs. simply a use base) then build the typeglobs
	# via symbol and populate them with ref's (data +
	# closures).

	if( $argz->{export} )
	{
		unshift @_, $caller;

		# basetype is ARRAY or HASH.  objtype is hash, queue,
		# or stack.  default array type is stack.

		my $basetype = &$objtype;

		$argz->{type} ||=
			$basetype eq 'HASH' ? 'hash' : 'queue';

		# at this point the base data type for the object
		# is known. next step is to manufacture an emtpy
		# item type, data to merge for the object, and a
		# closure to handle the job.

		my $data = $argz->{type} eq 'hash' ?
			{ &$expandvaluz } : [ &$expandvaluz ] ;

		# now to hack the caller's namespace...

		my $init  =   qualify_to_ref 'init',      $caller;
		my $meta  =   qualify_to_ref 'meta',      $caller;
		my $const =   qualify_to_ref 'construct', $caller;

		# caller can access %meta or @meta to mangle
		# the data after installing it. we use $class->meta
		# to access an un-blessed referent for checking the
		# base data type.

		*$meta = $data;

		*$meta = sub { $data };

		# the constructor is hard-wired for the
		# specific type. this is the only place
		# where array types really matter.

		*$const = $constructorz{ $argz->{type} }
			unless defined *{$const}{CODE};

		if( $argz->{type} eq 'hash' )
		{
			*$init =
			sub
			{
				$DB::single = 1 if $debug > 1;

				my $obj = shift;

				@{$obj}{ keys %{*$meta} } = values %{*$meta};

				$obj
			}
			unless defined *$init{CODE};
		}
		else
		{
			*$init = 
			sub
			{
				$DB::single = 1 if $debug > 1;

				my $obj = shift;

				push @$obj, @{*$meta};

				$obj
			}
			unless defined *$init{CODE};
		}

	}

	# someplace to hang a breakpoint

	$DB::single = 1 if $debug;

	1
}

# keep require happy 

1

__END__

=head1 NAME

NEXT::init

DWIM data inherited data initialization via NEXT.

=head1 SYNOPSIS

Data can be hash or array based.

Hashes use EVERY::LAST to assign slices (i.e., derived
classes override the base class' values, assign new keys).

Arrays use either EVERY::init (queue) or EVERY::LAST (stack)
to push each layer's data onto the object. 

=head2 Hash-based

Each level in the init does an array-slice assignment, 
overwriting values from less-derived classes as necessary.

Given:

	package Base_Hash;

	use NEXT::init
	{
		foo => 1,

		package => 'Base'
	};

	package Derived;

	use base qw( Base_Hash );

	use NEXT::init
	{
		bar => 1,

		package => 'Derived'
	};

=over 4

Base:

	{
		foo => 1,
		package => 'Base'
	}

Derived:

	{
		foo => 1,
		bar => 1,
		package => 'Derived'
	}

=back


=head2 Array-based

There are two flavors of array: queue  and stack. 

Both push successive class' data onto the object. The difference
is that queues use EVERY::init to push the data going down the 
inheritence tree; stacks use EVERY::LAST to push it going back
up. 

queues leave the arguments at the front of the list where shift
or for(@$obj) will find them first; stacks leave the argumnts
at the end where pop will get them.

For example, from test.pl:

Given a base class of "MyArray" and two derived classes,
one queue one stack:

	package MyArray;

	use NEXT::init [ qw( a b c ) ];

	package Queue;

	use base qw( MyArray );

	use NEXT::init qw( :type=queue d e f );

	package Stack;

	use base qw( MyArray );

	use NEXT::init qw( :type=stack d e f );


Calling the constructor as:

	my $obj8 = Queue->construct( qw( queue ) );

	my $obj9 = Stack->construct( qw( stack ) );


Yields a queue of:

	bless( [
	'queue',
	'd',
	'e',
	'f',
	'a',
	'b',
	'c'
	], 'Queue' )

and a stack of:

	bless( [
	'a',
	'b',
	'c',
	'd',
	'e',
	'f',
	'stack'
	], 'Stack' )


=head1 DESCRIPTION

This is a generic initializer class for objects that need
to inherit hash or array data. When use-ed the import module
installs "init" and "constructor" method which merge each 
level of inherited data and the arguments into the object.
A method for accessing the class data and the class data
itself are installed in *meta.

The resulting objects do not inherit anything from NEXT::init
(i.e., it is not a base class). The constructor uses whatever
classes the object is based on to locate "init" methods 
during object construction; NEXT::init only provides an
import sub to install the methods and validate the base data
types.

For effeciency the init handers are closures which handle
either hash or array data propery (i.e., no repeated if-
logic, changing the base data type of the object after
construction will likely break the associated init handlers).

The object's type (hash or array) is determined either by
the type of referent passed into init or an initial ":type=foo"
argument for arrays.

Classes used as bases for other classes should probably 
just pass the data in as a referent to begin with:

	use NEXT::init
	{
		key => value
	};

for hashes or

	use NEXT::init
	[
		array 
		data
	];

for arrays. 

Derived classes can then just pass in lists, which
will be formatted appropriately based on the ref type
of the base classses:

	use NEXT::init
	(
		simple
		list
		goes
		here
	);


=head1 Installed symbols

=head2 construct

Normal use of the classes will be via:

	ClassName->construct( runtime => 'arguments' );

The arguments will be converted to an approprpriate
type for the data.

=head2 init

This called from the constructor to add class data
to the object. This is where hashes are udpated via
hash slice and arrays via push.

There is no reason to call this externally.

=head2 %meta and @meta

The values passed into NEXT::init via use are installed
as *$class::meta = ref via the Symbol module. This allows
the class to inspect or modify its class data. A method
named 'meta' is installed which returns a reference to
the class metadata. One use for this is in NEXT::init's
import, where having an unblessed referent simplifies
determining the base type.

The value of $meta is built from Symbol, with the hash-or-array
data assigned via referent:

	my $meta  =   qualify_to_ref 'meta', $caller;

	*$meta = $data;

The initializers are closures which use the lexical $meta
glob to access the class data:

	@{$object}{ keys %{*$meta} } = values %{*$meta}

	push @$object, @{ *$meta };

The package can simply refer to %meta or @meta:

	use strict;

	use NEXT::init { qw( foo bar ) };

	scalar %meta

	__END__

works just fine (with %meta installed as a valid
package symbol before strict complains about %meta
being unknown).

=head1 Import arguments

=over 4

=item :type=hash

=item :type=queue

=item :type=stack

Given a hash or array referent as input these default to 
hash or queue respectively. The only real use for these
is passing in simple-list data (vs. a referent) for base
classes [i.e., artistic preference] or setting array types
to stack. 

Setting this to a value that does not match the base type
in a derived class (e.g., :type=hash with a base class of
queue or stack) is fatal.

Mixing and matching queue and stack types is allowed and
works pretty much as expected since both types push their
data at each stage.


=item :verbose

=item :verbose=1

Defaults to one if provided, use ":verbose=0" to turn it
off, prints a bit more information during the import cycle.

Main use is in #! code to control debugging of classes 
called from the main code:

	#!/opt/bin/perl

	use NEXT::init qw( :verbose=1 );

Will turn on verbose output for any subsequent classes.

=back

The remainder of these are really only intended for 
development.

=over 4

=item :debug

=item :debug=1

=item :debug=2

Defaults to zero normally, to one if provided. This is
used in the code as:

	$DB::single = 1 if $debug;

or 

	$DB::single = 1 if $debug > 1;

This is also mainly for use in #! code (see :verbose, above).

=item :export

=item :export=1

Controls whether the symbols are exported after the 
arguments are processed. Defaults to 0 if the calling
package is "main". This is mainly for internal use to
allow #! code's setting verbose and debug.

=back

=head1 AUTHOR

Steven Lembark (lembark@wrkhors.com)

=head2 To Do

- Deal with non-array/-hash referent types (suggestions
  welcome). CODE ref's might be executed in the proper
  order but that doesn't seem much like "inheritence".

=head1 COPYRIGHT

This code is released as-is under the same terms as Perl-5.8 (or
any later version of perl at the users preference).

=head1 SEE ALSO

NEXT perlreftut perlootut