]> git.vpit.fr Git - perl/modules/LaTeX-TikZ.git/blobdiff - lib/LaTeX/TikZ/Functor/Rule.pm
Insert rules after all their subrules and before all their superrules
[perl/modules/LaTeX-TikZ.git] / lib / LaTeX / TikZ / Functor / Rule.pm
index efaff7b1cc73078b012ff9b7499ec87feacad17f..d4e070011a3b5e7413d81c6b3e67bd3d0ff792dd 100644 (file)
@@ -9,15 +9,15 @@ LaTeX::TikZ::Functor::Rule - An object that specifies how functors should handle
 
 =head1 VERSION
 
-Version 0.01
+Version 0.02
 
 =cut
 
-our $VERSION = '0.01';
+our $VERSION = '0.02';
 
 =head1 DESCRIPTION
 
-A rule specifies how functors should handle a certain kind of set or mod.
+A rule specifies how functors (L<LaTeX::TikZ::Functor> objects) should handle a certain kind of set or mod.
 A functor is basically an ordered collection of rules.
 
 =cut
@@ -32,6 +32,9 @@ use Any::Moose 'Util::TypeConstraints';
 
 =head2 C<target>
 
+A class or role name against which set or mod candidates will be matched.
+It must consume either L<LaTeX::TikZ::Set> or L<LaTeX::TikZ::Mod>, directly or through inheritance.
+
 =cut
 
 has 'target' => (
@@ -42,6 +45,9 @@ has 'target' => (
 
 =head2 C<handler>
 
+The code reference executed when the rule handles a given set or mod object.
+It is called with the L<LaTeX::TikZ::Functor> object as its first argument, the set/mod object as its second, and then the arguments passed to the functor itself.
+
 =cut
 
 has 'handler' => (
@@ -52,6 +58,8 @@ has 'handler' => (
 
 =head2 C<is_role>
 
+True if and only if the target is a role.
+
 =cut
 
 has 'is_role' => (
@@ -62,6 +70,8 @@ has 'is_role' => (
 
 =head2 C<is_set>
 
+True when the target does the L<LaTeX::TikZ::Set> role, and false when it does L<LaTeX::TikZ::Mod>.
+
 =cut
 
 has 'is_set' => (
@@ -77,6 +87,8 @@ my $ltfrl_tc = subtype 'LaTeX::TikZ::Functor::RuleList'
 
 =head2 C<< new target => $target, handler => $handler >>
 
+Constructs a new rule object with target C<$target> and handler C<$handler>.
+
 =cut
 
 around 'BUILDARGS' => sub {
@@ -111,12 +123,15 @@ around 'BUILDARGS' => sub {
 =head2 C<< insert into => \@list, overwrite => $overwrite, replace => $replace >>
 
 Inserts the current rule into the list of rules C<@list>.
+The list is expected to be ordered, in that each rule must come after all the rules that have a target that inherits or consumes the original rule's own target.
 
-If C<$replace> is false, then the rule will be appended to the C<@list> ; except if there already is an existent entry for the same target, in which case it will be overwritten if C<$overwrite> is true, or an exception will be thrown if it is false.
+If C<$replace> is false, then the rule will be inserted into C<@list> after all the rules applying to the target's subclasses/subroles and before all its superclasses/superroles ; except if there is already an existent entry for the same target, in which case it will be overwritten if C<$overwrite> is true, or an exception will be thrown if it is false.
 
 If C<$replace> is true, then the rule will replace the first rule in the list that is a subclass or that consumes the role denoted by the target.
 All the subsequent rules in the list that inherit or consume the target will be removed.
 
+Returns true if and only if an existent rule was replaced.
+
 =cut
 
 sub insert {
@@ -128,42 +143,62 @@ sub insert {
  my $overwrite = $args{overwrite};
  my $replace   = $args{replace};
 
- my $target  = $rule->target;
- my $is_role = $rule->is_role;
-
  if ($replace) {
-  my @remove;
+  my (@remove, $replaced);
 
   for my $i (0 .. $#$list) {
    my $old_target = $list->[$i]->target;
    if ($rule->handles($old_target)) {
-    if (defined $rule) {
-     splice @$list, $i, 1, $rule;
-     $rule = undef;
-    } else {
+    if ($replaced) {
      push @remove, $i;
+    } else {
+     splice @$list, $i, 1, $rule;
+     $replaced = 1;
     }
    }
   }
 
-  my $shift;
+  my $shift = 0;
   for (@remove) {
    splice @$list, $_ - $shift, 1;
    ++$shift;
   }
-  return 1 unless defined $rule;
+  return 1 if $replaced;
 
  } else { # Replace only an existent rule
+  my $target  = $rule->target;
+
+  my $last_descendant = undef;
+  my $first_ancestor  = undef;
 
   for my $i (0 .. $#$list) {
-   my $old_target = $list->[$i]->target;
+   my $old_rule   = $list->[$i];
+   my $old_target = $old_rule->target;
    if ($old_target eq $target) {
     Carp::confess("Default rule already defined for target $target")
                                                               unless $overwrite;
     splice @$list, $i, 1, $rule;
     return 1;
+   } elsif ($rule->handles($old_target)) {
+    $last_descendant = $i;
+   } elsif ($old_rule->handles($target)) {
+    $first_ancestor  = $i;
    }
   }
+
+  my $pos;
+  if (defined $first_ancestor) {
+   Carp::confess("Unsorted rule list")
+            if defined $last_descendant and $first_ancestor <= $last_descendant;
+   $pos = $first_ancestor;
+  } elsif (defined $last_descendant) {
+   $pos = $last_descendant + 1;
+  }
+
+  if (defined $pos) {
+   splice @$list, $pos, 1, $rule;
+   return 0;
+  }
  }
 
  push @$list, $rule;