File: Grammar.pm

package info (click to toggle)
libgraph-grammar-perl 0.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 104 kB
  • sloc: perl: 159; makefile: 2
file content (211 lines) | stat: -rw-r--r-- 7,697 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package Graph::Grammar;

# ABSTRACT: Grammar for graphs
our $VERSION = '0.2.0'; # VERSION

=head1 NAME

Graph::Grammar - Graph grammar, i.e. rewriting method

=head1 SYNOPSIS

    use Graph::Grammar;
    use Graph::Undirected;

    my $graph = Graph::Undirected->new;

    # Create graph here

    my @rules = (
        [ sub { 1 }, ( sub { 1 } ) x 2, NO_MORE_VERTICES, sub { [ @_[1..3] ] } ],
    );

    parse_graph( $graph, @rules );

=head1 DESCRIPTION

Graph::Grammar is a Perl implementation of a graph rewriting method (a.k.a. graph grammar).
Much of the API draws inspiration from L<Parse::Yapp>, but instead of acting on text streams Graph::Grammar is oriented at graphs, as implemented in Perl's L<Graph> module.
Graph::Grammar implements a single method C<parse_graph()> which accepts an instance of L<Graph> and an array of rules.
Every rule is evaluated for each vertex in a graph and, if a match is found, an action associated with the rule is executed.
A rule generally looks like this:

    [ $vertex_condition, @neighbour_conditions, $action ]

Where:

C<$vertex_condition> is a subroutine reference evaluating the center vertex.
The subroutine is called with the graph in C<$_[0]> and the vertex in <$_[1]>.
Subroutine should evaluate to true if condition is fulfilled.

C<@neighbour_conditions> is an array of subroutine references for the neighbours of the center vertex.
Inputs and outputs of each subroutine reference are the same as defined for C<$vertex_condition>.
Every condition has to match at least one of the neighbours (without overlaps).
Thus the rule will automatically fail if the number of neighbours is less than C<@neighbour_conditions>.
There can be more neighbours than C<@neighbour_conditions>, but if strict number of neighbours is needed, look below for C<NO_MORE_VERTICES>.
C<@neighbour_conditions> can be empty.

C<$action> can be either a subroutine reference, or anything else.
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.
That is, C<$_[1]> is the center vertex, C<$_[2]> is a vertex matching the first neighbour condition and so on.
If C<$action> is not a subroutine reference, then it is cloned by L<Clone> and inserted instead of the center vertex.

There are two ways to request a particular number of neighbours for the central vertex.
First of them is to include an appropriate requirement into C<$vertex_condition>.
Second is to put C<NO_MORE_VERTICES> as the last element of C<@neighbour_conditions>, i.e.:

    [ sub { 1 }, ( sub { 1 } ) x 2, NO_MORE_VERTICES, sub { [ @_[1..3] ] } ]

Edge conditions are also supported and they always act on the center vertex and its neighbours matching their individual conditions, i.e.:

    [ $vertex_condition,
        EDGE { $edge_condition1->( @_ ) }, $vertex_condition1,
        EDGE { $edge_condition2->( @_ ) }, $vertex_condition2,
        # ...
        $action ]

=cut

use strict;
use warnings;

use parent Exporter::;
our @EXPORT = qw( EDGE NO_MORE_VERTICES parse_graph );

use Clone qw( clone );
use Graph::Grammar::Rule::Edge;
use Graph::Grammar::Rule::NoMoreVertices;
use Graph::MoreUtils qw( graph_replace );
use List::Util qw( first );
use Scalar::Util qw( blessed );
use Set::Object qw( set );

our $DEBUG = 0;

=head1 METHODS

=head2 C<parse_graph( $graph, @rules )>

Perform graph rewriting of C<$graph>.
Modifies the supplied graph and returns it upon completion.

=cut

sub parse_graph
{
    my( $graph, @rules ) = @_;

    my $changes = 1;

    MAIN:
    while( $changes ) {
        $changes = 0;

        for my $i (0..$#rules) {
            my $rule = $rules[$i];
            my @rule = @$rule;
            my $rule_name;
            my $self_rule = shift @rule;

            # First element in the rule could be a rule name
            if( !ref $self_rule ) {
                $rule_name = $self_rule;
                $self_rule = shift @rule;
            }

            my $action = pop @rule;
            my $no_more_vertices;
            if( @rule && blessed $rule[-1] && $rule[-1]->isa( Graph::Grammar::Rule::NoMoreVertices:: ) ) {
                $no_more_vertices = 1;
                pop @rule;
            }

            my $neighbours = grep { ref $_ eq 'CODE' } @rule;

            my $affected_vertices = set();

            VERTEX:
            for my $vertex ($graph->vertices) {
                next unless $self_rule->( $graph, $vertex );
                next unless defined $graph->degree( $vertex );
                next if $graph->degree( $vertex ) < $neighbours;
                next if $no_more_vertices && $graph->degree( $vertex ) > $neighbours;

                my @matching_neighbours;
                my $matching_neighbours = set();
                for my $i (0..$#rule) {
                    my $neighbour_rule = $rule[$i];
                    next if blessed $neighbour_rule && $neighbour_rule->isa( Graph::Grammar::Rule::Edge:: ); # Edge rules are evaluated separately

                    my $match;
                    if( $i && blessed $rule[$i-1] && $rule[$i-1]->isa( Graph::Grammar::Rule::Edge:: ) ) {
                        # With edge condition
                        $match = first { !$matching_neighbours->has( $_ ) &&
                                         $neighbour_rule->( $graph, $_ ) &&
                                         $rule[$i-1]->matches( $graph, $vertex, $_ ) }
                                       $graph->neighbours( $vertex );
                    } else {
                        # Without edge condition
                        $match = first { !$matching_neighbours->has( $_ ) &&
                                         $neighbour_rule->( $graph, $_ ) }
                                       $graph->neighbours( $vertex );
                    }
                    next VERTEX unless $match;

                    push @matching_neighbours, $match;
                    $matching_neighbours->insert( $match );
                }

                if( $DEBUG ) {
                    print STDERR defined $rule_name ? "apply rule $i: $rule_name\n" : "apply rule $i\n";
                }

                my $overlaps = ($affected_vertices * $matching_neighbours)->size +
                                $affected_vertices->has( $vertex );
                if( $DEBUG && $overlaps ) {
                    print STDERR "$overlaps overlapping vertices\n";
                }
                $affected_vertices->insert( $vertex, @matching_neighbours );

                if( ref $action eq 'CODE' ) {
                    $action->( $graph, $vertex, @matching_neighbours );
                } else {
                    graph_replace( $graph, clone( $action ), $vertex );
                }
                $changes++;
            }
        }
    }

    return $graph;
}

=head2 C<EDGE>

When used before a neighbour condition, places a condition on edge connecting the center vertex with a neighbour matched by the following rule.
Accepts a block or sub {}, i.e.:

    EDGE { $_[0]->get_edge_attribute( $_[1], $_[2], 'color' ) eq 'red' }

Subroutine is evaluated with three parameters: graph, center vertex and its neighbour matching the following neighbour condition.
Subroutine should evaluate to true if condition is fulfilled.

=cut

sub EDGE(&) { Graph::Grammar::Rule::Edge->new( $_[0] ) }

=head2 C<NO_MORE_VERTICES>

When used before the rule action in a rule, restricts the number of center vertex neighbours to vertex conditions.

=cut

sub NO_MORE_VERTICES { Graph::Grammar::Rule::NoMoreVertices->new }

=head1 AUTHORS

Andrius Merkys, E<lt>merkys@cpan.orgE<gt>

=cut

1;