# Copyright (c) 1997-2006 # Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany) # http://www.math.tu-berlin.de/polymake, mailto:polymake@math.tu-berlin.de # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any # later version: http://www.gnu.org/licenses/gpl.txt. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. #----------------------------------------------------------------------------- # $Project: polymake $$Id: Struct.pm 7540 2006-12-21 21:04:18Z gawrilow $ use integer; use strict; use Poly; use namespaces 'Poly'; package Struct; use Poly::Ext; use Carp; $Carp::Internal{Struct}=1; sub import { shift; # drop the own package name my $cnt=0; my ($pkg, $file, $line)=caller(0); if (my $def=$pkg->can(".defined")) { my $other_pkg=method_owner($def); if ($pkg eq $other_pkg) { croak "package $pkg already declared as Struct at ".&$def.", conflicting declaration"; } else { croak "inheritance from package $other_pkg declared as Struct at ".&$def. "\n seems to be established via plain \@ISA assignment and conflicts with Struct declaration"; } } my $symtab=get_pkg($pkg); my $constructor=""; my $constructor_deferred=""; my $merger=""; my ($signature, $own_signature, $check_arg, $min_arg, $max_arg, $keyed_args, %keys, $keys_changed, $trailing_list, $super, $super_symtab, $redefine, $merge_seen); my $with_namespaces=namespaces::uses($pkg); my $prologue="use strict;\n" . (!$with_namespaces && "no namespaces;\n") . "package $pkg"; while (ref($_[0])) { if ($_[0]->[0] eq '@ISA') { my $isa=shift; shift @$isa; { no strict 'refs'; @{"$pkg\::ISA"}=@$isa; } foreach my $s (@$isa) { if (defined (my $super_constructor=UNIVERSAL::can($s, ".constructor"))) { $super=$s; ($cnt, $constructor, $constructor_deferred, $merger)=$super_constructor->(); if (!$own_signature && defined (my $super_signature=UNIVERSAL::can($super, ".signature"))) { ($signature, $min_arg, $max_arg, $trailing_list)=$super_signature->(); } if (my $k=UNIVERSAL::can($s, ".keys")) { $keyed_args=1; %keys=%{$k->()}; } $redefine=1; if ($with_namespaces && namespaces::uses($super)) { namespaces::using($pkg, $super); } $super_symtab=get_pkg($super); last; } } } elsif ($_[0]->[0] eq 'new') { $signature=(shift)->[1]; if ($signature !~ m'^ (\$*) (?: ;(\$+) | (%))? (\@)? $ 'x) { croak "invalid constructor signature: '$signature'"; } $own_signature=$signature; $min_arg=length($1); $check_arg= $min_arg>0 && "\@_ <= $min_arg"; $max_arg=$min_arg+length($2)+1; $trailing_list=$4 && $max_arg; if (defined $2) { if (!$4) { $check_arg .= ($min_arg>0 && " or ") ."\@_ > $max_arg"; } } elsif ($3) { $keyed_args=1; } } else { last; } } foreach my $field (@_) { my ($code, $name)=split //, (ref($field) ? $field->[0] : (undef($redefine), $field)), 2; if ($code !~ m'[$@%&]') { croak "unknown field type '$code'$name"; } my $accessor=UNIVERSAL::can($pkg,$name); if (defined($redefine)) { if (!defined($accessor) || ($redefine=get_field_index($accessor))<0) { undef $redefine; # new field or an occasionaly overwritten non-field method } } elsif (get_field_index($accessor)>=0) { croak "multiple definition of field $name"; } my @aliases=split /\s*\|\s*/, $name; my ($kname, $get_kname); if ($keyed_args) { if (@aliases>1) { $kname='$kname'; $get_kname=<<"_#_#_#_"; do { undef \$kname; exists \$kw{\$_} and (\$kname=\$_, last) for (qw(@aliases)) } _#_#_#_ } else { $kname="'$name'"; $get_kname="exists \$kw{$name}"; } } my ($key_in_expr, $set_filter, $set_filter_is_method, $init_deferred, $merge_expr); my $deflt=$code eq '@' ? "[]" : $code eq '%' ? "{}" : $code eq '&' ? "undef" : "''"; my $add_to_keys=$keyed_args; my $init= ref($field) ? $field->[1] eq '##' ? do { croak "unexpected options for the uninitialized field $name" if $#$field>1; undef $get_kname; $add_to_keys=-1; $deflt } : $field->[1] eq '@' ? do { if (defined($trailing_list)) { croak "unexpected options for the trailer-initialized field $name" if $#$field>1; undef $get_kname; $add_to_keys=-1; $keyed_args ? "\\\@trailing_list" : "[ splice \@_, $max_arg ]" } else { croak "no trailing list declared in the signature" } } : $code eq '&' && $field->[1] =~ /^->\s*(\w+)(?:\s*\|\|\s*(.+))?$/ ? do { croak "unexpected options for the method field $name" if $#$field>1; $add_to_keys=-1; if ((my $super_index=get_field_index(UNIVERSAL::can($pkg,"$1"))) >= 0) { if ($2) { $set_filter=eval "$prologue; $2"; croak "syntax error compiling method fallback for the field $name: $@" if $@; } unless (exists &{$symtab->{original_object}}) { define_function($symtab, "original_object", \&original_object); } $super_index; } else { croak "reference to an unknown field $1 in initializer of the field $name"; } } : do { croak "odd options list for the field $name" if !($#$field%2); my $expr=$field->[1]; my %options=splice @$field, 2; $init_deferred=$set_filter_is_method= $expr =~ /(?0) { @keys{@aliases}=(); $keys_changed=1; } elsif ($add_to_keys<0 && $redefine && $keyed_args) { delete @keys{@aliases}; $keys_changed=1; } $set_filter ||= $key_in_expr && do { my $expr=$field->[1]; if ($expr =~ /^\s* (?: \$this\s*->\s* )? ((?!\d)\w+) \(\s* \#% \s*\) \s*$/x) { $1 } else { my $mys= ($set_filter_is_method &&= " : method") && 'my $this=shift; '; $expr =~ s{(?: (?<=^) | (?<=[\s,(])) \#% (?= $ | [\s,)])}{\@_}x; eval "$prologue; sub$set_filter_is_method { $mys$expr }" or croak "syntax error compiling access filter for the field $name: $@"; } }; if (defined($redefine)) { use re 'eval'; if ($redefine) { my $prev=$redefine-1; $constructor =~ /\#\#\#<$prev>\n/s; pos($constructor)=$+[0]; } $constructor =~ s/\G .*? (?= ,\s*\#\#\#<$redefine>\n)/$init/xs; if ($init_deferred) { $constructor_deferred =~ s{(^ | \#\#\#<(\d+)>\n) (?(?{ $2!=$redefine }) .*? | (?!.)) (?= ;\s*\#\#\#<$redefine>\n)} {$1\$this->[$redefine]=$init_deferred}xs or $constructor_deferred .= "\$this->[$redefine]=$init_deferred; ###<$redefine>\n"; } elsif ($constructor_deferred) { $constructor_deferred =~ s/(^ | \#\#\#<(\d+)>\n) (?(?{ $2!=$redefine }) .*? | (?!.)) ;\s*\#\#\#<$redefine>\n/$1/xs; } if ($set_filter || defined(${$super_symtab->{$aliases[0]}})) { $accessor=create_accessor($redefine, $code eq '&'); define_function($symtab, $_, $accessor) for @aliases; ${$symtab->{$aliases[0]}}=$set_filter; } if ($merge_expr) { $merger =~ s/(^ | \#\#\#<(\d+)>\n) (?(?{ $2!=$redefine }) .*? | (?!.)) (?= ;\s*\#\#\#<$redefine>\n)/$1$merge_expr/xs or $merger .= "$merge_expr; ###<$redefine>\n"; } } else { $constructor .= "$init, ###<$cnt>\n"; if ($init_deferred) { $constructor_deferred .= "\$this->[$cnt]=$init_deferred; ###<$cnt>\n"; } $accessor=create_accessor($cnt, $code eq '&'); define_function($symtab, $_, $accessor) for @aliases; if ($set_filter) { ${$symtab->{$aliases[0]}}=$set_filter; } if ($merge_expr) { $merger .= "$merge_expr; ###<$cnt>\n"; } ++$cnt; } } if (@_ || $own_signature) { my $new_text= <<"_#_0_#_"; $prologue; sub { _#_0_#_ if ($keyed_args) { $new_text .= <<"_#_1_#_"; my (%kw,\$kname); _#_1_#_ if (defined($trailing_list)) { $new_text .= <<"_#_2_#_"; my \@trailing_list; for (my \$i=$max_arg; \$i<=\$#_; ) { if (Poly::is_hash(\$_[\$i])) { push %kw, %{\$_[\$i++]}; } elsif (!ref(\$_[\$i]) && exists \$keys{\$_[\$i]}) { \$kw{\$_[\$i]}=\$_[\$i+1]; \$i+=2; } else { \@trailing_list=splice \@_, \$i; last; } } _#_2_#_ } else { $new_text .= <<"_#_3_#_"; for (my \$i=$max_arg; \$i<=\$#_; ) { if (Poly::is_hash(\$_[\$i])) { push %kw, %{\$_[\$i++]}; } else { \$kw{\$_[\$i]}=\$_[\$i+1]; \$i+=2; } } _#_3_#_ } } if ($check_arg) { $new_text .= <<"_#_4_#_"; if ($check_arg) { Poly::croak( "usage: new \$_[0] (" . '$signature' . ")" ); } _#_4_#_ } if ($keyed_args) { $new_text .= <<"_#_5_#_"; my \$this=Poly::inherit_class( Struct::start_compile_constructor(), [ $constructor ], shift); $constructor_deferred if (keys %kw) { Poly::croak( ref(\$this), "::new - unknown keywords: ", join(", ", keys %kw) ); } \$this _#_5_#_ } else { $new_text .= <<"_#_8_#_"; Poly::inherit_class([ $constructor ], shift); _#_8_#_ } $new_text .= <<"_#_9_#_"; } _#_9_#_ my $_new=eval $new_text; if ($@) { my @lines=(undef, split /(?:\#\#\#<\d+>)?\n/, $new_text); # leading undef makes the line numbers = array index $@ =~ s/at \(eval \d+\) line (\d+)/near line $1: '$lines[$1]'/g; croak "syntax error in $pkg\::new: $@"; } define_function($symtab, "__new", $_new); if ($merge_seen) { my $merge_text=<<"_#_#_#_"; $prologue; sub { my (\$this, %kw)=\@_; my \$kname; $merger if (keys %kw) { Poly::croak( ref(\$this), "::merge - unknown or non-mergeable field(s): ", join(", ", keys %kw) ); } \$this } _#_#_#_ my $_merge=eval $merge_text; if ($@) { my @lines=(undef, split /(?:\#\#\#<\d+>)?\n/, $new_text); $@ =~ s/at \(eval \d+\) line (\d+)/near line $1: '$lines[$1]'/g; croak "syntax error in $pkg\::merge: $@"; } define_function($symtab, "merge", $_merge); } } elsif (!$super) { croak "no own fields and no Struct-based super class specified"; } define_function($symtab, ".defined", sub { "$file line $line" }); define_function($symtab, ".constructor", sub { ($cnt, $constructor, $constructor_deferred, $merger) }); define_function($symtab, "sizeof", sub { $cnt }); define_function($symtab, ".signature", sub { ($signature, $min_arg, $max_arg, $trailing_list) }) if $own_signature; if ($keys_changed) { define_function($symtab, ".keys", sub { \%keys }); } define_function($symtab, "_new", \&_new); if (!$super || UNIVERSAL::can($super, "new")==UNIVERSAL::can($super, "_new")) { define_function($symtab, "new", \&_new); } elsif ($with_namespaces) { # predeclare for the sake of cleaner syntax in the package's code no strict 'refs'; *{"$pkg\::new"}=UNIVERSAL::can($super, "new"); } } sub _new { &{UNIVERSAL::can($_[0],"__new")} } 1;