package Class::Generate; $Class::Generate::VERSION = '1.18'; use 5.010; use strict; use Carp; use warnings::register; use Symbol qw(&delete_package); BEGIN { use vars qw(@ISA @EXPORT_OK); use vars qw($save $accept_refs $strict $allow_redefine $class_var $instance_var $check_params $check_code $check_default $nfi $warnings); require Exporter; @ISA = qw(Exporter); @EXPORT_OK = ( qw(&class &subclass &delete_class), qw($save $accept_refs $strict $allow_redefine $class_var $instance_var $check_params $check_code $check_default $nfi $warnings) ); $accept_refs = 1; $strict = 1; $allow_redefine = 0; $class_var = 'class'; $instance_var = 'self'; $check_params = 1; $check_code = 1; $check_default = 1; $nfi = 0; $warnings = 1; } use vars qw(@_initial_values); # Holds all initial values passed as references. my ( $class_name, $class ); my ( $class_vars, $use_packages, $excluded_methods, $param_style_spec, $default_pss ); my %class_options; my $cm; # These variables are for error messages. my $sa_needed = 'must be string or array reference'; my $sh_needed = 'must be string or hash reference'; my $allow_redefine_for_class; my ( $initialize, # These variables all hold $parse_any_flags, # references to package-local $set_class_type, # subs that other packages $parse_class_specification, # shouldn't call. $parse_method_specification, $parse_member_specification, $set_attributes, $class_defined, $process_class, $store_initial_value_reference, $check_for_invalid_parameter_names, $constructor_parameter_passing_style, $verify_class_type, $croak_if_duplicate_names, $invalid_spec_message ); my %valid_option = map( substr( $_, 0, 1 ) eq '$' ? ( substr( $_, 1 ) => 1 ) : (), @EXPORT_OK ); my %class_to_ref_map = ( 'Class::Generate::Array_Class' => 'ARRAY', 'Class::Generate::Hash_Class' => 'HASH' ); my %warnings_keys = map( ( $_ => 1 ), qw(use no register) ); sub class(%) { # One of the three interface my %params = @_; # routines to the package. if ( defined $params{-parent} ) { # Defines a class or a subclass(@_); # subclass. return; } &$initialize(); &$parse_any_flags( \%params ); croak "Missing/extra arguments to class()" if scalar( keys %params ) != 1; ( $class_name, undef ) = %params; $cm = qq|Class "$class_name"|; &$verify_class_type( $params{$class_name} ); croak "$cm: A package of this name already exists" if !$allow_redefine_for_class && &$class_defined($class_name); &$set_class_type( $params{$class_name} ); &$process_class( $params{$class_name} ); } sub subclass(%) { # One of the three interface my %params = @_; # routines to the package. &$initialize(); # Defines a subclass. my ( $p_spec, $parent ); if ( defined( $p_spec = $params{-parent} ) ) { delete $params{-parent}; } else { croak "Missing subclass parent"; } eval { $parent = Class::Generate::Array->new($p_spec) }; croak qq|Invalid parent specification ($sa_needed)| if $@ || scalar( $parent->values ) == 0; &$parse_any_flags( \%params ); croak "Missing/extra arguments to subclass()" if scalar( keys %params ) != 1; ( $class_name, undef ) = %params; $cm = qq|Subclass "$class_name"|; &$verify_class_type( $params{$class_name} ); croak "$cm: A package of this name already exists" if !$allow_redefine_for_class && &$class_defined($class_name); my $assumed_type = UNIVERSAL::isa( $params{$class_name}, 'ARRAY' ) ? 'ARRAY' : 'HASH'; my $child_type = lc($assumed_type); for my $p ( $parent->values ) { my $c = Class::Generate::Class_Holder::get( $p, $assumed_type ); croak qq|$cm: Parent package "$p" does not exist| if !defined $c; my $parent_type = lc( $class_to_ref_map{ ref $c } ); croak "$cm: $child_type-based class must have $child_type-based parent ($p is $parent_type-based)" if !UNIVERSAL::isa( $params{$class_name}, $class_to_ref_map{ ref $c } ); warnings::warn( qq{$cm: Parent class "$p" was not defined using class() or subclass(); $child_type reference assumed} ) if warnings::enabled() && eval "! exists \$" . $p . '::{_cginfo}'; } &$set_class_type( $params{$class_name}, $parent ); for my $p ( $parent->values ) { $class->add_parents( Class::Generate::Class_Holder::get($p) ); } &$process_class( $params{$class_name} ); } sub delete_class(@) { # One of the three interface routines for my $class (@_) { # to the package. Deletes a class next if !eval '%' . $class . '::'; # declared using Class::Generate. if ( !eval '%' . $class . '::_cginfo' ) { croak $class, ': Class was not declared using ', __PACKAGE__; } delete_package($class); Class::Generate::Class_Holder::remove($class); my $code_checking_package = __PACKAGE__ . '::Code_Checker::check::' . $class . '::'; if ( eval '%' . $code_checking_package ) { delete_package($code_checking_package); } } } $default_pss = Class::Generate::Array->new('key_value'); $initialize = sub { # Reset certain variables, and set undef $class_vars; # options to their default values. undef $use_packages; undef $excluded_methods; $param_style_spec = $default_pss; %class_options = ( virtual => 0, strict => $strict, save => $save, accept_refs => $accept_refs, class_var => $class_var, instance_var => $instance_var, check_params => $check_params, check_code => $check_code, check_default => $check_default, nfi => $nfi, warnings => $warnings ); $allow_redefine_for_class = $allow_redefine; }; $verify_class_type = sub { # Ensure that the class specification my $spec = $_[0]; # is a hash or array reference. return if UNIVERSAL::isa( $spec, 'HASH' ) || UNIVERSAL::isa( $spec, 'ARRAY' ); croak qq|$cm: Elements must be in array or hash reference|; }; $set_class_type = sub { # Set $class to the type (array or my ( $class_spec, $parent ) = @_; # hash) appropriate to its declaration. my @params = ( $class_name, %class_options ); if ( UNIVERSAL::isa( $class_spec, 'ARRAY' ) ) { if ( defined $parent ) { my ( $parent_name, @other_array_values ) = $parent->values; croak qq|$cm: An array reference based subclass must have exactly one parent| if @other_array_values; $parent = Class::Generate::Class_Holder::get( $parent_name, 'ARRAY' ); push @params, ( base_index => $parent->last + 1 ); } $class = Class::Generate::Array_Class->new(@params); } else { $class = Class::Generate::Hash_Class->new(@params); } }; my $class_name_regexp = '[A-Za-z_]\w*(::[A-Za-z_]\w*)*'; $parse_class_specification = sub { # Parse the class' specification, my %specs = @_; # checking for errors and amalgamating my %required; # class data. if ( defined $specs{new} ) { croak qq|$cm: Specification for "new" must be hash reference| unless UNIVERSAL::isa( $specs{new}, 'HASH' ); my %new_spec = %{ $specs{new} }; # Modify %new_spec, not parameter passed my $required_items; # to class() or subclass(). if ( defined $new_spec{required} ) { eval { $required_items = Class::Generate::Array->new( $new_spec{required} ); }; croak qq|$cm: Invalid specification for required constructor parameters ($sa_needed)| if $@; delete $new_spec{required}; } if ( defined $new_spec{style} ) { eval { $param_style_spec = Class::Generate::Array->new( $new_spec{style} ); }; croak qq|$cm: Invalid parameter-passing style ($sa_needed)| if $@; delete $new_spec{style}; } $class->constructor( Class::Generate::Constructor->new(%new_spec) ); if ( defined $required_items ) { for ( $required_items->values ) { if (/^\w+$/) { croak qq|$cm: Required params list for constructor contains unknown member "$_"| if !defined $specs{$_}; $required{$_} = 1; } else { $class->constructor->add_constraints($_); } } } } else { $class->constructor( Class::Generate::Constructor->new ); } my $actual_name; for my $member_name ( grep $_ ne 'new', keys %specs ) { $actual_name = $member_name; $actual_name =~ s/^&//; croak qq|$cm: Invalid member/method name "$actual_name"| unless $actual_name =~ /^[A-Za-z_]\w*$/; croak qq|$cm: "$instance_var" is reserved| unless $actual_name ne $class_options{instance_var}; if ( substr( $member_name, 0, 1 ) eq '&' ) { &$parse_method_specification( $member_name, $actual_name, \%specs ); } else { &$parse_member_specification( $member_name, \%specs, \%required ); } } $class->constructor->style(&$constructor_parameter_passing_style); }; $parse_method_specification = sub { my ( $member_name, $actual_name, $specs ) = @_; my ( %spec, $method ); eval { %spec = %{ Class::Generate::Hash->new( $$specs{$member_name} || die, 'body' ) }; }; croak &$invalid_spec_message( 'method', $actual_name, 'body' ) if $@; if ( $spec{class_method} ) { croak qq|$cm: Method "$actual_name": A class method cannot be protected| if $spec{protected}; $method = Class::Generate::Class_Method->new( $actual_name, $spec{body} ); if ( $spec{objects} ) { eval { $method->add_objects( ( Class::Generate::Array->new( $spec{objects} ) )->values ); }; croak qq|$cm: Invalid specification for objects of "$actual_name" ($sa_needed)| if $@; } delete $spec{objects} if exists $spec{objects}; } else { $method = Class::Generate::Method->new( $actual_name, $spec{body} ); } delete $spec{class_method} if exists $spec{class_method}; $class->user_defined_methods( $actual_name, $method ); &$set_attributes( $actual_name, $method, 'Method', 'body', \%spec ); }; $parse_member_specification = sub { my ( $member_name, $specs, $required ) = @_; my ( %spec, $member, %member_params ); eval { %spec = %{ Class::Generate::Hash->new( $$specs{$member_name} || die, 'type' ) }; }; croak &$invalid_spec_message( 'member', $member_name, 'type' ) if $@; $spec{required} = 1 if $$required{$member_name}; if ( exists $spec{default} ) { if ( warnings::enabled() && $class_options{check_default} ) { eval { Class::Generate::Support::verify_value( $spec{default}, $spec{type} ); }; warnings::warn( qq|$cm: Default value for "$member_name" is not correctly typed| ) if $@; } &$store_initial_value_reference( \$spec{default}, $member_name ) if ref $spec{default}; $member_params{default} = $spec{default}; } %member_params = map defined $spec{$_} ? ( $_ => $spec{$_} ) : (), qw(post pre assert); if ( $spec{type} =~ m/^[\$@%]?($class_name_regexp)$/o ) { $member_params{base} = $1; } elsif ( $spec{type} !~ m/^[\$\@\%]$/ ) { croak qq|$cm: Member "$member_name": "$spec{type}" is not a valid type|; } if ( $spec{required} && ( $spec{private} || $spec{protected} ) ) { warnings::warn( qq|$cm: "required" attribute ignored for private/protected member "$member_name"| ) if warnings::enabled(); delete $spec{required}; } if ( $spec{private} && $spec{protected} ) { warnings::warn( qq|$cm: Member "$member_name" declared both private and protected (protected assumed)| ) if warnings::enabled(); delete $spec{private}; } delete @member_params{ grep !defined $member_params{$_}, keys %member_params }; if ( substr( $spec{type}, 0, 1 ) eq '@' ) { $member = Class::Generate::Array_Member->new( $member_name, %member_params ); } elsif ( substr( $spec{type}, 0, 1 ) eq '%' ) { $member = Class::Generate::Hash_Member->new( $member_name, %member_params ); } else { $member = Class::Generate::Scalar_Member->new( $member_name, %member_params ); } delete $spec{type}; $class->members( $member_name, $member ); &$set_attributes( $member_name, $member, 'Member', undef, \%spec ); }; $parse_any_flags = sub { my $params = $_[0]; my %flags = map substr( $_, 0, 1 ) eq '-' ? ( $_ => $$params{$_} ) : (), keys %$params; return if !%flags; flag: while ( my ( $flag, $value ) = each %flags ) { $flag eq '-use' and do { eval { $use_packages = Class::Generate::Array->new($value) }; croak qq|"-use" flag $sa_needed| if $@; next flag; }; $flag eq '-class_vars' and do { eval { $class_vars = Class::Generate::Array->new($value) }; croak qq|"-class_vars" flag $sa_needed| if $@; for my $var_spec ( grep ref($_), $class_vars->values ) { croak 'Each class variable must be scalar or hash reference' unless UNIVERSAL::isa( $var_spec, 'HASH' ); for my $var ( grep ref( $$var_spec{$_} ), keys %$var_spec ) { &$store_initial_value_reference( \$$var_spec{$var}, $var ); } } next flag; }; $flag eq '-virtual' and do { $class_options{virtual} = $value; next flag; }; $flag eq '-exclude' and do { eval { $excluded_methods = Class::Generate::Array->new($value) }; croak qq|"-exclude" flag $sa_needed| if $@; next flag; }; $flag eq '-comment' and do { $class_options{comment} = $value; next flag; }; $flag eq '-options' and do { croak qq|Options must be in hash reference| unless UNIVERSAL::isa( $value, 'HASH' ); if ( exists $$value{allow_redefine} ) { $allow_redefine_for_class = $$value{allow_redefine}; delete $$value{allow_redefine}; } option: while ( my ( $o, $o_value ) = each %$value ) { if ( !$valid_option{$o} ) { warnings::warn(qq|Unknown option "$o" ignored|) if warnings::enabled(); next option; } $class_options{$o} = $o_value; } if ( exists $class_options{warnings} ) { my $w = $class_options{warnings}; if ( ref $w ) { croak 'Warnings must be scalar value or array reference' unless UNIVERSAL::isa( $w, 'ARRAY' ); croak 'Warnings array reference must have even number of elements' unless $#$w % 2 == 1; for ( my $i = 0 ; $i <= $#$w ; $i += 2 ) { croak qq|Warnings array: Unknown key "$$w[$i]"| unless exists $warnings_keys{ $$w[$i] }; } } } next flag; }; warnings::warn(qq|Unknown flag "$flag" ignored|) if warnings::enabled(); } delete @$params{ keys %flags }; }; # Set the appropriate attributes of $set_attributes = sub { # a member or method w.r.t. a class. my ( $name, $m, $type, $exclusion, $spec ) = @_; for my $attr ( defined $exclusion ? grep( $_ ne $exclusion, keys %$spec ) : keys %$spec ) { if ( $m->can($attr) ) { $m->$attr( $$spec{$attr} ); } elsif ( $class->can($attr) ) { $class->$attr( $name, $$spec{$attr} ); } else { warnings::warn(qq|$cm: $type "$name": Unknown attribute "$attr"|) if warnings::enabled(); } } }; my $containing_package = __PACKAGE__ . '::'; my $initial_value_form = $containing_package . '_initial_values'; $store_initial_value_reference = sub { # Store initial values that are my ( $default_value, $var_name ) = @_; # references in an accessible push @_initial_values, $$default_value; # place. $$default_value = "\$$initial_value_form" . "[$#_initial_values]"; warnings::warn(qq|Cannot save reference as initial value for "$var_name"|) if $class_options{save} && warnings::enabled(); }; $class_defined = sub { # Return TRUE if the argument my $class_name = $_[0]; # is the name of a Perl package. return eval '%' . $class_name . '::'; }; # Do the main work of processing a class. $process_class = sub { # Parse its specification, generate a my $class_spec = $_[0]; # form, and evaluate that form. my ( @warnings, $errors ); &$croak_if_duplicate_names($class_spec); for my $var ( grep defined $class_options{$_}, qw(instance_var class_var) ) { croak qq|$cm: Value of $var option must be an identifier (without a "\$")| unless $class_options{$var} =~ /^[A-Za-z_]\w*$/; } &$parse_class_specification( UNIVERSAL::isa( $class_spec, 'ARRAY' ) ? @$class_spec : %$class_spec ); Class::Generate::Member_Names::set_element_regexps(); $class->add_class_vars( $class_vars->values ) if $class_vars; $class->add_use_packages( $use_packages->values ) if $use_packages; $class->warnings( $class_options{warnings} ) if $class_options{warnings}; $class->check_params( $class_options{check_params} ) if $class_options{check_params}; $class->excluded_methods_regexp( join '|', map "(?:$_)", $excluded_methods->values ) if $excluded_methods; if ( warnings::enabled() && $class_options{check_code} ) { Class::Generate::Code_Checker::check_user_defined_code( $class, $cm, \@warnings, \$errors ); for my $warning (@warnings) { warnings::warn($warning); } warnings::warn($errors) if $errors; } my $form = $class->form; if ( $class_options{save} ) { my ( $class_file, $ob, $cb ); if ( $class_options{save} =~ /\.p[ml]$/ ) { $class_file = $class_options{save}; open CLASS_FILE, ">>$class_file" or croak qq|$cm: Cannot append to "$class_file": $!|; $ob = "{\n"; # The form is enclosed in braces to prevent $cb = "}\n"; # renaming duplicate "my" variables. } else { $class_file = $class_name . '.pm'; $class_file =~ s|::|/|g; open CLASS_FILE, ">$class_file" or croak qq|$cm: Cannot save to "$class_file": $!|; $ob = $cb = ''; } $form =~ s/^(my [%@\$]\w+) = ([%@]\{)?\$$initial_value_form\[\d+\]\}?;/$1;/mgo; print CLASS_FILE $ob, $form, $cb, "\n1;\n"; close CLASS_FILE; } croak "$cm: Cannot continue after errors" if $errors; { local $SIG{__WARN__} = sub { }; # Warnings have been reported during eval $form; # user-defined code analysis. if ($@) { my @lines = split( "\n", $form ); my ($l) = ( $@ =~ /(\d+)\.$/ ); $@ =~ s/\(eval \d+\) //; croak "$cm: Evaluation failed (problem in ", __PACKAGE__, "?)\n", $@, "\n", join( "\n", @lines[ $l - 1 .. $l + 1 ] ), "\n"; } } Class::Generate::Class_Holder::store($class); }; $constructor_parameter_passing_style = sub { # Establish the parameter-passing style my ( $style, # for a class' constructor, meanwhile @values, # checking for mismatches w.r.t. the $parent_with_constructor, # class' superclass. Return an $parent_constructor_package_name ); # appropriate style. if ( defined $class->parents ) { $parent_with_constructor = Class::Generate::Support::class_containing_method( 'new', $class ); $parent_constructor_package_name = ( ref $parent_with_constructor ? $parent_with_constructor->name : $parent_with_constructor ); } ( ( $style, @values ) = $param_style_spec->values )[0] eq 'key_value' and do { if ( defined $parent_with_constructor && ref $parent_with_constructor && index( ref $parent_with_constructor, $containing_package ) == 0 ) { my $invoked_constructor_style = $parent_with_constructor->constructor->style; unless ( $invoked_constructor_style->isa( $containing_package . 'Key_Value' ) || $invoked_constructor_style->isa( $containing_package . 'Own' ) ) { warnings::warn( qq{$cm: Probable mismatch calling constructor in superclass "$parent_constructor_package_name"} ) if warnings::enabled(); } } return Class::Generate::Key_Value->new( 'params', $class->public_member_names ); }; $style eq 'positional' and do { &$check_for_invalid_parameter_names(@values); my @member_names = $class->public_member_names; croak "$cm: Missing/extra members in style" unless $#values == $#member_names; return Class::Generate::Positional->new(@values); }; $style eq 'mix' and do { &$check_for_invalid_parameter_names(@values); my @member_names = $class->public_member_names; croak "$cm: Extra parameters in style specifier" unless $#values <= $#member_names; my %kv_members = map( ( $_ => 1 ), @member_names ); delete @kv_members{@values}; return Class::Generate::Mix->new( 'params', [@values], keys %kv_members ); }; $style eq 'own' and do { for ( my $i = 0 ; $i <= $#values ; $i++ ) { &$store_initial_value_reference( \$values[$i], $parent_constructor_package_name . '::new' ) if ref $values[$i]; } return Class::Generate::Own->new( [@values] ); }; croak qq|$cm: Invalid parameter passing style "$style"|; }; $check_for_invalid_parameter_names = sub { my @param_names = @_; my $i = 0; for my $param (@param_names) { croak qq|$cm: Error in new => { style => '... $param' }: $param is not a member| if !defined $class->members($param); croak qq|$cm: Error in new => { style => '... $param' }: $param is not a public member| if $class->private($param) || $class->protected($param); } my %uses; for my $param (@param_names) { $uses{$param}++; } %uses = map( ( $uses{$_} > 1 ? ( $_ => $uses{$_} ) : () ), keys %uses ); if (%uses) { croak "$cm: Error in new => { style => '...' }: ", join( '; ', map qq|Name "$_" used $uses{$_} times|, keys %uses ); } }; $croak_if_duplicate_names = sub { my $class_spec = $_[0]; my ( @names, %uses ); if ( UNIVERSAL::isa( $class_spec, 'ARRAY' ) ) { for ( my $i = 0 ; $i <= $#$class_spec ; $i += 2 ) { push @names, $$class_spec[$i]; } } else { @names = keys %$class_spec; } for (@names) { $uses{ substr( $_, 0, 1 ) eq '&' ? substr( $_, 1 ) : $_ }++; } %uses = map( ( $uses{$_} > 1 ? ( $_ => $uses{$_} ) : () ), keys %uses ); if (%uses) { croak "$cm: ", join( '; ', map qq|Name "$_" used $uses{$_} times|, keys %uses ); } }; $invalid_spec_message = sub { return sprintf qq|$cm: Invalid specification of %s "%s" ($sh_needed with "%s" key)|, @_; }; package Class::Generate::Class_Holder; # This package encapsulates functions $Class::Generate::Class_Holder::VERSION = '1.18'; use strict; # related to storing and retrieving # information on classes. It lets classes # saved in files be reused transparently. my %classes; sub store($) { # Given a class, store it so it's my $class = $_[0]; # accessible in future invocations of $classes{ $class->name } = $class; # class() and subclass(). } # Given a class name, try to return an instance of Class::Generate::Class # that models the class. The instance comes from one of 3 places. We # first try to get it from wherever store() puts it. If that fails, # we check to see if the variable %::_cginfo exists (see # form(), below); if it does, we use the information it contains to # create an instance of Class::Generate::Class. If %::_cginfo # doesn't exist, the package wasn't created by Class::Generate. We try # to infer some characteristics of the class. sub get($;$) { my ( $class_name, $default_type ) = @_; return $classes{$class_name} if exists $classes{$class_name}; return undef if !eval '%' . $class_name . '::'; # Package doesn't exist. my ( $class, %info ); if ( !eval "exists \$" . $class_name . '::{_cginfo}' ) { # Package exists but is return undef if !defined $default_type; # not a class generated if ( $default_type eq 'ARRAY' ) { # by Class::Generate. $class = new Class::Generate::Array_Class $class_name; } else { $class = new Class::Generate::Hash_Class $class_name; } $class->constructor( new Class::Generate::Constructor ); $class->constructor->style( new Class::Generate::Own ); $classes{$class_name} = $class; return $class; } eval '%info = %' . $class_name . '::_cginfo'; if ( $info{base} eq 'ARRAY' ) { $class = Class::Generate::Array_Class->new( $class_name, last => $info{last} ); } else { $class = Class::Generate::Hash_Class->new($class_name); } if ( exists $info{members} ) { # Add members ... while ( my ( $name, $mem_info_ref ) = each %{ $info{members} } ) { my ( $member, %mem_info ); %mem_info = %$mem_info_ref; DEFN: { $mem_info{type} eq "\$" and do { $member = Class::Generate::Scalar_Member->new($name); last DEFN; }; $mem_info{type} eq '@' and do { $member = Class::Generate::Array_Member->new($name); last DEFN; }; $mem_info{type} eq '%' and do { $member = Class::Generate::Hash_Member->new($name); last DEFN; }; } $member->base( $mem_info{base} ) if exists $mem_info{base}; $class->members( $name, $member ); } } if ( exists $info{class_methods} ) { # Add methods... for my $name ( @{ $info{class_methods} } ) { $class->user_defined_methods( $name, Class::Generate::Class_Method->new($name) ); } } if ( exists $info{instance_methods} ) { for my $name ( @{ $info{instance_methods} } ) { $class->user_defined_methods( $name, Class::Generate::Method->new($name) ); } } if ( exists $info{protected} ) { # Set access ... for my $protected_member ( @{ $info{protected} } ) { $class->protected( $protected_member, 1 ); } } if ( exists $info{private} ) { for my $private_member ( @{ $info{private} } ) { $class->private( $private_member, 1 ); } } $class->excluded_methods_regexp( $info{emr} ) if exists $info{emr}; $class->constructor( new Class::Generate::Constructor ); CONSTRUCTOR_STYLE: { exists $info{kv_style} and do { $class->constructor->style( new Class::Generate::Key_Value 'params', @{ $info{kv_style} } ); last CONSTRUCTOR_STYLE; }; exists $info{pos_style} and do { $class->constructor->style( new Class::Generate::Positional( @{ $info{pos_style} } ) ); last CONSTRUCTOR_STYLE; }; exists $info{mix_style} and do { $class->constructor->style( new Class::Generate::Mix( 'params', [ @{ $info{mix_style}{keyed} } ], @{ $info{mix_style}{pos} } ) ); last CONSTRUCTOR_STYLE; }; exists $info{own_style} and do { $class->constructor->style( new Class::Generate::Own( @{ $info{own_style} } ) ); last CONSTRUCTOR_STYLE; }; } $classes{$class_name} = $class; return $class; } sub remove($) { delete $classes{ $_[0] }; } sub form($) { my $class = $_[0]; my $form = qq|use vars qw(\%_cginfo);\n| . '%_cginfo = ('; if ( $class->isa('Class::Generate::Array_Class') ) { $form .= q|base => 'ARRAY', last => | . $class->last; } else { $form .= q|base => 'HASH'|; } if ( my @members = $class->members_values ) { $form .= ', members => { ' . join( ', ', map( member($_), @members ) ) . ' }'; } my ( @class_methods, @instance_methods ); for my $m ( $class->user_defined_methods_values ) { if ( $m->isa('Class::Generate::Class_Method') ) { push @class_methods, $m->name; } else { push @instance_methods, $m->name; } } $form .= comma_prefixed_list_of_values( 'class_methods', @class_methods ); $form .= comma_prefixed_list_of_values( 'instance_methods', @instance_methods ); $form .= comma_prefixed_list_of_values( 'protected', do { my %p = $class->protected; keys %p } ); $form .= comma_prefixed_list_of_values( 'private', do { my %p = $class->private; keys %p } ); if ( my $emr = $class->excluded_methods_regexp ) { $emr =~ s/\'/\\\'/g; $form .= ", emr => '$emr'"; } if ( ( my $constructor = $class->constructor ) ) { my $style = $constructor->style; STYLE: { $style->isa('Class::Generate::Key_Value') and do { my @kpn = $style->keyed_param_names; if (@kpn) { $form .= comma_prefixed_list_of_values( 'kv_style', $style->keyed_param_names ); } else { $form .= ', kv_style => []'; } last STYLE; }; $style->isa('Class::Generate::Positional') and do { my @members = sort { $style->order($a) <=> $style->order($b) } do { my %m = $style->order; keys %m }; if (@members) { $form .= comma_prefixed_list_of_values( 'pos_style', @members ); } else { $form .= ', pos_style => []'; } last STYLE; }; $style->isa('Class::Generate::Mix') and do { my @keyed_members = $style->keyed_param_names; my @pos_members = sort { $style->order($a) <=> $style->order($b) } do { my %m = $style->order; keys %m }; if ( @keyed_members || @pos_members ) { my $km_form = list_of_values( 'keyed', @keyed_members ); my $pm_form = list_of_values( 'pos', @pos_members ); $form .= ', mix_style => {' . join( ', ', grep( length > 0, ( $km_form, $pm_form ) ) ) . '}'; } else { $form .= ', mix_style => {}'; } last STYLE; }; $style->isa('Class::Generate::Own') and do { my @super_values = $style->super_values; if (@super_values) { for my $sv (@super_values) { $sv =~ s/\'/\\\'/g; } $form .= comma_prefixed_list_of_values( 'own_style', @super_values ); } else { $form .= ', own_style => []'; } last STYLE; }; } } $form .= ');' . "\n"; return $form; } sub member($) { my $member = $_[0]; my $base; my $form = $member->name . ' => {'; $form .= " type => '" . ( $member->isa('Class::Generate::Scalar_Member') ? "\$" : $member->isa('Class::Generate::Array_Member') ? '@' : '%' ) . "'"; if ( defined( $base = $member->base ) ) { $form .= ", base => '$base'"; } return $form . '}'; } sub list_of_values($@) { my ( $key, @list ) = @_; return '' if !@list; return "$key => [" . join( ', ', map( "'$_'", @list ) ) . ']'; } sub comma_prefixed_list_of_values($@) { return $#_ > 0 ? ', ' . list_of_values( $_[0], @_[ 1 .. $#_ ] ) : ''; } package Class::Generate::Member_Names; # This package encapsulates functions $Class::Generate::Member_Names::VERSION = '1.18'; use strict; # to handle name substitution in # user-defined code. my ( $member_regexp, # Regexp of accessible members. $accessor_regexp, # Regexp of accessible member accessors (x_size, etc.). $user_defined_methods_regexp , # Regexp of accessible user-defined instance methods. $nonpublic_member_regexp , # (For class methods) Regexp of accessors for protected and private members. $private_class_methods_regexp ); # (Ditto) Regexp of private class methods. sub accessible_member_regexps($;$); sub accessible_members($;$); sub accessible_accessor_regexps($;$); sub accessible_user_defined_method_regexps($;$); sub class_of($$;$); sub member_index($$); sub set_element_regexps() { # Establish the regexps for my @names; # name substitution. # First for members... @names = accessible_member_regexps($class); if ( !@names ) { undef $member_regexp; } else { $member_regexp = '(?:\b(?:my|local)\b[^=;()]+)?(' . join( '|', sort { length $b <=> length $a } @names ) . ')\b'; } # Next for accessors (e.g., x_size)... @names = accessible_accessor_regexps($class); if ( !@names ) { undef $accessor_regexp; } else { $accessor_regexp = '&(' . join( '|', sort { length $b <=> length $a } @names ) . ')\b(?:\s*\()?'; } # Next for user-defined instance methods... @names = accessible_user_defined_method_regexps($class); if ( !@names ) { undef $user_defined_methods_regexp; } else { $user_defined_methods_regexp = '&(' . join( '|', sort { length $b <=> length $a } @names ) . ')\b(?:\s*\()?'; } # Next for protected and private members, and instance methods in class methods... if ( $class->class_methods ) { @names = ( map( $_->accessor_names( $class, $_->name ), grep $class->protected( $_->name ) || $class->private( $_->name ), $class->members_values ), grep( $class->private($_) || $class->protected($_), map( $_->name, $class->instance_methods ) ) ); if ( !@names ) { undef $nonpublic_member_regexp; } else { $nonpublic_member_regexp = join( '|', sort { length $b <=> length $a } @names ); } } else { undef $nonpublic_member_regexp; } # Finally for private class methods invoked from class and instance methods. if ( my @private_class_methods = grep $_->isa('Class::Generate::Class_Method') && $class->private( $_->name ), $class->user_defined_methods ) { $private_class_methods_regexp = $class->name . '\s*->\s*(' . join( '|', map $_->name, @private_class_methods ) . ')' . '(\s*\((?:\s*\))?)?'; } else { undef $private_class_methods_regexp; } } sub substituted($) { # Within a code fragment, replace my $code = $_[0]; # member names and accessors with the # appropriate forms. $code =~ s/$member_regexp/member_invocation($1, $&)/eg if defined $member_regexp; $code =~ s/$accessor_regexp/accessor_invocation($1, $+, $&)/eg if defined $accessor_regexp; $code =~ s/$user_defined_methods_regexp/accessor_invocation($1, $1, $&)/eg if defined $user_defined_methods_regexp; $code =~ s/$private_class_methods_regexp/nonpublic_method_invocation("'" . $class->name . "'", $1, $2)/eg if defined $private_class_methods_regexp; return $code; } # Perform the actual substitution sub member_invocation($$) { # for member references. my ( $member_reference, $match ) = @_; my ( $name, $type, $form, $index ); return $member_reference if $match =~ /\A(?:my|local)\b[^=;()]+$member_reference$/s; $member_reference =~ /^(\W+)(\w+)$/; $name = $2; return $member_reference if !defined( $index = member_index( $class, $name ) ); $type = $1; $form = $class->instance_var . '->' . $index; return $type eq '$' ? $form : $type . '{' . $form . '}'; } # Perform the actual substitution for sub accessor_invocation($$$) { # accessor and user-defined method references. my ( $accessor_name, $element_name, $match ) = @_; my $prefix = $class->instance_var . '->'; my $c = class_of( $element_name, $class ); if ( !( $c->protected($element_name) || $c->private($element_name) ) ) { return $prefix . $accessor_name . ( substr( $match, -1 ) eq '(' ? '(' : '' ); } if ( $c->private($element_name) || $c->name eq $class->name ) { return "$prefix\$$accessor_name(" if substr( $match, -1 ) eq '('; return "$prefix\$$accessor_name()"; } my $form = "&{$prefix" . $class->protected_members_info_index . qq|->{'$accessor_name'}}(|; $form .= $class->instance_var . ','; return substr( $match, -1 ) eq '(' ? $form : $form . ')'; } sub substituted_in_class_method { my $method = $_[0]; my ( @objs, $code, @private_class_methods ); $code = $method->body; if ( defined $nonpublic_member_regexp && ( @objs = $method->objects ) ) { my $nonpublic_member_invocation_regexp = '(' . join( '|', map( quotemeta($_), @objs ) ) . ')' . '\s*->\s*(' . $nonpublic_member_regexp . ')' . '(\s*\((?:\s*\))?)?'; $code =~ s/$nonpublic_member_invocation_regexp/nonpublic_method_invocation($1, $2, $3)/ge; } if ( defined $private_class_methods_regexp ) { $code =~ s/$private_class_methods_regexp/nonpublic_method_invocation("'" . $class->name . "'", $1, $2)/ge; } return $code; } sub nonpublic_method_invocation { # Perform the actual my ( $object, $nonpublic_member, $paren_matter ) = @_; # substitution for my $form = '&$' . $nonpublic_member . '(' . $object; # nonpublic method and if ( defined $paren_matter ) { # member references. if ( index( $paren_matter, ')' ) != -1 ) { $form .= ')'; } else { $form .= ', '; } } else { $form .= ')'; } return $form; } sub member_index($$) { my ( $class, $member_name ) = @_; return $class->index($member_name) if defined $class->members($member_name); for my $parent ( grep ref $_, $class->parents ) { my $index = member_index( $parent, $member_name ); return $index if defined $index; } return undef; } sub accessible_member_regexps($;$) { my ( $class, $disallow_private_members ) = @_; my @members; if ($disallow_private_members) { @members = grep !$class->private( $_->name ), $class->members_values; } else { @members = $class->members_values; } return ( map( $_->method_regexp($class), @members ), map( accessible_member_regexps( $_, 1 ), grep( ref $_, $class->parents ) ) ); } sub accessible_members($;$) { my ( $class, $disallow_private_members ) = @_; my @members; if ($disallow_private_members) { @members = grep !$class->private( $_->name ), $class->members_values; } else { @members = $class->members_values; } return ( @members, map( accessible_members( $_, 1 ), grep( ref $_, $class->parents ) ) ); } sub accessible_accessor_regexps($;$) { my ( $class, $disallow_private_members ) = @_; my ( $member_name, @accessor_names ); for my $member ( $class->members_values ) { next if $class->private( $member_name = $member->name ) && $disallow_private_members; for my $accessor_name ( grep $class->include_method($_), $member->accessor_names( $class, $member_name ) ) { $accessor_name =~ s/$member_name/($&)/; push @accessor_names, $accessor_name; } } return ( @accessor_names, map( accessible_accessor_regexps( $_, 1 ), grep( ref $_, $class->parents ) ) ); } sub accessible_user_defined_method_regexps($;$) { my ( $class, $disallow_private_methods ) = @_; return ( ( $disallow_private_methods ? grep !$class->private($_), $class->user_defined_methods_keys : $class->user_defined_methods_keys ), map( accessible_user_defined_method_regexps( $_, 1 ), grep( ref $_, $class->parents ) ) ); } # Given element E and class C, return C if E is an sub class_of($$;$) { # element of C; if not, search parents recursively. my ( $element_name, $class, $disallow_private_members ) = @_; return $class if ( defined $class->members($element_name) || defined $class->user_defined_methods($element_name) ) && ( !$disallow_private_members || !$class->private($element_name) ); for my $parent ( grep ref $_, $class->parents ) { my $c = class_of( $element_name, $parent, 1 ); return $c if defined $c; } return undef; } package Class::Generate::Code_Checker; # This package encapsulates $Class::Generate::Code_Checker::VERSION = '1.18'; use strict; # checking for warnings and use Carp; # errors in user-defined code. my $package_decl; my $member_error_message = '%s, member "%s": In "%s" code: %s'; my $method_error_message = '%s, method "%s": %s'; sub create_code_checking_package($); sub fragment_as_sub($$\@;\@); sub collect_code_problems($$$$@); # Check each user-defined code fragment in $class for errors. This includes # pre, post, and assert code, as well as user-defined methods. Set # $errors_found according to whether errors (not warnings) were found. sub check_user_defined_code($$$$) { my ( $class, $class_name_label, $warnings, $errors ) = @_; my ( $code, $instance_var, @valid_variables, @class_vars, $w, $e, @members, $problems_in_pre, %seen ); create_code_checking_package $class; @valid_variables = map { $seen{ $_->name } ? () : do { $seen{ $_->name } = 1; $_->as_var } } ( ( @members = $class->members_values ), Class::Generate::Member_Names::accessible_members($class) ); @class_vars = $class->class_vars; $instance_var = $class->instance_var; @$warnings = (); undef $$errors; for my $member ( $class->constructor, @members ) { if ( defined( $code = $member->pre ) ) { $code = fragment_as_sub $code, $instance_var, @class_vars, @valid_variables; collect_code_problems $code, $warnings, $errors, $member_error_message, $class_name_label, $member->name, 'pre'; $problems_in_pre = @$warnings || $$errors; } # Because post shares pre's scope, check post with pre prepended. # Strip newlines in pre to preserve line numbers in post. if ( defined( $code = $member->post ) ) { my $pre = $member->pre; if ( defined $pre && !$problems_in_pre ) { # Don't report errors $pre =~ s/\n+/ /g; # in pre again. $code = $pre . $code; } $code = fragment_as_sub $code, $instance_var, @class_vars, @valid_variables; collect_code_problems $code, $warnings, $errors, $member_error_message, $class_name_label, $member->name, 'post'; } if ( defined( $code = $member->assert ) ) { $code = fragment_as_sub "unless($code){die}", $instance_var, @class_vars, @valid_variables; collect_code_problems $code, $warnings, $errors, $member_error_message, $class_name_label, $member->name, 'assert'; } } for my $method ( $class->user_defined_methods_values ) { if ( $method->isa('Class::Generate::Class_Method') ) { $code = fragment_as_sub $method->body, $class->class_var, @class_vars; } else { $code = fragment_as_sub $method->body, $instance_var, @class_vars, @valid_variables; } collect_code_problems $code, $warnings, $errors, $method_error_message, $class_name_label, $method->name; } } sub create_code_checking_package($) { # Each class with user-defined code gets my $class = $_[0]; # its own package in which that code is # evaluated. Create said package. $package_decl = 'package ' . __PACKAGE__ . '::check::' . $class->name . ";"; $package_decl .= 'use strict;' if $class->strict; my $packages = ''; if ( $class->check_params ) { $packages .= 'use Carp;'; $packages .= join( ';', $class->warnings_pragmas ); } $packages .= join( '', map( 'use ' . $_ . ';', $class->use_packages ) ); $packages .= 'use vars qw(@ISA);' if $class->parents; eval $package_decl . $packages; } # Evaluate a code fragment, passing on sub collect_code_problems($$$$@) { # warnings and errors. my ( $code_form, $warnings, $errors, $error_message, @params ) = @_; my @warnings; local $SIG{__WARN__} = sub { push @warnings, $_[0] }; local $SIG{__DIE__}; eval $package_decl . $code_form; push @$warnings, map( filtered_message( $error_message, $_, @params ), @warnings ); $$errors .= filtered_message( $error_message, $@, @params ) if $@; } sub filtered_message { # Clean up errors and messages my ( $message, $error, @params ) = @_; # a little by removing the $error =~ s/\(eval \d+\) //g; # "(eval N)" forms that perl return sprintf( $message, @params, $error ); # inserts. } sub fragment_as_sub($$\@;\@) { my ( $code, $id_var, $class_vars, $valid_vars ) = @_; my $form; $form = "sub{my $id_var;"; if ( $#$class_vars >= 0 ) { $form .= 'my(' . join( ',', map( ( ref $_ ? keys %$_ : $_ ), @$class_vars ) ) . ');'; } if ( $valid_vars && $#$valid_vars >= 0 ) { $form .= 'my(' . join( ',', @$valid_vars ) . ');'; } $form .= '{' . $code . '}};'; } package Class::Generate::Array; # Given a string or an ARRAY, return an $Class::Generate::Array::VERSION = '1.18'; use strict; # object that is either the ARRAY or use Carp; # the string made into an ARRAY by # splitting the string on white space. sub new { my $class = shift; my $self; if ( !ref $_[0] ) { $self = [ split /\s+/, $_[0] ]; } elsif ( UNIVERSAL::isa( $_[0], 'ARRAY' ) ) { $self = $_[0]; } else { croak 'Expected string or array reference'; } bless $self, $class; return $self; } sub values { my $self = shift; return @$self; } package Class::Generate::Hash; # Given a string or a HASH and a key $Class::Generate::Hash::VERSION = '1.18'; use strict; # name, return an object that is either use Carp; # the HASH or a HASH of the form # (key => string). Also, if the object sub new { # is a HASH, it *must* contain the key. my $class = shift; my $self; my ( $value, $key ) = @_; if ( !ref $value ) { $self = { $key => $value }; } else { croak 'Expected string or hash reference' unless UNIVERSAL::isa( $value, 'HASH' ); croak qq|Missing "$key"| unless exists $value->{$key}; $self = $value; } bless $self, $class; return $self; } package Class::Generate::Support; # Miscellaneous support routines. $Class::Generate::Support::VERSION = '1.18'; no strict; # Definitely NOT strict! # Return the superclass of $class that sub class_containing_method { # contains the method that the form my ( $method, $class ) = @_; # (new $class)->$method would invoke. for my $parent ( $class->parents ) { # Return undef if no such class exists. local *stab = eval( '*' . ( ref $parent ? $parent->name : $parent ) . '::' ); if ( exists $stab{$method} && do { local *method_entry = $stab{$method}; defined &method_entry } ) { return $parent; } return class_containing_method( $method, $parent ); } return undef; } my %map = ( '@' => 'ARRAY', '%' => 'HASH' ); sub verify_value($$) { # Die if a given value (ref or string) my ( $value, $type ) = @_; # is not the specified type. # The following code is not wrong, but it could be smarter. if ( $type =~ /^\w/ ) { $map{$type} = $type; } else { $type = substr $type, 0, 1; } return if $type eq '$'; local $SIG{__WARN__} = sub { }; my $result; $result = ref $value ? $value : eval $value; die "Wrong type" if !UNIVERSAL::isa( $result, $map{$type} ); } use strict; sub comment_form { # Given arbitrary text, return a form that my $comment = $_[0]; # is a valid Perl comment of that text. $comment =~ s/^/# /mg; $comment .= "\n" if substr( $comment, -1, 1 ) ne "\n"; return $comment; } sub my_decl_form { # Given a non-empty set of variable names, my @vars = @_; # return a form declaring them as "my" variables. return 'my ' . ( $#vars == 0 ? $vars[0] : '(' . join( ', ', @vars ) . ')' ) . ";\n"; } package Class::Generate::Member; # A virtual class describing class $Class::Generate::Member::VERSION = '1.18'; use strict; # members. sub new { my $class = shift; my $self = { name => $_[0], @_[ 1 .. $#_ ] }; bless $self, $class; return $self; } sub name { my $self = shift; return $self->{'name'}; } sub default { my $self = shift; return $self->{'default'} if $#_ == -1; $self->{'default'} = $_[0]; } sub base { my $self = shift; return $self->{'base'} if $#_ == -1; $self->{'base'} = $_[0]; } sub assert { my $self = shift; return $self->{'assert'} if $#_ == -1; $self->{'assert'} = $_[0]; } sub post { my $self = shift; return $self->{'post'} if $#_ == -1; $self->{'post'} = possibly_append_semicolon_to( $_[0] ); } sub pre { my $self = shift; return $self->{'pre'} if $#_ == -1; $self->{'pre'} = possibly_append_semicolon_to( $_[0] ); } sub possibly_append_semicolon_to { # If user omits a trailing semicolon my $code = $_[0]; # (or doesn't use braces), add one. if ( $code !~ /[;\}]\s*\Z/s ) { $code =~ s/\s*\Z/;$&/s; } return $code; } sub comment { my $self = shift; return $self->{'comment'}; } sub key { my $self = shift; return $self->{'key'} if $#_ == -1; $self->{'key'} = $_[0]; } sub nocopy { my $self = shift; return $self->{'nocopy'} if $#_ == -1; $self->{'nocopy'} = $_[0]; } sub assertion { # Return a form that croaks if my $self = shift; # the member's assertion fails. my $class = $_[0]; my $assertion = $self->{'assert'}; return undef if !defined $assertion; my $quoted_form = $assertion; $quoted_form =~ s/'/\\'/g; $assertion = Class::Generate::Member_Names::substituted($assertion); return qq|unless ( $assertion ) { croak '| . $self->name_form($class) . qq|Failed assertion: $quoted_form' }|; } sub param_message { # Encapsulate the messages for my $self = shift; # incorrect parameters. my $class = $_[0]; my $name = $self->name; my $prefix_form = q|croak '| . $class->name . '::new' . ': '; $class->required($name) && !$self->default and do { return $prefix_form . qq|Missing or invalid value for $name'| if $self->can_be_invalid; return $prefix_form . qq|Missing value for required member $name'|; }; $self->can_be_invalid and do { return $prefix_form . qq|Invalid value for $name'|; }; } sub param_test { # Return a form that dies if a constructor my $self = shift; # parameter is not correctly passed. my $class = $_[0]; my $name = $self->name; my $param = $class->constructor->style->ref($name); my $exists = $class->constructor->style->existence_test($name) . ' ' . $param; my $form = ''; if ( $class->required($name) && !$self->default ) { $form .= $self->param_message($class) . ' unless ' . $exists; $form .= ' && ' . $self->valid_value_form($param) if $self->can_be_invalid; } elsif ( $self->can_be_invalid ) { $form .= $self->param_message($class) . ' unless ! ' . $exists . ' || ' . $self->valid_value_form($param); } return $form . ';'; } sub form { # Return a form for a member and all my $self = shift; # its relevant associated accessors. my $class = $_[0]; my ( $element, $exists, $lvalue, $values, $form, $body, $member_name ); $element = $class->instance_var . '->' . $class->index( $member_name = $self->name ); $exists = $class->existence_test . ' ' . $element; $lvalue = $self->lvalue('$_[0]') if $self->can('lvalue'); $values = $self->values('$_[0]') if $self->can('values'); $form = ''; $form .= Class::Generate::Support::comment_form( $self->comment ) if defined $self->comment; if ( $class->include_method($member_name) ) { $body = ''; for my $param_form ( $self->member_forms($class) ) { $body .= $self->$param_form( $class, $element, $exists, $lvalue, $values ); } $body .= ' ' . $self->param_count_error_form($class) . ";\n" if $class->check_params; $form .= $class->sub_form( $member_name, $member_name, $body ); } for my $a ( grep $_ ne $member_name, $self->accessor_names( $class, $member_name ) ) { $a =~ s/^([a-z]+)_$member_name$/$1_form/ || $a =~ s/^${member_name}_([a-z]+)$/$1_form/; $form .= $self->$a( $class, $element, $member_name, $exists ); } return $form; } sub invalid_value_assignment_message { # Return a form that dies, reporting my $self = shift; # a parameter that's not of the my $class = $_[0]; # correct type for its element. return 'croak \'' . $self->name_form($class) . 'Invalid parameter value (expected ' . $self->expected_type_form . ')\''; } sub valid_value_test_form { # Return a form that dies unless my $self = shift; # a value is of the correct type my $class = shift; # for the member. return $self->invalid_value_assignment_message($class) . ' unless ' . $self->valid_value_form(@_) . ';'; } sub param_must_be_checked { my $self = shift; my $class = $_[0]; return ( $class->required( $self->name ) && !defined $self->default ) || $self->can_be_invalid; } sub maybe_guarded { # If parameter checking is enabled, guard a my $self = shift; # form to check against a parameter my ( $form, $param_no, $class ) = @_; # count. In any case, format the form if ( $class->check_params ) { # a little. $form =~ s/^/\t/mg; return " \$#_ == $param_no\tand do {\n$form };\n"; } else { $form =~ s/^/ /mg; return $form; } } sub accessor_names { my $self = shift; my ( $class, $name ) = @_; return !( $class->readonly($name) || $class->required($name) ) ? ("undef_$name") : (); } sub undef_form { # Return the form to undefine my $self = shift; # a member. my ( $class, $element, $member_name ) = @_[ 0 .. 2 ]; return $class->sub_form( $member_name, 'undef_' . $member_name, ' ' . $class->undef_form . " $element;\n" ); } sub param_count_error_form { # Return a form that standardizes my $self = shift; # the message for dieing because my $class = $_[0]; # of an incorrect parameter count. return q|croak '| . $self->name_form($class) . q|Invalid number of parameters (', ($#_+1), ')'|; } sub name_form { # Standardize a method name my $self = shift; # for error messages. my $class = $_[0]; return $class->name . '::' . $self->name . ': '; } sub param_assignment_form { # Return a form that assigns a parameter my $self = shift; # value to the member. my ( $class, $style ) = @_; my ( $name, $element, $param, $default, $exists ); $name = $self->name; $element = $class->instance_var . '->' . $class->index($name); $param = $style->ref($name); $default = $self->default; $exists = $style->existence_test($name) . ' ' . $param; my $form = " $element = "; if ( defined $default ) { $form .= "$exists ? $param : $default"; } elsif ( $class->check_params && $class->required($name) ) { $form .= $param; } else { $form .= "$param if $exists"; } return $form . ";\n"; } sub default_assignment_form { # Return a form that assigns a default value my $self = shift; # to a member. my $class = $_[0]; my $element; $element = $class->instance_var . '->' . $class->index( $self->name ); return " $element = " . $self->default . ";\n"; } package Class::Generate::Scalar_Member; # A Member subclass for $Class::Generate::Scalar_Member::VERSION = '1.18'; use strict; # scalar class members. use vars qw(@ISA); # accessor accepts 0 or 1 parameters. @ISA = qw(Class::Generate::Member); sub member_forms { my $self = shift; my $class = $_[0]; return $class->readonly( $self->name ) ? 'no_params' : ( 'no_params', 'one_param' ); } sub no_params { my $self = shift; my ( $class, $element ) = @_; if ( $class->readonly( $self->name ) && !$class->check_params ) { return " return $element;\n"; } return " \$#_ == -1\tand do { return $element };\n"; } sub one_param { my $self = shift; my ( $class, $element ) = @_; my $form = ''; $form .= Class::Generate::Member_Names::substituted( $self->pre ) if defined $self->pre; $form .= $self->valid_value_test_form( $class, '$_[0]' ) . "\n" if $class->check_params && defined $self->base; $form .= "$element = \$_[0];\n"; $form .= Class::Generate::Member_Names::substituted( $self->post ) if defined $self->post; $form .= $self->assertion($class) . "\n" if defined $class->check_params && defined $self->assert; $form .= "return;\n"; return $self->maybe_guarded( $form, 0, $class ); } sub valid_value_form { # Return a form that tests if my $self = shift; # a ref is of the correct my ($param) = @_; # base type. return qq|UNIVERSAL::isa($param, '| . $self->base . qq|')|; } sub can_be_invalid { # Validity for a scalar member my $self = shift; # is testable only if the member return defined $self->base; # is supposed to be a class. } sub as_var { my $self = shift; return '$' . $self->name; } sub method_regexp { my $self = shift; my $class = $_[0]; return $class->include_method( $self->name ) ? ( '\$' . $self->name ) : (); } sub accessor_names { my $self = shift; my ( $class, $name ) = @_; return grep $class->include_method($_), ( $name, $self->SUPER::accessor_names( $class, $name ) ); } sub expected_type_form { my $self = shift; return $self->base; } sub copy_form { my $self = shift; my ( $from, $to ) = @_; my $form = " $to = $from"; if ( !$self->nocopy ) { $form .= '->copy' if $self->base; } $form .= " if defined $from;\n"; return $form; } sub equals { my $self = shift; my ( $index, $existence_test ) = @_; my ( $sr, $or ) = ( '$self->' . $index, '$o->' . $index ); my $form = " return undef if $existence_test $sr ^ $existence_test $or;\n" . " if ( $existence_test $sr ) { return undef unless $sr"; if ( $self->base ) { $form .= "->equals($or)"; } else { $form .= " eq $or"; } return $form . " }\n"; } package Class::Generate::List_Member; # A Member subclass for list $Class::Generate::List_Member::VERSION = '1.18'; use strict; # (array and hash) members. use vars qw(@ISA); # accessor accepts 0-2 parameters. @ISA = qw(Class::Generate::Member); sub member_forms { my $self = shift; my $class = $_[0]; return $class->readonly( $self->name ) ? ( 'no_params', 'one_param' ) : ( 'no_params', 'one_param', 'two_params' ); } sub no_params { my $self = shift; my ( $class, $element, $exists, $lvalue, $values ) = @_; return " \$#_ == -1\tand do { return $exists ? " . $self->whole_lvalue($element) . " : () };\n"; } sub one_param { my $self = shift; my ( $class, $element, $exists, $lvalue, $values ) = @_; my $form; if ( $class->accept_refs ) { $form = " \$#_ == 0\tand do {\n" . "\t" . "return ($exists ? ${element}->$lvalue : undef) if ! ref \$_[0];\n"; if ( $class->check_params && $class->readonly( $self->name ) ) { $form .= "croak '" . $self->name_form($class) . "Member is read-only';\n"; } else { $form .= "\t" . Class::Generate::Member_Names::substituted( $self->pre ) if defined $self->pre; $form .= "\t" . $self->valid_value_test_form( $class, '$_[0]' ) . "\n" if $class->check_params; $form .= "\t" . $self->whole_lvalue($element) . ' = ' . $self->whole_lvalue('$_[0]') . ";\n"; $form .= "\t" . Class::Generate::Member_Names::substituted( $self->post ) if defined $self->post; $form .= "\t" . $self->assertion($class) . "\n" if defined $class->check_params && defined $self->assert; $form .= "\t" . "return;\n"; } $form .= " };\n"; } else { $form = " \$#_ == 0\tand do { return $exists ? ${element}->$lvalue : undef };\n"; } return $form; } sub two_params { my $self = shift; my ( $class, $element, $exists, $lvalue, $values ) = @_; my $form = ''; $form .= Class::Generate::Member_Names::substituted( $self->pre ) if defined $self->pre; $form .= $self->valid_element_test( $class, '$_[1]' ) . "\n" if $class->check_params && defined $self->base; $form .= "${element}->$lvalue = \$_[1];\n"; $form .= Class::Generate::Member_Names::substituted( $self->post ) if defined $self->post; $form .= "return;\n"; return $self->maybe_guarded( $form, 1, $class ); } sub valid_value_form { # Return a form that tests if a my $self = shift; # parameter is a correct list reference my $param = $_[0]; # and (if relevant) if all of its my $base = $self->base; # elements have the correct base type. ref($self) =~ /::(\w+)_Member$/; my $form = "UNIVERSAL::isa($param, '" . uc($1) . "')"; if ( defined $base ) { $form .= qq| && ! grep ! (defined \$_ && UNIVERSAL::isa(\$_, '$base')), | . $self->values($param); } return $form; } sub valid_element_test { # Return a form that dies unless an my $self = shift; # element has the correct base type. my ( $class, $param ) = @_; return $self->invalid_value_assignment_message($class) . qq| unless UNIVERSAL::isa($param, '| . $self->base . q|');|; } sub valid_elements_test { # Return a form that dies unless all my $self = shift; # elements of a list are validly typed. my ( $class, $values ) = @_; my $base = $self->base; return $self->invalid_value_assignment_message($class) . q| unless ! grep ! UNIVERSAL::isa($_, '| . $self->base . qq|'), $values;|; } sub can_be_invalid { # A value for a list member can return 1; # always be invalid: the wrong } # type of list can be given. package Class::Generate::Array_Member; # A List subclass for array $Class::Generate::Array_Member::VERSION = '1.18'; use strict; # members. Provides the use vars qw(@ISA); # of accessing array members. @ISA = qw(Class::Generate::List_Member); sub lvalue { my $self = shift; return '[' . $_[0] . ']'; } sub whole_lvalue { my $self = shift; return '@{' . $_[0] . '}'; } sub values { my $self = shift; return '@{' . $_[0] . '}'; } sub size_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; return $class->sub_form( $member_name, $member_name . '_size', " return $exists ? \$#{$element} : -1;\n" ); } sub last_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; return $class->sub_form( $member_name, 'last_' . $member_name, " return $exists ? $element" . "[\$#{$element}] : undef;\n" ); } sub add_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; my $body = ''; $body .= ' ' . $self->valid_elements_test( $class, '@_' ) . "\n" if $class->check_params && defined $self->base; $body .= Class::Generate::Member_Names::substituted( $self->pre ) if defined $self->pre; $body .= ' push @{' . $element . '}, @_;' . "\n"; $body .= Class::Generate::Member_Names::substituted( $self->post ) if defined $self->post; $body .= ' ' . $self->assertion($class) . "\n" if defined $class->check_params && defined $self->assert; return $class->sub_form( $member_name, 'add_' . $member_name, $body ); } sub as_var { my $self = shift; return '@' . $self->name; } sub method_regexp { my $self = shift; my $class = $_[0]; return $class->include_method( $self->name ) ? ( '@' . $self->name, '\$#?' . $self->name ) : (); } sub accessor_names { my $self = shift; my ( $class, $name ) = @_; my @names = ( $name, "${name}_size", "last_$name", $self->SUPER::accessor_names( $class, $name ) ); push @names, "add_$name" if !$class->readonly($name); return grep $class->include_method($_), @names; } sub expected_type_form { my $self = shift; if ( defined $self->base ) { return 'reference to array of ' . $self->base; } else { return 'array reference'; } } sub copy_form { my $self = shift; my ( $from, $to ) = @_; my $form = " $to = "; if ( !$self->nocopy ) { $form .= '[ '; $form .= 'map defined $_ ? $_->copy : undef, ' if $self->base; $form .= "\@{$from} ]"; } else { $form .= $from; } $form .= " if defined $from;\n"; return $form; } sub equals { my $self = shift; my ( $index, $existence_test ) = @_; my ( $sr, $or ) = ( '$self->' . $index, '$o->' . $index ); my $form = " return undef if $existence_test($sr) ^ $existence_test($or);\n" . " if ( $existence_test $sr ) {\n" . " return undef unless (\$ub = \$#{$sr}) == \$#{$or};\n" . " for ( my \$i = 0; \$i <= \$ub; \$i++ ) {\n" . " return undef unless $sr" . '[$i]'; if ( $self->base ) { $form .= '->equals(' . $or . '[$i])'; } else { $form .= ' eq ' . $or . '[$i]'; } return $form . ";\n\t}\n }\n"; } package Class::Generate::Hash_Member; # A List subclass for Hash $Class::Generate::Hash_Member::VERSION = '1.18'; use strict; # members. Provides the n_keys use vars qw(@ISA); # specifics of accessing @ISA = qw(Class::Generate::List_Member); # hash members. sub lvalue { my $self = shift; return '{' . $_[0] . '}'; } sub whole_lvalue { my $self = shift; return '%{' . $_[0] . '}'; } sub values { my $self = shift; return 'values %{' . $_[0] . '}'; } sub delete_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; return $class->sub_form( $member_name, 'delete_' . $member_name, " delete \@{$element}{\@_} if $exists;\n" ); } sub keys_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; return $class->sub_form( $member_name, $member_name . '_keys', " return $exists ? keys \%{$element} : ();\n" ); } sub values_form { my $self = shift; my ( $class, $element, $member_name, $exists ) = @_; return $class->sub_form( $member_name, $member_name . '_values', " return $exists ? values \%{$element} : ();\n" ); } sub as_var { my $self = shift; return '%' . $self->name; } sub method_regexp { my $self = shift; my $class = $_[0]; return $class->include_method( $self->name ) ? ( '[%$]' . $self->name ) : (); } sub accessor_names { my $self = shift; my ( $class, $name ) = @_; my @names = ( $name, "${name}_keys", "${name}_values", $self->SUPER::accessor_names( $class, $name ) ); push @names, "delete_$name" if !$class->readonly($name); return grep $class->include_method($_), @names; } sub expected_type_form { my $self = shift; if ( defined $self->base ) { return 'reference to hash of ' . $self->base; } else { return 'hash reference'; } } sub copy_form { my $self = shift; my ( $from, $to ) = @_; if ( !$self->nocopy ) { if ( $self->base ) { return " if ( defined $from ) {\n" . "\t$to = {};\n" . "\twhile ( my (\$key, \$value) = each \%{$from} ) {\n" . "\t $to" . '->{$key} = defined $value ? $value->copy : undef;' . "\n" . "\t}\n" . " }\n"; } else { return " $to = { \%{$from} } if defined $from;\n"; } } else { return " $to = $from if defined $from;\n"; } } sub equals { my $self = shift; my ( $index, $existence_test ) = @_; my ( $sr, $or ) = ( '$self->' . $index, '$o->' . $index ); my $form = " return undef if $existence_test $sr ^ $existence_test $or;\n" . " if ( $existence_test $sr ) {\n" . ' @self_keys = keys %{' . $sr . '};' . "\n" . ' return undef unless $#self_keys == scalar(keys %{' . $or . '}) - 1;' . "\n" . ' for my $k ( @self_keys ) {' . "\n" . " return undef unless exists $or" . '{$k};' . "\n" . ' return undef if ($self_value_defined = defined ' . $sr . '{$k}) ^ defined ' . $or . '{$k};' . "\n" . ' if ( $self_value_defined ) { return undef unless '; if ( $self->base ) { $form .= $sr . '{$k}->equals(' . $or . '{$k})'; } else { $form .= $sr . '{$k} eq ' . $or . '{$k}'; } $form .= " }\n\t}\n }\n"; return $form; } package Class::Generate::Constructor; # The constructor is treated as a $Class::Generate::Constructor::VERSION = '1.18'; use strict; # special type of member. It includes use vars qw(@ISA); # constraints on required members. @ISA = qw(Class::Generate::Member); sub new { my $class = shift; my $self = $class->SUPER::new( 'new', @_ ); return $self; } sub style { my $self = shift; return $self->{'style'} if $#_ == -1; $self->{'style'} = $_[0]; } sub constraints { my $self = shift; return exists $self->{'constraints'} ? @{ $self->{'constraints'} } : () if $#_ == -1; return exists $self->{'constraints'} ? $self->{'constraints'}->[ $_[0] ] : undef if $#_ == 0; $self->{'constraints'}->[ $_[0] ] = $_[1]; } sub add_constraints { my $self = shift; push @{ $self->{'constraints'} }, @_; } sub constraints_size { my $self = shift; return exists $self->{'constraints'} ? $#{ $self->{'constraints'} } : -1; } sub constraint_form { my $self = shift; my ( $class, $style, $constraint ) = @_; my $param_given = $constraint; $param_given =~ s/\w+/$style->existence_test($&) . ' ' . $style->ref($&)/eg; $constraint =~ s/'/\\'/g; return q|croak '| . $self->name_form($class) . qq|Parameter constraint "$constraint" failed' unless $param_given;|; } sub param_tests_form { my $self = shift; my ( $class, $style ) = @_; my $form = ''; if ( !$class->parents && $style->can('params_check_form') ) { $form .= $style->params_check_form( $class, $self ); } if ( !$style->isa('Class::Generate::Own') ) { my @public_members = map $class->members($_), $class->public_member_names; for my $param_test ( map $_->param_must_be_checked($class) ? $_->param_test($class) : (), @public_members ) { $form .= ' ' . $param_test . "\n"; } for my $constraint ( $self->constraints ) { $form .= ' ' . $self->constraint_form( $class, $style, $constraint ) . "\n"; } } return $form; } sub assertions_form { my $self = shift; my $class = $_[0]; my $form = ''; $form .= ' ' . $self->assertion($class) . "\n" if defined $class->check_params && defined $self->assert; for my $member ( grep defined $_->assert, $class->members_values ) { $form .= ' ' . $member->assertion($class) . "\n"; } return $form; } sub form { my $self = shift; my $class = $_[0]; my $style = $self->style; my ( $iv, $cv ) = ( $class->instance_var, $class->class_var ); my $form; $form = "sub new {\n" . " my $cv = " . ( $class->nfi ? 'do { my $proto = shift; ref $proto || $proto }' : 'shift' ) . ";\n"; if ( $class->check_params && $class->virtual ) { $form .= q| croak '| . $self->name_form($class) . q|Virtual class' unless $class ne '| . $class->name . qq|';\n|; } $form .= $style->init_form( $class, $self ) if !$class->can_assign_all_params && $style->can('init_form'); $form .= $self->param_tests_form( $class, $style ) if $class->check_params; if ( defined $class->parents ) { $form .= $style->self_from_super_form($class); } else { $form .= ' my ' . $iv . ' = ' . $class->base . ";\n" . ' bless ' . $iv . ', ' . $cv . ";\n"; } if ( !$class->can_assign_all_params ) { $form .= $class->size_establishment($iv) if $class->can('size_establishment'); if ( !$style->isa('Class::Generate::Own') ) { for my $name ( $class->public_member_names ) { $form .= $class->members($name) ->param_assignment_form( $class, $style ); } } } $form .= $class->protected_members_info_form; for my $member ( grep( ( $style->isa('Class::Generate::Own') || $class->protected( $_->name ) || $class->private( $_->name ) ) && defined $_->default, $class->members_values ) ) { $form .= $member->default_assignment_form($class); } $form .= Class::Generate::Member_Names::substituted( $self->post ) if defined $self->post; $form .= $self->assertions_form($class) if $class->check_params; $form .= ' return ' . $iv . ";\n" . "}\n"; return $form; } package Class::Generate::Method; # A user-defined method, $Class::Generate::Method::VERSION = '1.18'; # with a name and body. sub new { my $class = shift; my $self = { name => $_[0], body => $_[1] }; bless $self, $class; return $self; } sub name { my $self = shift; return $self->{'name'}; } sub body { my $self = shift; return $self->{'body'}; } sub comment { my $self = shift; return $self->{'comment'} if $#_ == -1; $self->{'comment'} = $_[0]; } sub form { my $self = shift; my $class = $_[0]; my $form = ''; $form .= Class::Generate::Support::comment_form( $self->comment ) if defined $self->comment; $form .= $class->sub_form( $self->name, $self->name, Class::Generate::Member_Names::substituted( $self->body ) ); return $form; } package Class::Generate::Class_Method; # A user-defined class method, $Class::Generate::Class_Method::VERSION = '1.18'; use strict; # which may specify objects use vars qw(@ISA); # of the class used within its @ISA = qw(Class::Generate::Method); # body. sub objects { my $self = shift; return exists $self->{'objects'} ? @{ $self->{'objects'} } : () if $#_ == -1; return exists $self->{'objects'} ? $self->{'objects'}->[ $_[0] ] : undef if $#_ == 0; $self->{'objects'}->[ $_[0] ] = $_[1]; } sub add_objects { my $self = shift; push @{ $self->{'objects'} }, @_; } sub form { my $self = shift; my $class = $_[0]; return $class->class_sub_form( $self->name, Class::Generate::Member_Names::substituted_in_class_method($self) ); } package Class::Generate::Class; # A virtual class describing $Class::Generate::Class::VERSION = '1.18'; use strict; # a user-specified class. sub new { my $class = shift; my $self = { name => shift, @_ }; bless $self, $class; return $self; } sub name { my $self = shift; return $self->{'name'}; } sub parents { my $self = shift; return exists $self->{'parents'} ? @{ $self->{'parents'} } : () if $#_ == -1; return exists $self->{'parents'} ? $self->{'parents'}->[ $_[0] ] : undef if $#_ == 0; $self->{'parents'}->[ $_[0] ] = $_[1]; } sub add_parents { my $self = shift; push @{ $self->{'parents'} }, @_; } sub members { my $self = shift; return exists $self->{'members'} ? %{ $self->{'members'} } : () if $#_ == -1; return exists $self->{'members'} ? $self->{'members'}->{ $_[0] } : undef if $#_ == 0; $self->{'members'}->{ $_[0] } = $_[1]; } sub members_keys { my $self = shift; return exists $self->{'members'} ? keys %{ $self->{'members'} } : (); } sub members_values { my $self = shift; return exists $self->{'members'} ? values %{ $self->{'members'} } : (); } sub user_defined_methods { my $self = shift; return exists $self->{'udm'} ? %{ $self->{'udm'} } : () if $#_ == -1; return exists $self->{'udm'} ? $self->{'udm'}->{ $_[0] } : undef if $#_ == 0; $self->{'udm'}->{ $_[0] } = $_[1]; } sub user_defined_methods_keys { my $self = shift; return exists $self->{'udm'} ? keys %{ $self->{'udm'} } : (); } sub user_defined_methods_values { my $self = shift; return exists $self->{'udm'} ? values %{ $self->{'udm'} } : (); } sub class_vars { my $self = shift; return exists $self->{'class_vars'} ? @{ $self->{'class_vars'} } : () if $#_ == -1; return exists $self->{'class_vars'} ? $self->{'class_vars'}->[ $_[0] ] : undef if $#_ == 0; $self->{'class_vars'}->[ $_[0] ] = $_[1]; } sub add_class_vars { my $self = shift; push @{ $self->{'class_vars'} }, @_; } sub use_packages { my $self = shift; return exists $self->{'use_packages'} ? @{ $self->{'use_packages'} } : () if $#_ == -1; return exists $self->{'use_packages'} ? $self->{'use_packages'}->[ $_[0] ] : undef if $#_ == 0; $self->{'use_packages'}->[ $_[0] ] = $_[1]; } sub add_use_packages { my $self = shift; push @{ $self->{'use_packages'} }, @_; } sub excluded_methods_regexp { my $self = shift; return $self->{'em'} if $#_ == -1; $self->{'em'} = $_[0]; } sub private { my $self = shift; return exists $self->{'private'} ? %{ $self->{'private'} } : () if $#_ == -1; return exists $self->{'private'} ? $self->{'private'}->{ $_[0] } : undef if $#_ == 0; $self->{'private'}->{ $_[0] } = $_[1]; } sub protected { my $self = shift; return exists $self->{'protected'} ? %{ $self->{'protected'} } : () if $#_ == -1; return exists $self->{'protected'} ? $self->{'protected'}->{ $_[0] } : undef if $#_ == 0; $self->{'protected'}->{ $_[0] } = $_[1]; } sub required { my $self = shift; return exists $self->{'required'} ? %{ $self->{'required'} } : () if $#_ == -1; return exists $self->{'required'} ? $self->{'required'}->{ $_[0] } : undef if $#_ == 0; $self->{'required'}->{ $_[0] } = $_[1]; } sub readonly { my $self = shift; return exists $self->{'readonly'} ? %{ $self->{'readonly'} } : () if $#_ == -1; return exists $self->{'readonly'} ? $self->{'readonly'}->{ $_[0] } : undef if $#_ == 0; $self->{'readonly'}->{ $_[0] } = $_[1]; } sub constructor { my $self = shift; return $self->{'constructor'} if $#_ == -1; $self->{'constructor'} = $_[0]; } sub virtual { my $self = shift; return $self->{'virtual'} if $#_ == -1; $self->{'virtual'} = $_[0]; } sub comment { my $self = shift; return $self->{'comment'} if $#_ == -1; $self->{'comment'} = $_[0]; } sub accept_refs { my $self = shift; return $self->{'accept_refs'}; } sub strict { my $self = shift; return $self->{'strict'}; } sub nfi { my $self = shift; return $self->{'nfi'}; } sub warnings { my $self = shift; return $self->{'warnings'} if $#_ == -1; $self->{'warnings'} = $_[0]; } sub check_params { my $self = shift; return $self->{'check_params'} if $#_ == -1; $self->{'check_params'} = $_[0]; } sub instance_methods { my $self = shift; return grep !$_->isa('Class::Generate::Class_Method'), $self->user_defined_methods_values; } sub class_methods { my $self = shift; return grep $_->isa('Class::Generate::Class_Method'), $self->user_defined_methods_values; } sub include_method { my $self = shift; my $method_name = $_[0]; my $r = $self->excluded_methods_regexp; return !defined $r || $method_name !~ m/$r/; } sub member_methods_form { # Return a form containing methods for all my $self = shift; # non-private members in the class, plus my $form = ''; # private members used in class methods. for my $element ( $self->public_member_names, $self->protected_member_names, $self->private_members_used_in_user_defined_code ) { $form .= $self->members($element)->form($self); } $form .= "\n" if $form ne ''; return $form; } sub user_defined_methods_form { # Return a form containing all my $self = shift; # user-defined methods. my $form = join( '', map( $_->form($self), $self->user_defined_methods_values ) ); return length $form > 0 ? $form . "\n" : ''; } sub warnings_pragmas { # Return an array containing the my $self = shift; # warnings pragmas for the class. my $w = $self->{'warnings'}; return () if !defined $w; return ('no warnings;') if !$w; return ('use warnings;') if $w =~ /^\d+$/; return ("use warnings $w;") if !ref $w; my @pragmas; for ( my $i = 0 ; $i <= $#$w ; $i += 2 ) { my ( $key, $value ) = ( $$w[$i], $$w[ $i + 1 ] ); if ( $key eq 'register' ) { push @pragmas, 'use warnings::register;' if $value; } elsif ( defined $value && $value ) { if ( $value =~ /^\d+$/ ) { push @pragmas, $key . ' warnings;'; } else { push @pragmas, $key . ' warnings ' . $value . ';'; } } } return @pragmas; } sub warnings_form { # Return a form representing the my $self = shift; # warnings pragmas for a class. my @warnings_pragmas = $self->warnings_pragmas; return @warnings_pragmas ? join( "\n", @warnings_pragmas ) . "\n" : ''; } sub form { # Return a form representing my $self = shift; # a class. my $form; $form = 'package ' . $self->name . ";\n"; $form .= "use strict;\n" if $self->strict; $form .= join( "\n", map( "use $_;", $self->use_packages ) ) . "\n" if $self->use_packages; $form .= "use Carp;\n" if defined $self->{'check_params'}; $form .= $self->warnings_form; $form .= Class::Generate::Class_Holder::form($self); $form .= "\n"; $form .= Class::Generate::Support::comment_form( $self->comment ) if defined $self->comment; $form .= $self->isa_decl_form if $self->parents; $form .= $self->private_methods_decl_form if grep $self->private($_), $self->user_defined_methods_keys; $form .= $self->private_members_decl_form if $self->private_members_used_in_user_defined_code; $form .= $self->protected_methods_decl_form if grep $self->protected($_), $self->user_defined_methods_keys; $form .= $self->protected_members_decl_form if grep $self->protected($_), $self->members_keys; $form .= join( "\n", map( class_var_form($_), $self->class_vars ) ) . "\n\n" if $self->class_vars; $form .= $self->constructor->form($self) if $self->needs_constructor; $form .= $self->member_methods_form; $form .= $self->user_defined_methods_form; my $emr = $self->excluded_methods_regexp; $form .= $self->copy_form if !defined $emr || 'copy' !~ m/$emr/; $form .= $self->equals_form if ( !defined $emr || 'equals' !~ m/$emr/ ) && !defined $self->user_defined_methods('equals'); return $form; } sub class_var_form { # Return a form for declaring a class my $var_spec = $_[0]; # variable. Account for an initial value. return "my $var_spec;" if !ref $var_spec; return map { my $value = $$var_spec{$_}; "my $_ = " . ( ref $value ? substr( $_, 0, 1 ) . "{$value}" : $value ) . ';' } keys %$var_spec; } sub isa_decl_form { my $self = shift; my @parent_names = map !ref $_ ? $_ : $_->name, $self->parents; return "use vars qw(\@ISA);\n" . '@ISA = qw(' . join( ' ', @parent_names ) . ");\n"; } sub sub_form { # Return a declaration for a sub, as an my $self = shift; # assignment to a variable if not public. my ( $element_name, $sub_name, $body ) = @_; my ( $form, $not_public ); $not_public = $self->private($element_name) || $self->protected($element_name); $form = ( $not_public ? "\$$sub_name = sub" : "sub $sub_name" ) . " {\n" . ' my ' . $self->instance_var . " = shift;\n" . $body . '}'; $form .= ';' if $not_public; return $form . "\n"; } sub class_sub_form { # Ditto, but for a class method. my $self = shift; my ( $method_name, $body ) = @_; my ( $form, $not_public ); $not_public = $self->private($method_name) || $self->protected($method_name); $form = ( $not_public ? "\$$method_name = sub" : "sub $method_name" ) . " {\n" . ' my ' . $self->class_var . " = shift;\n" . $body . '}'; $form .= ';' if $not_public; return $form . "\n"; } sub private_methods_decl_form { # Private methods are implemented as CODE refs. my $self = shift; # Return a form declaring the variables to hold them. my @private_methods = grep $self->private($_), $self->user_defined_methods_keys; return Class::Generate::Support::my_decl_form( map "\$$_", @private_methods ); } sub private_members_used_in_user_defined_code { # Return the names of all private my $self = shift; # members that appear in user-defined code. my @private_members = grep $self->private($_), $self->members_keys; return () if !@private_members; my $member_regexp = join '|', @private_members; my %private_members; for my $code ( map( $_->body, $self->user_defined_methods_values ), grep( defined $_, ( map( ( $_->pre, $_->post, $_->assert ), $self->members_values ), map( ( $_->post, $_->assert ), $self->constructor ) ) ) ) { while ( $code =~ /($member_regexp)/g ) { $private_members{$1}++; } } return keys %private_members; } sub nonpublic_members_decl_form { my $self = shift; my @members = @_; my @accessor_names = map( $_->accessor_names( $self, $_->name ), @members ); return Class::Generate::Support::my_decl_form( map "\$$_", @accessor_names ); } sub private_members_decl_form { my $self = shift; return $self->nonpublic_members_decl_form( map $self->members($_), $self->private_members_used_in_user_defined_code ); } sub protected_methods_decl_form { my $self = shift; return Class::Generate::Support::my_decl_form( map $self->protected($_) ? "\$$_" : (), $self->user_defined_methods_keys ); } sub protected_members_decl_form { my $self = shift; return $self->nonpublic_members_decl_form( grep $self->protected( $_->name ), $self->members_values ); } sub protected_members_info_form { my $self = shift; my @protected_members = grep $self->protected( $_->name ), $self->members_values; my @protected_methods = grep $self->protected( $_->name ), $self->user_defined_methods_values; return '' if !( @protected_members || @protected_methods ); my $info_index_lvalue = $self->instance_var . '->' . $self->protected_members_info_index; my @protected_element_names = ( map( $_->accessor_names( $class, $_->name ), @protected_members ), map( $_->name, @protected_methods ) ); if ( $self->parents ) { my $form = ''; for my $element_name (@protected_element_names) { $form .= " ${info_index_lvalue}->{'$element_name'} = \$$element_name;\n"; } return $form; } else { return " $info_index_lvalue = { " . join( ', ', map "$_ => \$$_", @protected_element_names ) . " };\n"; } } sub copy_form { my $self = shift; my ( $form, @members, $has_parents ); @members = $self->members_values; $has_parents = defined $self->parents; $form = "sub copy {\n" . " my \$self = shift;\n" . " my \$copy;\n"; if ( !( do { my $has_complex_mems; for my $m (@members) { if ( $m->isa('Class::Generate::List_Member') || defined $m->base ) { $has_complex_mems = 1; last; } } $has_complex_mems; } || $has_parents ) ) { $form .= ' $copy = ' . $self->wholesale_copy . ";\n"; } else { $form .= ' $copy = ' . ( $has_parents ? '$self->SUPER::copy' : $self->empty_form ) . ";\n"; $form .= $self->size_establishment('$copy') if $self->can('size_establishment'); for my $m (@members) { my $index = $self->index( $m->name ); $form .= $m->copy_form( '$self->' . $index, '$copy->' . $index ); } } $form .= " bless \$copy, ref \$self;\n" . " return \$copy;\n" . "}\n"; return $form; } sub equals_form { my $self = shift; my ( $form, @parents, @members, $existence_test, @local_vars, @key_members ); @parents = $self->parents; @members = $self->members_values; if ( @key_members = grep $_->key, @members ) { @members = @key_members; } $existence_test = $self->existence_test; $form = "sub equals {\n" . " my \$self = shift;\n" . " my \$o = \$_[0];\n"; for my $m (@members) { if ( $m->isa('Class::Generate::Hash_Member'), @members ) { push @local_vars, qw($self_value_defined @self_keys); last; } } for my $m (@members) { if ( $m->isa('Class::Generate::Array_Member'), @members ) { push @local_vars, qw($ub); last; } } if (@local_vars) { $form .= ' my (' . join( ', ', @local_vars ) . ");\n"; } if (@parents) { $form .= " return undef unless \$self->SUPER::equals(\$o);\n"; } $form .= join( "\n", map $_->equals( $self->index( $_->name ), $existence_test ), @members ) . " return 1;\n" . "}\n"; return $form; } sub all_members_required { my $self = shift; for my $m ( $self->members_keys ) { return 0 if !( $self->private($m) || $self->required($m) ); } return 1; } sub private_member_names { my $self = shift; return grep $self->private($_), $self->members_keys; } sub protected_member_names { my $self = shift; return grep $self->protected($_), $self->members_keys; } sub public_member_names { my $self = shift; return grep !( $self->private($_) || $self->protected($_) ), $self->members_keys; } sub class_var { my $self = shift; return '$' . $self->{'class_var'}; } sub instance_var { my $self = shift; return '$' . $self->{'instance_var'}; } sub needs_constructor { my $self = shift; return ( defined $self->members || ( $self->virtual && $self->check_params ) || !$self->parents || do { my $c = $self->constructor; ( defined $c->post || defined $c->assert || $c->style->isa('Class::Generate::Own') ); } ); } package Class::Generate::Array_Class; # A subclass of Class defining $Class::Generate::Array_Class::VERSION = '1.18'; use strict; # array-based classes. use vars qw(@ISA); @ISA = qw(Class::Generate::Class); sub new { my $class = shift; my $name = shift; my %params = @_; my %super_params = %params; delete @super_params{qw(base_index member_index)}; my $self = $class->SUPER::new( $name, %super_params ); $self->{'base_index'} = defined $params{'base_index'} ? $params{'base_index'} : 1; $self->{'next_index'} = $self->base_index - 1; return $self; } sub base_index { my $self = shift; return $self->{'base_index'}; } sub base { my $self = shift; return '[]' if !$self->can_assign_all_params; my @sorted_members = sort { $$self{member_index}{$a} <=> $$self{member_index}{$b} } $self->members_keys; my %param_indices = map( ( $_, $self->constructor->style->order($_) ), $self->members_keys ); for ( my $i = 0 ; $i <= $#sorted_members ; $i++ ) { next if $param_indices{ $sorted_members[$i] } == $i; return '[ undef, ' . join( ', ', map { '$_[' . $param_indices{$_} . ']' } @sorted_members ) . ' ]'; } return '[ undef, @_ ]'; } sub base_type { return 'ARRAY'; } sub members { my $self = shift; return $self->SUPER::members(@_) if $#_ != 1; $self->SUPER::members(@_); my $overridden_class; if ( defined( $overridden_class = Class::Generate::Support::class_containing_method( $_[0], $self ) ) ) { $self->{'member_index'}{ $_[0] } = $overridden_class->{'member_index'}->{ $_[0] }; } else { $self->{'member_index'}{ $_[0] } = ++$self->{'next_index'}; } } sub index { my $self = shift; return '[' . $self->{'member_index'}{ $_[0] } . ']'; } sub last { my $self = shift; return $self->{'next_index'}; } sub existence_test { my $self = shift; return 'defined'; } sub size_establishment { my $self = shift; my $instance_var = $_[0]; return ' $#' . $instance_var . ' = ' . $self->last . ";\n"; } sub can_assign_all_params { my $self = shift; return !$self->check_params && $self->all_members_required && $self->constructor->style->isa('Class::Generate::Positional') && !defined $self->parents; } sub undef_form { return 'undef'; } sub wholesale_copy { return '[ @$self ]'; } sub empty_form { return '[]'; } sub protected_members_info_index { return q|[0]|; } package Class::Generate::Hash_Class; # A subclass of Class defining $Class::Generate::Hash_Class::VERSION = '1.18'; use vars qw(@ISA); # hash-based classes. @ISA = qw(Class::Generate::Class); sub index { my $self = shift; return "{'" . ( $self->private( $_[0] ) ? '*' . $self->name . '_' . $_[0] : $_[0] ) . "'}"; } sub base { my $self = shift; return '{}' if !$self->can_assign_all_params; my $style = $self->constructor->style; return '{ @_ }' if $style->isa('Class::Generate::Key_Value'); my %order = $style->order; my $form = '{ ' . join( ', ', map( "$_ => \$_[$order{$_}]", keys %order ) ); if ( $style->isa('Class::Generate::Mix') ) { $form .= ', @_[' . $style->pcount . '..$#_]'; } return $form . ' }'; } sub base_type { return 'HASH'; } sub existence_test { return 'exists'; } sub can_assign_all_params { my $self = shift; return !$self->check_params && $self->all_members_required && !$self->constructor->style->isa('Class::Generate::Own') && !defined $self->parents; } sub undef_form { return 'delete'; } sub wholesale_copy { return '{ %$self }'; } sub empty_form { return '{}'; } sub protected_members_info_index { return q|{'*protected*'}|; } package Class::Generate::Param_Style; # A virtual class encompassing $Class::Generate::Param_Style::VERSION = '1.18'; use strict; # parameter-passing styles for sub new { my $class = shift; return bless {}, $class; } sub keyed_param_names { return (); } sub delete_self_members_form { shift; my @self_members = @_; if ( $#self_members == 0 ) { return q|delete $super_params{'| . $self_members[0] . q|'};|; } elsif ( $#self_members > 0 ) { return q|delete @super_params{qw(| . join( ' ', @self_members ) . q|)};|; } } sub odd_params_check_form { my $self = shift; my ( $class, $constructor ) = @_; return q| croak '| . $constructor->name_form($class) . q|Odd number of parameters' if | . $self->odd_params_test($class) . ";\n"; } sub my_decl_form { my $self = shift; my $class = $_[0]; return ' my ' . $class->instance_var . ' = ' . $class->class_var . '->SUPER::new'; } package Class::Generate::Key_Value; # The key/value parameter- $Class::Generate::Key_Value::VERSION = '1.18'; use strict; # passing style. It adds use vars qw(@ISA); # the name of the variable @ISA = qw(Class::Generate::Param_Style); # that holds the parameters. sub new { my $class = shift; my $self = $class->SUPER::new; $self->{'holder'} = $_[0]; $self->{'keyed_param_names'} = [ @_[ 1 .. $#_ ] ]; return $self; } sub holder { my $self = shift; return $self->{'holder'}; } sub ref { my $self = shift; return '$' . $self->holder . "{'" . $_[0] . "'}"; } sub keyed_param_names { my $self = shift; return @{ $self->{'keyed_param_names'} }; } sub existence_test { return 'exists'; } sub init_form { my $self = shift; my ( $class, $constructor ) = @_; my ( $form, $cn ); $form = ''; $form .= $self->odd_params_check_form( $class, $constructor ) if $class->check_params; $form .= " my \%params = \@_;\n"; return $form; } sub odd_params_test { return '$#_%2 == 0'; } sub self_from_super_form { my $self = shift; my $class = $_[0]; return ' my %super_params = %params;' . "\n" . ' ' . $self->delete_self_members_form( $class->public_member_names ) . "\n" . $self->my_decl_form($class) . "(\%super_params);\n"; } sub params_check_form { my $self = shift; my ( $class, $constructor ) = @_; my ( $cn, @valid_names, $form ); @valid_names = $self->keyed_param_names; $cn = $constructor->name_form($class); if ( !@valid_names ) { $form = " croak '$cn', join(', ', keys %params), ': Not a member' if keys \%params;\n"; } else { $form = " {\n"; if ( $#valid_names == 0 ) { $form .= "\tmy \@unknown_params = grep \$_ ne '$valid_names[0]', keys \%params;\n"; } else { $form .= "\tmy %valid_param = (" . join( ', ', map( "'$_' => 1", @valid_names ) ) . ");\n" . "\tmy \@unknown_params = grep ! defined \$valid_param{\$_}, keys \%params;\n"; } $form .= "\tcroak '$cn', join(', ', \@unknown_params), ': Not a member' if \@unknown_params;\n" . " }\n"; } return $form; } package Class::Generate::Positional; # The positional parameter- $Class::Generate::Positional::VERSION = '1.18'; use strict; # passing style. It adds use vars qw(@ISA); # an ordering of parameters. @ISA = qw(Class::Generate::Param_Style); sub new { my $class = shift; my $self = $class->SUPER::new; for ( my $i = 0 ; $i <= $#_ ; $i++ ) { $self->{'order'}->{ $_[$i] } = $i; } return $self; } sub order { my $self = shift; return exists $self->{'order'} ? %{ $self->{'order'} } : () if $#_ == -1; return exists $self->{'order'} ? $self->{'order'}->{ $_[0] } : undef if $#_ == 0; $self->{'order'}->{ $_[0] } = $_[1]; } sub ref { my $self = shift; return '$_[' . $self->{'order'}->{ $_[0] } . ']'; } sub existence_test { return 'defined'; } sub self_from_super_form { my $self = shift; my $class = $_[0]; my $lb = scalar( $class->public_member_names ) || 0; return ' my @super_params = @_[' . $lb . '..$#_];' . "\n" . $self->my_decl_form($class) . "(\@super_params);\n"; } sub params_check_form { my $self = shift; my ( $class, $constructor ) = @_; my $cn = $constructor->name_form($class); my $max_params = scalar( $class->public_member_names ) || 0; return qq| croak '$cn| . qq|Only $max_params parameter(s) allowed (', \$#_+1, ' given)'| . " unless \$#_ < $max_params;\n"; } package Class::Generate::Mix; # The mix parameter-passing $Class::Generate::Mix::VERSION = '1.18'; use strict; # style. It combines key/value use vars qw(@ISA); # and positional. @ISA = qw(Class::Generate::Param_Style); sub new { my $class = shift; my $self = $class->SUPER::new; $self->{'pp'} = Class::Generate::Positional->new( @{ $_[1] } ); $self->{'kv'} = Class::Generate::Key_Value->new( $_[0], @_[ 2 .. $#_ ] ); $self->{'pnames'} = { map( ( $_ => 1 ), @{ $_[1] } ) }; return $self; } sub keyed_param_names { my $self = shift; return $self->{'kv'}->keyed_param_names; } sub order { my $self = shift; return $self->{'pp'}->order(@_) if $#_ <= 0; $self->{'pp'}->order(@_); $self->{'pnames'}{ $_[0] } = 1; } sub ref { my $self = shift; return $self->{'pnames'}->{ $_[0] } ? $self->{'pp'}->ref( $_[0] ) : $self->{'kv'}->ref( $_[0] ); } sub existence_test { my $self = shift; return $self->{'pnames'}->{ $_[0] } ? $self->{'pp'}->existence_test : $self->{'kv'}->existence_test; } sub pcount { my $self = shift; return exists $self->{'pnames'} ? scalar( keys %{ $self->{'pnames'} } ) : 0; } sub init_form { my $self = shift; my ( $class, $constructor ) = @_; my ( $form, $m ) = ( '', $self->max_possible_params($class) ); $form .= $self->odd_params_check_form( $class, $constructor, $self->pcount, $m ) if $class->check_params; $form .= ' my %params = ' . $self->kv_params_form($m) . ";\n"; return $form; } sub odd_params_test { my $self = shift; my $class = $_[0]; my ( $p, $test ); $p = $self->pcount; $test = '$#_>=' . $p; $test .= ' && $#_<=' . $self->max_possible_params($class) if $class->parents; $test .= ' && $#_%2 == ' . ( $p % 2 == 0 ? '0' : '1' ); return $test; } sub self_from_super_form { my $self = shift; my $class = $_[0]; my @positional_members = keys %{ $self->{'pnames'} }; my %self_members = map { ( $_ => 1 ) } $class->public_member_names; delete @self_members{@positional_members}; my $m = $self->max_possible_params($class); return $self->my_decl_form($class) . '(@_[' . ( $m + 1 ) . '..$#_]);' . "\n"; } sub max_possible_params { my $self = shift; my $class = $_[0]; my $p = $self->pcount; return $p + 2 * ( scalar( $class->public_member_names ) - $p ) - 1; } sub params_check_form { my $self = shift; my ( $class, $constructor ) = @_; my ( $form, $cn ); $cn = $constructor->name_form($class); $form = $self->{'kv'}->params_check_form(@_); my $max_params = $self->max_possible_params($class) + 1; $form .= qq| croak '$cn| . qq|Only $max_params parameter(s) allowed (', \$#_+1, ' given)'| . " unless \$#_ < $max_params;\n"; return $form; } sub kv_params_form { my $self = shift; my $max_params = $_[0]; return '@_[' . $self->pcount . "..(\$#_ < $max_params ? \$#_ : $max_params)]"; } package Class::Generate::Own; # The "own" parameter-passing $Class::Generate::Own::VERSION = '1.18'; use strict; # style. use vars qw(@ISA); @ISA = qw(Class::Generate::Param_Style); sub new { my $class = shift; my $self = $class->SUPER::new; $self->{'super_values'} = $_[0] if defined $_[0]; return $self; } sub super_values { my $self = shift; return defined $self->{'super_values'} ? @{ $self->{'super_values'} } : (); } sub can_assign_all_params { return 0; } sub self_from_super_form { my $self = shift; my $class = $_[0]; my ( $form, @sv ); $form = $self->my_decl_form($class); if ( @sv = $self->super_values ) { $form .= '(' . join( ',', @sv ) . ')'; } $form .= ";\n"; return $form; } 1; =pod =encoding UTF-8 =head1 NAME Class::Generate - Generate Perl class hierarchies =head1 VERSION version 1.18 =head1 SYNOPSIS use Class::Generate qw(class subclass delete_class); # Declare class Class_Name, with the following types of members: class Class_Name => [ s => '$', # scalar a => '@', # array h => '%', # hash c => 'Class', # Class c_a => '@Class', # array of Class c_h => '%Class', # hash of Class '&m' => 'body', # method ]; # Allocate an instance of class_name, with members initialized to the # given values (pass arrays and hashes using references). $obj = Class_Name->new ( s => scalar, a => [ values ], h => { key1 => v1, ... }, c => Class->new, c_a => [ Class->new, ... ], c_h => [ key1 => Class->new, ... ] ); # Scalar type accessor: $obj->s($value); # Assign $value to member s. $member_value = $obj->s; # Access member's value. # (Class) Array type accessor: $obj->a([value1, value2, ...]); # Assign whole array to member. $obj->a(2, $value); # Assign $value to array member 2. $obj->add_a($value); # Append $value to end of array. @a = $obj->a; # Access whole array. $ary_member_value = $obj->a(2); # Access array member 2. $s = $obj->a_size; # Return size of array. $value = $obj->last_a; # Return last element of array. # (Class) Hash type accessor: $obj->h({ k_1=>v1, ..., k_n=>v_n }) # Assign whole hash to member. $obj->h($key, $value); # Assign $value to hash member $key. %hash = $obj->h; # Access whole hash. $hash_member_value = $obj->h($key); # Access hash member value $key. $obj->delete_h($key); # Delete slot occupied by $key. @keys = $obj->h_keys; # Access keys of member h. @values = $obj->h_values; # Access values of member h. $another = $obj->copy; # Copy an object. if ( $obj->equals($another) ) { ... } # Test equality. subclass s => [ ], -parent => 'class_name'; =head1 DESCRIPTION The C package exports functions that take as arguments a class specification and create from these specifications a Perl 5 class. The specification language allows many object-oriented constructs: typed members, inheritance, private members, required members, default values, object methods, class methods, class variables, and more. CPAN contains similar packages. Why another? Because object-oriented programming, especially in a dynamic language like Perl, is a complicated endeavor. I wanted a package that would work very hard to catch the errors you (well, I anyway) commonly make. I wanted a package that could help me enforce the contract of object-oriented programming. I also wanted it to get out of my way when I asked. =head1 THE CLASS FUNCTION You create classes by invoking the C function. The C function has two forms: class Class_Name => [ specification ]; # Objects are array-based. class Class_Name => { specification }; # Objects are hash-based. The result is a Perl 5 class, in a package C. This package must not exist when C is invoked. An array-based object is faster and smaller. A hash-based object is more flexible. Subsequent sections explain where and why flexibility matters. The specification consists of zero or more name/value pairs. Each pair declares one member of the class, with the given name, and with attributes specified by the given value. =head1 MEMBER TYPES In the simplest name/value form, the value you give is a string that defines the member's type. A C<'$'> denotes a scalar member type. A C<'@'> denotes an array type. A C<'%'> denotes a hash type. Thus: class Person => [ name => '$', age => '$' ]; creates a class named C with two scalar members, C and C. If the type is followed by an identifier, the identifier is assumed to be a class name, and the member is restricted to a blessed reference of the class (or one of its subclasses), an array whose elements are blessed references of the class, or a hash whose keys are strings and whose values are blessed references of the class. For scalars, the C<$> may be omitted; i.e., C and C<$Class_Name> are equivalent. The class need not be declared using the C package. =head1 CREATING INSTANCES Each class that you generate has a constructor named C. Invoking the constructor creates an instance of the class. You may provide C with parameters to set the values of members: class Person => [ name => '$', age => '$' ]; $p = Person->new; # Neither name nor age is defined. $q = Person->new( name => 'Jim' ); # Only name is defined. $r = Person->new( age => 32 ); # Only age is defined. =head1 ACCESSOR METHODS A class has a standard set of accessor methods for each member you specify. The accessor methods depend on a member's type. =head2 Scalar (name => '$', name => 'Class_Name', or name => '$Class_Name') The member is a scalar. The member has a single method C. If called with no arguments, it returns the member's current value. If called with arguments, it sets the member to the first value: $p = Person->new; $p->age(32); # Sets age member to 32. print $p->age; # Prints 32. If the C form is used, the member must be a reference blessed to the named class or to one of its subclasses. The method will C (see L) if the argument is not a blessed reference to an instance of C or one of its subclasses. class Person => [ name => '$', spouse => 'Person' # Works, even though Person ]; # isn't yet defined. $p = Person->new(name => 'Simon Bar-Sinister'); $q = Person->new(name => 'Polly Purebred'); $r = Person->new(name => 'Underdog'); $r->spouse($q); # Underdog marries Polly. print $r->spouse->name; # Prints 'Polly Purebred'. print "He's married" if defined $p->spouse; # Prints nothing. $p->spouse('Natasha Fatale'); # Croaks. =head2 Array (name => '@' or name => '@Class') The member is an array. If the C<@Class> form is used, all members of the array must be a blessed reference to C or one of its subclasses. An array member has four associated methods: =over 4 =item C With no argument, C returns the member's whole array. With one argument, C's behavior depends on whether the argument is an array reference. If it is not, then the argument must be an integer I, and C returns element I of the member. If no such element exists, C returns C. If the argument is an array reference, it is cast into an array and assigned to the member. With two arguments, the first argument must be an integer I. The second argument is assigned to element I of the member. =item C This method appends its arguments to the member's array. =item C This method returns the index of the last element in the array. =item C This method returns the last element of C, or C if C has no elements. It's a shorthand for C<$o-Earray_mem($o-Earray_mem_size)>. =back For example: class Person => [ name => '$', kids => '@Person' ]; $p = Person->new; $p->add_kids(Person->new(name => 'Heckle'), Person->new(name => 'Jeckle')); print $p->kids_size; # Prints 1. $p->kids([Person->new(name => 'Bugs Bunny'), Person->new(name => 'Daffy Duck')]); $p->add_kids(Person->new(name => 'Yosemite Sam'), Person->new(name => 'Porky Pig')); print $p->kids_size; # Prints 3. $p->kids(2, Person->new(name => 'Elmer Fudd')); print $p->kids(2)->name; # Prints 'Elmer Fudd'. @kids = $p->kids; # Get all the kids. print $p->kids($p->kids_size)->name; # Prints 'Porky Pig'. print $p->last_kids->name; # So does this. =head2 Hash (name => '%' or name => '%Class') The member is a hash. If the C<%Class> form is used, all values in the hash must be a blessed reference to C or one of its subclasses. A hash member has four associated methods: =over 4 =item C With no arguments, C returns the member's whole hash. With one argument that is a hash reference, the member's value becomes the key/value pairs in that reference. With one argument that is a string, the element of the hash keyed by that string is returned. If no such element exists, C returns C. With two arguments, the second argument is assigned to the hash, keyed by the string representation of the first argument. =item C The C method returns all keys associated with the member. =item C The C method returns all values associated with the member. =item C The C method takes one or more arguments. It deletes from C's hash all elements matching the arguments. =back For example: class Person => [ name => '$', kids => '%Kid_Info' ]; class Kid_Info => [ grade => '$', skills => '@' ]; $f = new Person( name => 'Fred Flintstone', kids => { Pebbles => new Kid_Info(grade => 1, skills => ['Programs VCR']) } ); print $f->kids('Pebbles')->grade; # Prints 1. $b = new Kid_Info; $b->grade('Kindergarten'); $b->skills(['Knows Perl', 'Phreaks']); $f->kids('BamBam', $b); print join ', ', $f->kids_keys; # Prints "Pebbles, BamBam", # though maybe not in that order. =head1 COMMON METHODS All members also have a method C. This method undefines a member C. =head1 OBJECT INSTANCE METHODS C also generates methods that you can invoke on an object instance. These are as follows: =head2 Copy Use the C method to copy the value of an object. The expression: $p = $o->copy; assigns to C<$p> a copy of C<$o>. Members of C<$o> that are classes (or arrays or hashes of classes) are copied using their own C method. =head2 Equals Use the C method to test the equality of two object instances: if ( $o1->equals($o2) ) { ... } The two object instances are equal if members that have values in C<$o1> have equal values in C<$o2>, and vice versa. Equality is tested as you would expect: two scalar members are equal if they have the same value; two array members are equal if they have the same elements; two hash members are equal if they have the same key/value pairs. If a member's value is restricted to a class, then equality is tested using that class' C method. Otherwise, it is tested using the C operator. By default, all members participate in the equality test. If one or more members possess true values for the C attribute, then only those members participate in the equality test. You can override this definition of equality. See L. =head1 ADVANCED MEMBER SPECIFICATIONS As shown, you specify each member as a Cvalue> pair. If the C is a string, it specifies the member's type. The value may also be a hash reference. You use hash references to specify additional member attributes. The following is a complete list of the attributes you may specify for a member: =over 4 =item type=>string If you use a hash reference for a member's value, you I use the C attribute to specify its type: scalar_member => { type => '$' } =item required=>boolean If the C attribute is true, the member must be passed each time the class' constructor is invoked: class Person => [ name => { type => '$', required => 1 } ]; Person->new ( name => 'Wilma' ); # Valid Person->new; # Invalid Also, you may not call C for the member. =item default=>value The C attribute provides a default value for a member if none is passed to the constructor: class Person => [ name => '$', job => { type => '$', default => "'Perl programmer'" } ]; $p = Person->new(name => 'Larry'); print $p->job; # Prints 'Perl programmer'. $q = Person->new(name => 'Bjourne', job => 'C++ programmer'); print $q->job; # Unprintable. The value is treated as a string that is evaluated when the constructor is invoked. For array members, use a string that looks like a Perl expression that evaluates to an array reference: class Person => { name => '$', lucky_numbers => { type => '@', default => '[42, 17]' } }; class Silly => { UIDs => { # Default value is all UIDs type => '@', # currently in /etc/passwd. default => 'do { local $/ = undef; open PASSWD, "/etc/passwd"; [ map {(split(/:/))[2]} split /\n/, ] }' } }; Specify hash members analogously. The value is evaluated each time the constructor is invoked. In C, the default value for C can change between invocations. If the default value is a reference rather than a string, it is not re-evaluated. In the following, default values for C and C are based on the members of C<@default_value> each time Cnew> is invoked, whereas C's default value is set when the C function is invoked to define C: @default_value = (1, 2, 3); $var_name = '@' . __PACKAGE__ . '::default_value'; class Example => { e1 => { type => '@', default => "[$var_name]" }, e2 => { type => '@', default => \@default_value }, e3 => { type => '@', default => [ @default_value ] } }; Example->new; # e1, e2, and e3 are all identical. @default_value = (10, 20, 30); Example->new; # Now only e3 is (1, 2, 3). There are two more things to know about default values that are strings. First, if a member is typed, the C function evaluates its (string-based) default value to ensure that it is of the correct type for the member. Be aware of this if your default value has side effects (and see L). Second, the context of the default value is the C method of the package generated to implement your class. That's why C in C, above, needs the name of the current package in its default value. =item post=>code The value of this attribute is a string of Perl code. It is executed immediately after the member's value is modified through its accessor. Within C code, you can refer to members as if they were Perl identifiers. For instance: class Person => [ age => { type => '$', post => '$age *= 2;' } ]; $p = Person->new(age => 30); print $p->age; # Prints 30. $p->age(15); print $p->age; # Prints 30 again. The trailing semicolon used to be required, but everyone forgot it. As of version 1.06 it's optional: C<'$age*=2'> is accepted and equivalent to C<'$age*=2;'> (but see L<"BUGS">). You reference array and hash members as usual (except for testing for definition; see L<"BUGS">). You can reference individual elements, or the whole list: class Foo => [ m1 => { type => '@', post => '$m1[$#m1/2] = $m2{xxx};' }, m2 => { type => '%', post => '@m1 = keys %m2;' } ]; You can also invoke accessors. Prefix them with a C<&>: class Bar => [ m1 => { type => '@', post => '&undef_m1;' }, m2 => { type => '%', post => '@m1 = &m2_keys;' } ]; $o = new Bar; $o->m1([1, 2, 3]); # m1 is still undefined. $o->m2({a => 1, b => 2}); # Now m1 is qw(a b). =item pre=>code The C
 key is similar to the C key,
but it is executed just before an member is changed.
It is I executed if the member is only accessed.
The C
 and C code have the same scope,
which lets you share variables.
For instance:

    class Foo => [
	mem => { type => '$', pre => 'my $v = $mem;', post => 'return $v;' }
    ];
    $o = new Foo;
    $p = $o->mem(1);	# Sets $p to undef.
    $q = $o->mem(2);	# Sets $q to 1.

is a way to return the previous value of C any time it's modified
(but see L<"NOTES">).

=item assert=>expression

The value of this key should be a Perl expression
that evaluates to true or false.
Use member names in the expression, as with C.
The expression will be tested any time
the member is modified through its accessors.
Your code will C if the expression evaluates to false.
For instance,

    class Person => [
	name => '$',
	age => { type => '$',
		 assert => '$age =~ /^\d+$/ && $age < 200' } ];

ensures the age is reasonable.

The assertion is executed after any C code associated with the member.

=item private=>boolean

If the C attribute is true,
the member cannot be accessed outside the class;
that is, it has no accessor functions that can be called
outside the scope of the package defined by C.
A private member can, however, be accessed in C, C
, and C
code of other members of the class.

=item protected=>boolean

If the C attribute is true,
the member cannot be accessed outside the class or any of its subclasses.
A protected member can, however, be accessed in C, C
, and C
code of other members of the class or its subclasses.

=item readonly=>boolean

If this attribute is true, then the member cannot be modified
through its accessors.
Users can set the member only by using the class constructor.
The member's accessor that is its name can retrieve but not set the member.
The CI accessor is not defined for the member,
nor are other accessors that might modify the member.
(Code in C can set it, however.)

=item key=>boolean

If this attribute is true, then the member participates in equality tests.
See L<"Equals">.

=item nocopy=>value

The C attribute gives you some per-member control
over how the C method.
If C is false (the default),
the original's value is copied as described in L<"Copy">.
If C is true,
the original's value is assigned rather than copied;
in other words, the copy and the original will have the same value
if the original's value is a reference.

=back

=head1 AFFECTING THE CONSTRUCTOR

You may include a C attribute in the specification to affect the constructor.
Its value must be a hash reference.
Its attributes are:

=over 4

=item required=>list of constraints

This is another (and more general) way to require that
parameters be passed to the constructor.
Its value is a reference to an array of constraints.
Each constraint is a string that must be an expression
composed of Perl logical operators and member names.
For example:

    class Person => {
	name   => '$',
        age    => '$',
	height => '$',
	weight => '$',
	new => { required => ['name', 'height^weight'] }
    };

requires member C, and exactly one of C or C.
Note that the names are I prefixed with C<$>, C<@>, or C<%>.

Specifying a list of constraints as an array reference can be clunky.
The C function also lets you specify the list as a string,
with individual constraints separated by spaces.
The following two strings are equivalent to the above C attribute:

    'name height^weight'
    'name&(height^weight)'

However, C<'name & (height ^ weight)'> would not work.
The C function interprets it as a five-member list,
four members of which are not valid expressions.

This equivalence between a reference to array of strings
and a string of space-separated items is used throughout C.
Use whichever form works best for you.

=item post=>string of code

The C key is similar to the C key for members.
Its value is code that is inserted into the constructor
after parameter values have been assigned to members.
The C function performs variable substitution.

The C
 key is I recognized in C.

=item assert=>expression

The C key's value is inserted
just after the C key's value (if any).
Assertions for members are inserted after the constructor's assertion.

=item comment=>string

This attribute's value can be any string.
If you save the class to a file
(see L),
the string is included as a comment just before
the member's methods.

=item style=>style definition

The C