#
# Module Parse::Yapp::Grammar
#
# (c) Copyright 1998-2001 Francois Desarmenien, all rights reserved.
# (see the pod text in Parse::Yapp module for use and distribution rights)
#
package Parse::Yapp::Grammar;
@ISA=qw( Parse::Yapp::Options );
require 5.004;
use Carp;
use strict;
use Parse::Yapp::Options;
use Parse::Yapp::Parse;
###############
# Constructor #
###############
sub new {
my($class)=shift;
my($values);
my($self)=$class->SUPER::new(@_);
my($parser)=new Parse::Yapp::Parse;
defined($self->Option('input'))
or croak "No input grammar";
$values = $parser->Parse($self->Option('input'));
undef($parser);
$$self{GRAMMAR}=_ReduceGrammar($values);
ref($class)
and $class=ref($class);
bless($self, $class);
}
###########
# Methods #
###########
##########################
# Method To View Grammar #
##########################
sub ShowRules {
my($self)=shift;
my($rules)=$$self{GRAMMAR}{RULES};
my($ruleno)=-1;
my($text);
for (@$rules) {
my($lhs,$rhs)=@$_;
$text.=++$ruleno.":\t".$lhs." -> ";
if(@$rhs) {
$text.=join(' ',map { $_ eq chr(0) ? '$end' : $_ } @$rhs);
}
else {
$text.="/* empty */";
}
$text.="\n";
}
$text;
}
###########################
# Method To View Warnings #
###########################
sub Warnings {
my($self)=shift;
my($text);
my($grammar)=$$self{GRAMMAR};
exists($$grammar{UUTERM})
and do {
$text="Unused terminals:\n\n";
for (@{$$grammar{UUTERM}}) {
$text.="\t$$_[0], declared line $$_[1]\n";
}
$text.="\n";
};
exists($$grammar{UUNTERM})
and do {
$text.="Useless non-terminals:\n\n";
for (@{$$grammar{UUNTERM}}) {
$text.="\t$$_[0], declared line $$_[1]\n";
}
$text.="\n";
};
exists($$grammar{UURULES})
and do {
$text.="Useless rules:\n\n";
for (@{$$grammar{UURULES}}) {
$text.="\t$$_[0] -> ".join(' ',@{$$_[1]})."\n";
}
$text.="\n";
};
$text;
}
######################################
# Method to get summary about parser #
######################################
sub Summary {
my($self)=shift;
my($text);
$text ="Number of rules : ".
scalar(@{$$self{GRAMMAR}{RULES}})."\n";
$text.="Number of terminals : ".
scalar(keys(%{$$self{GRAMMAR}{TERM}}))."\n";
$text.="Number of non-terminals : ".
scalar(keys(%{$$self{GRAMMAR}{NTERM}}))."\n";
$text;
}
###############################
# Method to Ouput rules table #
###############################
sub RulesTable {
my($self)=shift;
my($inputfile)=$self->Option('inputfile');
my($linenums)=$self->Option('linenumbers');
my($rules)=$$self{GRAMMAR}{RULES};
my($ruleno);
my($text);
defined($inputfile)
or $inputfile = 'unkown';
$text="[\n\t";
$text.=join(",\n\t",
map {
my($lhs,$rhs,$code)=@$_[0,1,3];
my($len)=scalar(@$rhs);
my($text);
$text.="[#Rule ".$ruleno++."\n\t\t '$lhs', $len,";
if($code) {
$text.= "\nsub".
( $linenums
? qq(\n#line $$code[1] "$inputfile"\n)
: " ").
"{$$code[0]}";
}
else {
$text.=' undef';
}
$text.="\n\t]";
$text;
} @$rules);
$text.="\n]";
$text;
}
################################
# Methods to get HEAD and TAIL #
################################
sub Head {
my($self)=shift;
my($inputfile)=$self->Option('inputfile');
my($linenums)=$self->Option('linenumbers');
my($text);
$$self{GRAMMAR}{HEAD}[0]
or return '';
defined($inputfile)
or $inputfile = 'unkown';
for (@{$$self{GRAMMAR}{HEAD}}) {
$linenums
and $text.=qq(#line $$_[1] "$inputfile"\n);
$text.=$$_[0];
}
$text
}
sub Tail {
my($self)=shift;
my($inputfile)=$self->Option('inputfile');
my($linenums)=$self->Option('linenumbers');
my($text);
$$self{GRAMMAR}{TAIL}[0]
or return '';
defined($inputfile)
or $inputfile = 'unkown';
$linenums
and $text=qq(#line $$self{GRAMMAR}{TAIL}[1] "$inputfile"\n);
$text.=$$self{GRAMMAR}{TAIL}[0];
$text
}
#################
# Private Stuff #
#################
sub _UsefulRules {
my($rules,$nterm) = @_;
my($ufrules,$ufnterm);
my($done);
$ufrules=pack('b'.@$rules);
$ufnterm={};
vec($ufrules,0,1)=1; #start rules IS always useful
RULE:
for (1..$#$rules) { # Ignore start rule
for my $sym (@{$$rules[$_][1]}) {
exists($$nterm{$sym})
and next RULE;
}
vec($ufrules,$_,1)=1;
++$$ufnterm{$$rules[$_][0]};
}
do {
$done=1;
RULE:
for (grep { vec($ufrules,$_,1) == 0 } 1..$#$rules) {
for my $sym (@{$$rules[$_][1]}) {
exists($$nterm{$sym})
and not exists($$ufnterm{$sym})
and next RULE;
}
vec($ufrules,$_,1)=1;
exists($$ufnterm{$$rules[$_][0]})
or do {
$done=0;
++$$ufnterm{$$rules[$_][0]};
};
}
}until($done);
($ufrules,$ufnterm)
}#_UsefulRules
sub _Reachable {
my($rules,$nterm,$term,$ufrules,$ufnterm)=@_;
my($reachable);
my(@fifo)=( 0 );
$reachable={ '$start' => 1 }; #$start is always reachable
while(@fifo) {
my($ruleno)=shift(@fifo);
for my $sym (@{$$rules[$ruleno][1]}) {
exists($$term{$sym})
and do {
++$$reachable{$sym};
next;
};
( not exists($$ufnterm{$sym})
or exists($$reachable{$sym}) )
and next;
++$$reachable{$sym};
push(@fifo, grep { vec($ufrules,$_,1) } @{$$nterm{$sym}});
}
}
$reachable
}#_Reachable
sub _SetNullable {
my($rules,$term,$nullable) = @_;
my(@nrules);
my($done);
RULE:
for (@$rules) {
my($lhs,$rhs)=@$_;
exists($$nullable{$lhs})
and next;
for (@$rhs) {
exists($$term{$_})
and next RULE;
}
push(@nrules,[$lhs,$rhs]);
}
do {
$done=1;
RULE:
for (@nrules) {
my($lhs,$rhs)=@$_;
exists($$nullable{$lhs})
and next;
for (@$rhs) {
exists($$nullable{$_})
or next RULE;
}
$done=0;
++$$nullable{$lhs};
}
}until($done);
}
sub _ReduceGrammar {
my($values)=@_;
my($ufrules,$ufnterm,$reachable);
my($grammar)={ HEAD => $values->{HEAD},
TAIL => $values->{TAIL},
EXPECT => $values->{EXPECT} };
my($rules,$nterm,$term) = @$values {'RULES', 'NTERM', 'TERM'};
($ufrules,$ufnterm) = _UsefulRules($rules,$nterm);
exists($$ufnterm{$values->{START}})
or die "*Fatal* Start symbol $values->{START} derives nothing, at eof\n";
$reachable = _Reachable($rules,$nterm,$term,$ufrules,$ufnterm);
$$grammar{TERM}{chr(0)}=undef;
for my $sym (keys %$term) {
( exists($$reachable{$sym})
or exists($values->{PREC}{$sym}) )
and do {
$$grammar{TERM}{$sym}
= defined($$term{$sym}[0]) ? $$term{$sym} : undef;
next;
};
push(@{$$grammar{UUTERM}},[ $sym, $values->{SYMS}{$sym} ]);
}
$$grammar{NTERM}{'$start'}=[];
for my $sym (keys %$nterm) {
exists($$reachable{$sym})
and do {
exists($values->{NULL}{$sym})
and ++$$grammar{NULLABLE}{$sym};
$$grammar{NTERM}{$sym}=[];
next;
};
push(@{$$grammar{UUNTERM}},[ $sym, $values->{SYMS}{$sym} ]);
}
for my $ruleno (0..$#$rules) {
vec($ufrules,$ruleno,1)
and exists($$grammar{NTERM}{$$rules[$ruleno][0]})
and do {
push(@{$$grammar{RULES}},$$rules[$ruleno]);
push(@{$$grammar{NTERM}{$$rules[$ruleno][0]}},$#{$$grammar{RULES}});
next;
};
push(@{$$grammar{UURULES}},[ @{$$rules[$ruleno]}[0,1] ]);
}
_SetNullable(@$grammar{'RULES', 'TERM', 'NULLABLE'});
$grammar;
}#_ReduceGrammar
1;