Graph-Grammar-0.2.0/0000775000200400020040000000000014674751676014045 5ustar andriusandriusGraph-Grammar-0.2.0/dist.ini0000644000200400020040000000075114674751676015512 0ustar andriusandriusname = Graph-Grammar author = Andrius Merkys license = BSD copyright_holder = Andrius Merkys copyright_year = 2023-2024 version = 0.2.0 [@Filter] -bundle = @Basic -remove = License [AutoMetaResources] homepage = https://search.cpan.org/dist/%{dist} repository.github = user:merkys bugtracker.github = user:merkys [MetaJSON] [OurPkgVersion] [Prereqs] Clone = 0 Graph = 0 Graph::MoreUtils = 0.2.0 List::Util = 0 Set::Object = 0 Graph-Grammar-0.2.0/LICENSE0000644000200400020040000000273314674751676015055 0ustar andriusandriusCopyright (c) The Regents of the University of California. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the University nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Graph-Grammar-0.2.0/Changes0000644000200400020040000000027114674751676015336 0ustar andriusandrius0.2.0 2024-09-25 - Implement support for edge conditions. 0.1.1 2024-04-02 - Add NAME section so MetaCPAN recognises module (GH#1). 0.1.0 2023-12-13 - Initial release. Graph-Grammar-0.2.0/README0000644000200400020040000000045214674751676014724 0ustar andriusandriusThis archive contains the distribution Graph-Grammar, version 0.2.0: Grammar for graphs This software is Copyright (c) 2023-2024 by Andrius Merkys. This is free software, licensed under: The (three-clause) BSD License This README file was generated by Dist::Zilla::Plugin::Readme v6.012. Graph-Grammar-0.2.0/MANIFEST0000644000200400020040000000044514674751676015177 0ustar andriusandrius# This file was automatically generated by Dist::Zilla::Plugin::Manifest v6.012. Changes LICENSE MANIFEST META.json META.yml Makefile.PL README dist.ini lib/Graph/Grammar.pm lib/Graph/Grammar/Rule/Edge.pm lib/Graph/Grammar/Rule/NoMoreVertices.pm t/01_delete_pendants.t t/02_delete_red_edges.t Graph-Grammar-0.2.0/META.yml0000644000200400020040000000135614674751676015321 0ustar andriusandrius--- abstract: 'Grammar for graphs' author: - 'Andrius Merkys ' build_requires: {} configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 0 generated_by: 'Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010' license: bsd meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Graph-Grammar requires: Clone: '0' Graph: '0' Graph::MoreUtils: v0.2.0 List::Util: '0' Set::Object: '0' resources: bugtracker: https://github.com/merkys/graph-grammar/issues homepage: https://search.cpan.org/dist/Graph-Grammar repository: git://github.com/merkys/graph-grammar.git version: 0.2.0 x_generated_by_perl: v5.30.0 x_serialization_backend: 'YAML::Tiny version 1.73' Graph-Grammar-0.2.0/t/0000775000200400020040000000000014674751676014310 5ustar andriusandriusGraph-Grammar-0.2.0/t/02_delete_red_edges.t0000644000200400020040000000120114674751676020231 0ustar andriusandrius#!/usr/bin/perl use strict; use warnings; use Graph::Grammar; use Graph::Undirected; use Test::More; my @rules = ( [ 'delete red nodes', sub { 1 }, EDGE { $_[0]->get_edge_attribute( $_[1], $_[2], 'color' ) eq 'red' }, sub { 1 }, sub { $_[0]->delete_edge( $_[1], $_[2] ) } ], ); plan tests => 1; my $g = Graph::Undirected->new; $g->add_cycle( 1..6 ); for ($g->edges) { $g->set_edge_attribute( @$_, 'color', 'black' ); } $g->set_edge_attribute( 1, 2, 'color', 'red' ); $g->set_edge_attribute( 3, 4, 'color', 'red' ); $g->set_edge_attribute( 5, 6, 'color', 'red' ); parse_graph( $g, @rules ); is scalar $g->edges, 3; Graph-Grammar-0.2.0/t/01_delete_pendants.t0000644000200400020040000000127714674751676020140 0ustar andriusandrius#!/usr/bin/perl use strict; use warnings; use Graph::Grammar; use Graph::Undirected; use Test::More; my @rules = ( [ 'degree() based rule', sub { $_[0]->degree( $_[1] ) == 1 }, sub { $_[0]->delete_vertex( $_[1] ) } ], [ 'NoMoreVertices based rule', sub { 1 }, sub { 1 }, NO_MORE_VERTICES, sub { $_[0]->delete_vertex( $_[1] ) } ], ); plan tests => 3 * @rules; for my $rule (@rules) { my $g = Graph::Undirected->new; $g->add_cycle( 1..6 ); parse_graph( $g, $rule ); is scalar $g->vertices, 6; for (1..6) { $g->add_edge( $_, 10 + $_ ); } is scalar $g->vertices, 12; parse_graph( $g, $rule ); is scalar $g->vertices, 6; } Graph-Grammar-0.2.0/Makefile.PL0000644000200400020040000000210114674751676016007 0ustar andriusandrius# This file was automatically generated by Dist::Zilla::Plugin::MakeMaker v6.012. use strict; use warnings; use ExtUtils::MakeMaker; my %WriteMakefileArgs = ( "ABSTRACT" => "Grammar for graphs", "AUTHOR" => "Andrius Merkys ", "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => 0 }, "DISTNAME" => "Graph-Grammar", "LICENSE" => "bsd", "NAME" => "Graph::Grammar", "PREREQ_PM" => { "Clone" => 0, "Graph" => 0, "Graph::MoreUtils" => "0.2.0", "List::Util" => 0, "Set::Object" => 0 }, "VERSION" => "0.2.0", "test" => { "TESTS" => "t/*.t" } ); my %FallbackPrereqs = ( "Clone" => 0, "Graph" => 0, "Graph::MoreUtils" => "0.2.0", "List::Util" => 0, "Set::Object" => 0 ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.63_03) } ) { delete $WriteMakefileArgs{TEST_REQUIRES}; delete $WriteMakefileArgs{BUILD_REQUIRES}; $WriteMakefileArgs{PREREQ_PM} = \%FallbackPrereqs; } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); Graph-Grammar-0.2.0/lib/0000775000200400020040000000000014674751676014613 5ustar andriusandriusGraph-Grammar-0.2.0/lib/Graph/0000775000200400020040000000000014674751676015654 5ustar andriusandriusGraph-Grammar-0.2.0/lib/Graph/Grammar.pm0000644000200400020040000001702114674751676017577 0ustar andriusandriuspackage 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, but instead of acting on text streams Graph::Grammar is oriented at graphs, as implemented in Perl's L module. Graph::Grammar implements a single method C which accepts an instance of L 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. 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 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 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 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 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 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, Emerkys@cpan.orgE =cut 1; Graph-Grammar-0.2.0/lib/Graph/Grammar/0000775000200400020040000000000014674751676017242 5ustar andriusandriusGraph-Grammar-0.2.0/lib/Graph/Grammar/Rule/0000775000200400020040000000000014674751676020151 5ustar andriusandriusGraph-Grammar-0.2.0/lib/Graph/Grammar/Rule/NoMoreVertices.pm0000644000200400020040000000036114674751676023411 0ustar andriusandriuspackage Graph::Grammar::Rule::NoMoreVertices; # ABSTRACT: Marker for rules where no more vertices are allowed our $VERSION = '0.2.0'; # VERSION use strict; use warnings; sub new { my $class = shift; return bless {}, $class; } 1; Graph-Grammar-0.2.0/lib/Graph/Grammar/Rule/Edge.pm0000644000200400020040000000046614674751676021357 0ustar andriusandriuspackage Graph::Grammar::Rule::Edge; # ABSTRACT: Rule to be evaluated on graph edges our $VERSION = '0.2.0'; # VERSION use strict; use warnings; sub new { my( $class, $code ) = @_; return bless { code => $code }, $class; } sub matches { my $self = shift; return $self->{code}->( @_ ); } 1; Graph-Grammar-0.2.0/META.json0000644000200400020040000000236514674751676015472 0ustar andriusandrius{ "abstract" : "Grammar for graphs", "author" : [ "Andrius Merkys " ], "dynamic_config" : 0, "generated_by" : "Dist::Zilla version 6.012, CPAN::Meta::Converter version 2.150010", "license" : [ "bsd" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Graph-Grammar", "prereqs" : { "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Clone" : "0", "Graph" : "0", "Graph::MoreUtils" : "v0.2.0", "List::Util" : "0", "Set::Object" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/merkys/graph-grammar/issues" }, "homepage" : "https://search.cpan.org/dist/Graph-Grammar", "repository" : { "type" : "git", "url" : "git://github.com/merkys/graph-grammar.git", "web" : "https://github.com/merkys/graph-grammar" } }, "version" : "0.2.0", "x_generated_by_perl" : "v5.30.0", "x_serialization_backend" : "Cpanel::JSON::XS version 4.19" }