File Coverage

blib/lib/Graph/Grammar.pm
Criterion Covered Total %
statement 79 82 96.3
branch 20 30 66.6
condition 16 24 66.6
subroutine 15 15 100.0
pod 3 3 100.0
total 133 154 86.3


line stmt bran cond sub pod time code
1             package Graph::Grammar;
2              
3             # ABSTRACT: Grammar for graphs
4             our $VERSION = '0.2.0'; # VERSION
5              
6             =head1 NAME
7              
8             Graph::Grammar - Graph grammar, i.e. rewriting method
9              
10             =head1 SYNOPSIS
11              
12             use Graph::Grammar;
13             use Graph::Undirected;
14              
15             my $graph = Graph::Undirected->new;
16              
17             # Create graph here
18              
19             my @rules = (
20             [ sub { 1 }, ( sub { 1 } ) x 2, NO_MORE_VERTICES, sub { [ @_[1..3] ] } ],
21             );
22              
23             parse_graph( $graph, @rules );
24              
25             =head1 DESCRIPTION
26              
27             Graph::Grammar is a Perl implementation of a graph rewriting method (a.k.a. graph grammar).
28             Much of the API draws inspiration from L, but instead of acting on text streams Graph::Grammar is oriented at graphs, as implemented in Perl's L module.
29             Graph::Grammar implements a single method C which accepts an instance of L and an array of rules.
30             Every rule is evaluated for each vertex in a graph and, if a match is found, an action associated with the rule is executed.
31             A rule generally looks like this:
32              
33             [ $vertex_condition, @neighbour_conditions, $action ]
34              
35             Where:
36              
37             C<$vertex_condition> is a subroutine reference evaluating the center vertex.
38             The subroutine is called with the graph in C<$_[0]> and the vertex in <$_[1]>.
39             Subroutine should evaluate to true if condition is fulfilled.
40              
41             C<@neighbour_conditions> is an array of subroutine references for the neighbours of the center vertex.
42             Inputs and outputs of each subroutine reference are the same as defined for C<$vertex_condition>.
43             Every condition has to match at least one of the neighbours (without overlaps).
44             Thus the rule will automatically fail if the number of neighbours is less than C<@neighbour_conditions>.
45             There can be more neighbours than C<@neighbour_conditions>, but if strict number of neighbours is needed, look below for C.
46             C<@neighbour_conditions> can be empty.
47              
48             C<$action> can be either a subroutine reference, or anything else.
49             If C<$action> is a subroutine reference, then in the case of a match it is called with the graph in C<$_[0]> and remaining C<@_> members being graph vertices corresponding to rule conditions.
50             That is, C<$_[1]> is the center vertex, C<$_[2]> is a vertex matching the first neighbour condition and so on.
51             If C<$action> is not a subroutine reference, then it is cloned by L and inserted instead of the center vertex.
52              
53             There are two ways to request a particular number of neighbours for the central vertex.
54             First of them is to include an appropriate requirement into C<$vertex_condition>.
55             Second is to put C as the last element of C<@neighbour_conditions>, i.e.:
56              
57             [ sub { 1 }, ( sub { 1 } ) x 2, NO_MORE_VERTICES, sub { [ @_[1..3] ] } ]
58              
59             Edge conditions are also supported and they always act on the center vertex and its neighbours matching their individual conditions, i.e.:
60              
61             [ $vertex_condition,
62             EDGE { $edge_condition1->( @_ ) }, $vertex_condition1,
63             EDGE { $edge_condition2->( @_ ) }, $vertex_condition2,
64             # ...
65             $action ]
66              
67             =cut
68              
69 2     2   10942 use strict;
  2         4  
  2         83  
70 2     2   20 use warnings;
  2         3  
  2         102  
71              
72 2     2   944 use parent Exporter::;
  2         633  
  2         10  
73             our @EXPORT = qw( EDGE NO_MORE_VERTICES parse_graph );
74              
75 2     2   1332 use Clone qw( clone );
  2         1179  
  2         170  
76 2     2   1059 use Graph::Grammar::Rule::Edge;
  2         10  
  2         60  
77 2     2   1058 use Graph::Grammar::Rule::NoMoreVertices;
  2         6  
  2         93  
78 2     2   974 use Graph::MoreUtils qw( graph_replace );
  2         167920  
  2         186  
79 2     2   16 use List::Util qw( first );
  2         4  
  2         250  
80 2     2   20 use Scalar::Util qw( blessed );
  2         17  
  2         78  
81 2     2   8 use Set::Object qw( set );
  2         29  
  2         1764  
82              
83             our $DEBUG = 0;
84              
85             =head1 METHODS
86              
87             =head2 C
88              
89             Perform graph rewriting of C<$graph>.
90             Modifies the supplied graph and returns it upon completion.
91              
92             =cut
93              
94             sub parse_graph
95             {
96 5     5 1 22745 my( $graph, @rules ) = @_;
97              
98 5         12 my $changes = 1;
99              
100             MAIN:
101 5         20 while( $changes ) {
102 8         15 $changes = 0;
103              
104 8         30 for my $i (0..$#rules) {
105 8         19 my $rule = $rules[$i];
106 8         30 my @rule = @$rule;
107 8         14 my $rule_name;
108 8         19 my $self_rule = shift @rule;
109              
110             # First element in the rule could be a rule name
111 8 50       25 if( !ref $self_rule ) {
112 8         15 $rule_name = $self_rule;
113 8         14 $self_rule = shift @rule;
114             }
115              
116 8         17 my $action = pop @rule;
117 8         12 my $no_more_vertices;
118 8 100 100     69 if( @rule && blessed $rule[-1] && $rule[-1]->isa( Graph::Grammar::Rule::NoMoreVertices:: ) ) {
      66        
119 3         5 $no_more_vertices = 1;
120 3         4 pop @rule;
121             }
122              
123 8         21 my $neighbours = grep { ref $_ eq 'CODE' } @rule;
  7         25  
124              
125 8         30 my $affected_vertices = set();
126              
127             VERTEX:
128 8         178 for my $vertex ($graph->vertices) {
129 60 100       8792 next unless $self_rule->( $graph, $vertex );
130 42 50       2004 next unless defined $graph->degree( $vertex );
131 42 50       10798 next if $graph->degree( $vertex ) < $neighbours;
132 42 100 100     10402 next if $no_more_vertices && $graph->degree( $vertex ) > $neighbours;
133              
134 24         1625 my @matching_neighbours;
135 24         58 my $matching_neighbours = set();
136 24         466 for my $i (0..$#rule) {
137 30         47 my $neighbour_rule = $rule[$i];
138 30 100 66     115 next if blessed $neighbour_rule && $neighbour_rule->isa( Graph::Grammar::Rule::Edge:: ); # Edge rules are evaluated separately
139              
140 18         23 my $match;
141 18 100 66     90 if( $i && blessed $rule[$i-1] && $rule[$i-1]->isa( Graph::Grammar::Rule::Edge:: ) ) {
      66        
142             # With edge condition
143 14 50 33 14   1396 $match = first { !$matching_neighbours->has( $_ ) &&
144             $neighbour_rule->( $graph, $_ ) &&
145             $rule[$i-1]->matches( $graph, $vertex, $_ ) }
146 12         78 $graph->neighbours( $vertex );
147             } else {
148             # Without edge condition
149 6 50   6   721 $match = first { !$matching_neighbours->has( $_ ) &&
150             $neighbour_rule->( $graph, $_ ) }
151 6         51 $graph->neighbours( $vertex );
152             }
153 18 100       2380 next VERTEX unless $match;
154              
155 9         23 push @matching_neighbours, $match;
156 9         44 $matching_neighbours->insert( $match );
157             }
158              
159 15 50       39 if( $DEBUG ) {
160 0 0       0 print STDERR defined $rule_name ? "apply rule $i: $rule_name\n" : "apply rule $i\n";
161             }
162              
163 15         229 my $overlaps = ($affected_vertices * $matching_neighbours)->size +
164             $affected_vertices->has( $vertex );
165 15 50 33     1001 if( $DEBUG && $overlaps ) {
166 0         0 print STDERR "$overlaps overlapping vertices\n";
167             }
168 15         65 $affected_vertices->insert( $vertex, @matching_neighbours );
169              
170 15 50       46 if( ref $action eq 'CODE' ) {
171 15         54 $action->( $graph, $vertex, @matching_neighbours );
172             } else {
173 0         0 graph_replace( $graph, clone( $action ), $vertex );
174             }
175 15         3014 $changes++;
176             }
177             }
178             }
179              
180 5         913 return $graph;
181             }
182              
183             =head2 C
184              
185             When used before a neighbour condition, places a condition on edge connecting the center vertex with a neighbour matched by the following rule.
186             Accepts a block or sub {}, i.e.:
187              
188             EDGE { $_[0]->get_edge_attribute( $_[1], $_[2], 'color' ) eq 'red' }
189              
190             Subroutine is evaluated with three parameters: graph, center vertex and its neighbour matching the following neighbour condition.
191             Subroutine should evaluate to true if condition is fulfilled.
192              
193             =cut
194              
195 1     1 1 295767 sub EDGE(&) { Graph::Grammar::Rule::Edge->new( $_[0] ) }
196              
197             =head2 C
198              
199             When used before the rule action in a rule, restricts the number of center vertex neighbours to vertex conditions.
200              
201             =cut
202              
203 1     1 1 316777 sub NO_MORE_VERTICES { Graph::Grammar::Rule::NoMoreVertices->new }
204              
205             =head1 AUTHORS
206              
207             Andrius Merkys, Emerkys@cpan.orgE
208              
209             =cut
210              
211             1;