File Coverage

blib/lib/Parse/RandGen/Production.pm
Criterion Covered Total %
statement 78 176 44.3
branch 24 122 19.6
condition 6 28 21.4
subroutine 11 19 57.8
pod 5 14 35.7
total 124 359 34.5


line stmt bran cond sub pod time code
1             # $Revision: #3 $$Date: 2005/08/31 $$Author: jd150722 $
2             ######################################################################
3             #
4             # This program is Copyright 2003-2005 by Jeff Dutton.
5             #
6             # This program is free software; you can redistribute it and/or modify
7             # it under the terms of either the GNU General Public License or the
8             # Perl Artistic License.
9             #
10             # This program is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13             # GNU General Public License for more details.
14             #
15             # If you do not have a copy of the GNU General Public License write to
16             # the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
17             # MA 02139, USA.
18             ######################################################################
19              
20             package Parse::RandGen::Production;
21              
22             require 5.006_001;
23 4     4   24 use Carp;
  4         7  
  4         339  
24 4     4   25 use Parse::RandGen qw($Debug);
  4         7  
  4         491  
25 4     4   22 use strict;
  4         15  
  4         135  
26 4     4   21 use vars qw($Debug);
  4         6  
  4         10453  
27              
28             ######################################################################
29             #### Creators
30              
31             sub new {
32 11     11 1 20 my $class = shift;
33 11         68 my $self = {
34             _conditions => [ ], # Ordered list of Conditions that must be satisfied for the production to be true
35             _action => undef, # Action to take if the production is satisfied
36             _rule => undef, # The Rule that this Production belongs to
37             _name => undef, # The name of the Production (most are anonymous, but Productions can be named if they need to be accessed later)
38             _number => undef, # The number of the Production in the Rule (which production is this 0...X)
39             #@_,
40             };
41 11   33     80 bless $self, ref($class)||$class;
42              
43             # Optional named arguments can be passed. Any unknown named arguments are turned into object data members.
44 11         157 my @args = @_; # Arguments can override defaults or create new attributes in the object
45 11         24 my $numArgs = $#args + 1;
46 11 50       57 if ($numArgs == 1) {
    100          
47 0         0 $self->addCond(shift(@args));
48             } elsif ($numArgs) {
49 1 50       4 ($numArgs % 2) and confess("%Error: new Production called with an odd number of arguments ($numArgs); arguments must be in named pairs (or a single Condition argument)!");
50 1         11 $self->set(@args);
51             }
52              
53 10         29 my $rule = $self->{_rule};
54 10 50 33     52 (!defined($self->{_rule}) || UNIVERSAL::isa($rule, "Parse::RandGen::Rule"))
55             or confess("%Error: new Production was passed an unknown \"rule\" argument \"$rule\"!\n");
56              
57 10         36 return($self);
58             }
59              
60             ######################################################################
61             #### Methods
62              
63             sub set {
64 1 50   1 0 7 my $self = shift or confess("%Error: Cannot call without a valid object!");
65 1         3 my @args = @_;
66            
67 1         1 my $numArgs = $#args + 1;
68 1 50       4 ($numArgs) or confess("%Error: Production::set() called with no arguments!");
69 1 50       4 ($numArgs % 2) and confess("%Error: Production::set() called with an odd number of arguments ($numArgs); arguments must be in named pairs!");
70 1         3 while ($#args >= 0) {
71 1         3 my ($arg, $val) = (shift(@args), shift(@args));
72 1 50       3 if ($arg eq "cond") {
    0          
    0          
73 1         4 $self->addCond($val);
74             } elsif ($arg eq "action") {
75 0         0 $self->{_action} = $val;
76             } elsif ($arg eq "rule") {
77 0 0       0 UNIVERSAL::isa($val, "Parse::RandGen::Rule") or confess("%Error: Production::set() called with a bad \"rule\" argument ($val)!");
78 0         0 $self->{_rule} = $val;
79             } else {
80             # Unknown arguments become data members
81 0         0 $self->{$arg} = $val;
82             }
83             }
84             }
85              
86             sub addCond {
87 11 50   11 0 34 my $self = shift or confess("%Error: Cannot call without a valid object!");
88 11         57 my @args = @_;
89            
90 11         32 while ($#args >= 0) {
91 11         19 my $val = shift(@args);
92 11 50       29 defined($val) or confess("%Error: Production::addCond(): condition is undefined!");
93 11         14 my $element = $val;
94 11         15 my $cond = undef;
95 11         17 my $valRef = ref($val);
96 11 50       22 if ($valRef) {
    0          
    0          
97 11 100       43 if ($valRef eq "Regexp") {
98             # Regular expression
99 1         22 $cond = Parse::RandGen::Regexp->new($element);
100             } else {
101 10 50       46 (UNIVERSAL::isa($val, "Parse::RandGen::Condition"))
102             or confess("%Error: The Production condition is a reference (ref=\"$valRef\"), but not a supported type!");
103 10         21 $cond = $val;
104             }
105             } elsif ($val =~ m/^\s* (\w+) (?: \( (.*?) \) )? \s*$/x) { # subrule(subargs)
106 0         0 my ($min, $max) = (1, 1);
107 0         0 $element = $1;
108 0         0 my $subargs = $2;
109 0 0 0     0 if (defined($subargs) && $subargs) {
110 0 0       0 if ($subargs eq "?" ) { $min = 0; $max = 1; } # ?
  0 0       0  
  0 0       0  
    0          
111 0         0 elsif ($subargs =~ m/^(s|\+)$/ ) { $min = 1; $max = undef; } # s or +
  0         0  
112 0         0 elsif ($subargs =~ m/^((s\?)|\*)$/ ) { $min = 0; $max = undef; } # s? or *
  0         0  
113 0   0     0 elsif ($subargs =~ /(\d+)(?:\.\.(\d+))?/ ) { $min = $1; $max = ($2 || $min); } # 2..3 or ..4 or 5..
  0         0  
114             else {
115 0         0 confess("%Error: The Production condition \"${val}\" has decoded to be a subrule, but the subargs are not understood (${subargs})!");
116             }
117             }
118 0         0 $cond = Parse::RandGen::Subrule->new($element, min=>$min, max=>$max);
119             } elsif ($element =~ $Parse::RandGen::Literal::ValidLiteralRE) {
120             # Must be a literal surrounded by single or double quotes
121 0         0 $element = Parse::RandGen::Literal::stripLiteral($element);
122 0         0 $cond = Parse::RandGen::Literal->new($element);
123             } else {
124 0         0 confess("%Error: The Production condition \"${val}\" has decoded to be a literal, but it doesn't look good!");
125             }
126            
127 10         19 $cond->{_production} = $self;
128 10         13 push @{$self->{_conditions}}, $cond;
  10         521  
129             }
130             }
131              
132             sub check {
133 0 0   0 1 0 my $self = shift or confess("%Error: Cannot call without a valid object!");
134 0 0       0 return "%Error: Production has no RandGen object!\n" unless $self->grammar();
135 0         0 my $grammarName = $self->grammar()->name();
136              
137 0         0 my $err = "";
138 0         0 foreach my $cond (@{$self->{_conditions}}) {
  0         0  
139 0 0       0 next unless $cond->isSubrule();
140 0         0 my $subrule = $cond->subrule(); # Will be undef if there is a problem
141 0         0 my $subruleName = $cond->element();
142 0 0       0 next unless defined($subruleName); # Anonymous subrule
143 0         0 my $rule = $self->grammar()->rule($subruleName);
144 0 0 0     0 next if (defined($rule) && ($rule == $subrule)); # Everything is OK!
145 0         0 my $ruleName = $self->rule()->name(); # The name of the rule that this production belongs to...
146 0 0       0 $err .= "%Error: The \"${ruleName}\" rule references the subrule \"${subruleName}\", which is not defined in the \"${grammarName}\" grammar!\n" unless (defined($subrule));
147             }
148 0         0 return $err;
149             }
150              
151             sub dump {
152 0 0   0 0 0 my $self = shift or confess("%Error: Cannot call without a valid object!");
153 0         0 my $output = "";
154 0         0 foreach my $cond (@{$self->{_conditions}}) {
  0         0  
155 0 0       0 $output .= " " if $output;
156 0         0 $output .= $cond->dump();
157             }
158 0         0 $output .= $self->_dumpParseFunction();
159 0         0 return $output;
160             }
161              
162             sub dumpHeir {
163 400 50   400 0 1677 my $self = shift or confess("%Error: Cannot call without a valid object!");
164 400         660 my $output = "";
165 400         395 foreach my $cond (@{$self->{_conditions}}) {
  400         1409  
166 400 50       1401 $output .= " " if $output;
167 400         1289 $output .= $cond->dump();
168             }
169 400         1904 return $output;
170             }
171              
172             sub pick {
173 805 50   805 0 1873 my $self = shift or confess("%Error: Cannot call without a valid object!");
174 805         3368 my %args = ( match=>1, # Default is to pick matching data
175             vals => { }, # Hash of values of various hard-coded sub-rules (by name)
176             @_ );
177 805         2419 my @conds = $self->conditions();
178 805         1267 my $badCond;
179 805         1093 my $val = "";
180              
181 805 100       1815 if (!$args{match}) {
182 132         449 my @badConds;
183 132         228 foreach my $cond (@conds) {
184 233 50 66     966 next if ($cond->isQuantSupported() && $cond->zeroOrMore()); # Cannot corrupt
185 233         680 push(@badConds, $cond);
186             }
187 132         6890 my $i = int(rand($#badConds+1));
188 132         274 $badCond = $badConds[$i];
189             }
190              
191 805         2114 for (my $i=0; $i <= $#conds; $i++) {
192 1007   66     7001 $val .= $conds[$i]->pick(%args, match=>($args{match} || ((defined($badCond) && ($badCond==$conds[$i]))?0:1)) );
193             }
194              
195 805         31794 return( $val );
196             }
197              
198             # Returns true (1) if this production contains any of the rules specified by the "vals" argument
199             sub containsVals {
200 1812 50   1812 0 4586 my $self = shift or confess("%Error: Cannot call without a valid object!");
201 1812         9261 my %args = ( vals => { }, # Hash of values of various hard-coded sub-rules (by name)
202             @_ );
203 1812         7597 foreach my $cond ($self->conditions()) {
204 2014 50       7671 return 1 if $cond->containsVals(%args);
205             }
206 1812         15574 return 0;
207             }
208              
209             ######################################################################
210             #### Accessors
211              
212             sub action {
213 0 0   0 0 0 my $self = shift or confess("%Error: Cannot call name() without a valid object!");
214 0         0 return $self->{_action};
215             }
216              
217             sub rule { # Rule that this Production belongs to
218 0 0   0 1 0 my $self = shift or confess("%Error: Cannot call rule() without a valid object!");
219 0         0 return $self->{_rule};
220             }
221              
222             sub name { # Name of the Production (optional)
223 0 0   0 0 0 my $self = shift or confess("%Error: Cannot call rule() without a valid object!");
224 0         0 return $self->{_name};
225             }
226              
227             sub number { # Production number on its Rule (required if defined(rule()))
228 0 0   0 0 0 my $self = shift or confess("%Error: Cannot call rule() without a valid object!");
229 0         0 return $self->{_number};
230             }
231              
232             sub grammar {
233 0 0   0 1 0 my $self = shift or confess("%Error: Cannot call grammar() without a valid object!");
234 0 0       0 my $grammar = $self->rule()->grammar() if defined($self->rule());
235 0         0 return $grammar;
236             }
237              
238             sub conditions {
239 2617 50   2617 1 8318 my $self = shift or confess("%Error: Cannot call conditions() without a valid object!");
240 2617         3322 return (@{$self->{_conditions}});
  2617         7979  
241             }
242              
243             ######################################################################
244             #### Private Functions
245              
246             sub _dumpParseFunction {
247 0 0   0     my $self = shift or confess("%Error: Cannot call without a valid object!");
248 0           my $output = "\n\t\t\t {\t";
249 0           my $indent = "\n\t\t\t\t";
250              
251 0 0         if (defined($self->{_action})) {
252 0           $output .= $self->{_action} . " }";
253 0           return $output;
254             }
255             # Determine whether this is a single terminal production or not
256 0           my @conds = @{$self->{_conditions}};
  0            
257 0           my $ind = 1; # Index 0 is the rule name
258 0           $output .= 'my $val=""; my $obj={val=>undef,offset=>$itempos[1]{offset}{from},len=>0,rules=>{}};';
259 0           foreach my $cond (@conds) {
260 0           $output .= $indent;
261 0 0         my $sName = $cond->isSubrule() ? $cond->subrule()->name() : ""; # Name of subrule, if subrule...
262 0 0 0       my $sKeep = $sName ? $cond->subrule()->{keep}||"" : ""; # Keep this subrule?
263 0 0 0       my $sParse = $sName ? $cond->subrule()->{parse}||"" : ""; # Parse this subrule (preserve heirarchy beneath it)
264 0 0         if ($cond->once()) {
265             #$output .= "if (ref(\$item[$ind])) { \$val.=\$item[$ind]->{val}; } else { \$val.=\$item[$ind]; }";
266 0 0         if ($sName) {
267 0           $output .= "\$val.=\$item[$ind]->{val};";
268 0 0         if ($sKeep eq "once") {
    0          
269 0           $output .= " \$obj->{rules}{$sName}=\$item[$ind];";
270             } elsif ($sKeep eq "all") {
271 0           $output .= " \$obj->{rules}{$sName}||=[]; push(\@{\$obj->{rules}{$sName}}, \$item[$ind]);";
272             }
273 0 0         if (!$sParse) { # Not adding a new level of parse, so flatten rules
274 0           $output .= "${indent}foreach my \$j (keys \%{\$item[$ind]->{rules}}) {"
275             ."${indent}\tmy \$o=\$item[$ind]->{rules}{\$j};"
276             ."${indent}\tif (ref(\$o) eq \"ARRAY\") { \$obj->{rules}{\$j}||=[]; push(\@{\$obj->{rules}{\$j}}, \$o); }"
277             ."${indent}\telse { \$obj->{rules}{\$j}=\$o; } }";
278             }
279             } else {
280 0           $output .= "\$val.=\$item[$ind];";
281             }
282             } else {
283             #$output .= "foreach my \$i (\@{\$item[$ind]}) { if(ref(\$i)){ \$val.=\$i->{val}; } else { \$val.=\$i; }";
284 0 0         if ($sName) {
285 0           $output .= "foreach my \$i (\@{\$item[$ind]}) { \$val.=\$i->{val};";
286 0 0         if ($sKeep eq "once") {
    0          
287 0           $output .= " \$obj->{rules}{$sName}=\$i;";
288             } elsif ($sKeep eq "all") {
289 0           $output .= " \$obj->{rules}{$sName} ||= []; push(\@{\$obj->{rules}{$sName}}, \$i);";
290             }
291 0 0         if (!$sParse) { # Not adding a new level of parse, so flatten rules
292 0           $output .= "${indent}\tforeach my \$j (keys \%{\$i->{rules}}) {"
293             ."${indent}\t\tmy \$o=\$i->{rules}{\$j};"
294             ."${indent}\t\tif (ref(\$o) eq \"ARRAY\") { \$obj->{rules}{\$j}||=[]; push(\@{\$obj->{rules}{\$j}}, \$o); }"
295             ."${indent}\t\telse { \$obj->{rules}{\$j}=\$o; } }";
296             }
297 0           $output .= " }";
298             } else {
299 0           $output .= "foreach my \$i (\@{\$item[$ind]}) { \$val.=\$i; }";
300             }
301             }
302 0           $ind++;
303             }
304             #$output .= " print(\$item[0],\" [\",\$itempos[1]{offset}{from},\"..\${thisoffset}]\\n\");";
305 0 0 0       (defined($self->rule()) and $self->rule()->name()) or confess("%Error: _dumpParseFunction(): Rule is not defined or the Rule is anonymous (no name)!");
306 0           my $ruleName = $self->rule()->name();
307 0           my $prodNum = $self->number();
308 0 0         ($self->grammar()->rule($ruleName) == $self->rule()) or confess("%Error: Internal error! Cannot find our Rule \"$ruleName\" on our RandGen!");
309             #$output .= " \$thisparser->{local}{grammar}->rule(\"$ruleName\")->production($prodNum);";
310 0           $output .= $indent.'$obj->{val}=$val; $obj->{len}=length($val);';
311 0           $output .= ' $return=$obj; }';
312 0           return $output;
313             }
314              
315             ######################################################################
316             #### Package return
317             1;
318             __END__