File Coverage

lib/Parse/Gnaw/Blocks/Letter.pm
Criterion Covered Total %
statement 88 124 70.9
branch 11 32 34.3
condition 16 37 43.2
subroutine 13 17 76.4
pod 10 10 100.0
total 138 220 62.7


line stmt bran cond sub pod time code
1              
2              
3              
4             package Parse::Gnaw::Blocks::Letter;
5              
6             our $VERSION = '0.001';
7              
8             #BEGIN {print "Parse::Gnaw::Blocks::Letter\n";}
9              
10 19     19   103 use warnings;
  19         32  
  19         551  
11 19     19   94 use strict;
  19         32  
  19         555  
12 19     19   123 use Carp;
  19         46  
  19         1222  
13 19     19   118 use Data::Dumper;
  19         31  
  19         1101  
14 19     19   96 use Storable 'dclone';
  19         43  
  19         1428  
15              
16              
17 19     19   7443 use Parse::Gnaw::Blocks::LetterConstants;
  19         53  
  19         1697  
18 19     19   7878 use Parse::Gnaw::LinkedListConstants;
  19         45  
  19         32260  
19              
20             =head1 NAME
21              
22             Parse::Gnaw::Blocks::Letter - a linked list element that holds a single scalar payload.
23              
24              
25             =head2 new
26              
27             This is the constructor for a letter object, which is part of a LinkedListObject
28              
29             Parse::Gnaw::Blocks::Letter->new($linkedlist, $lettervalue, $letterlocation);
30              
31             Linkedlist is the linkedlist object that contains this letter.
32             Lettervalue is probably a single character like 'b'.
33             Letterlocation is a string that describes where the letter originaly came from (filename/linenum).
34              
35              
36             =cut
37              
38             sub new {
39              
40 263     263 1 435 my ($pkg, $llist, $value, $location)=@_;
41 263   100     668 $location ||= 'unknown';
42              
43 263         339 my $connmin1=$llist->[LIST__CONNECTIONS_MINUS_ONE];
44 263         286 my @connections;
45 263         487 foreach my $dimension (0 .. $connmin1){
46 566         1500 push(@connections, [undef,undef]);
47             }
48              
49              
50 263         753 my $ltrobj=bless([],$pkg);
51              
52 263         504 $ltrobj->[LETTER__LINKED_LIST] = $llist;
53 263         424 $ltrobj->[LETTER__DATA_PAYLOAD]= $value;
54 263         398 $ltrobj->[LETTER__CONNECTIONS] = \@connections;
55 263         454 $ltrobj->[LETTER__WHERE_LETTER_CAME_FROM] = $location;
56 263         409 $ltrobj->[LETTER__LETTER_HAS_BEEN_CONSUMED]=0;
57              
58             # get the most recently created letter
59 263         281 my $previous_letter;
60 263 100       683 if( $llist->[LIST__MOST_RECENTLY_CREATED_LETTER]){
61 236         299 $previous_letter = $llist->[LIST__MOST_RECENTLY_CREATED_LETTER];
62              
63             # find out what most recently created letter pointed "next start" to.
64 236         258 my $next_letter;
65 236 100 66     1010 if($previous_letter and $previous_letter->[LETTER__NEXT_START]){
66 182         268 $next_letter = $previous_letter->[LETTER__NEXT_START];
67             }
68              
69             # previous_letter connects to newletter
70             # $previous_letter->link_two_letters_via_next_start($ltrobj);
71 236         313 $previous_letter->[LETTER__NEXT_START]=$ltrobj;
72 236         392 $ltrobj->[LETTER__PREVIOUS_START]=$previous_letter;
73              
74             }
75              
76             # update the linked list so that THIS newly created letter is now the most recently created letter.
77 263         405 $llist->[LIST__MOST_RECENTLY_CREATED_LETTER] = $ltrobj;
78              
79 263         897 return $ltrobj; # return the letter
80             }
81              
82              
83             my $blank_obj=[];
84             #print "blank_obj is '$blank_obj'\n"; die;
85             my $blank_str=$blank_obj.'';
86             my $blank_len=length($blank_str);
87             my $BLANK = '.'x($blank_len-5);
88              
89             =head2 get_raw_address
90              
91             This is a subroutine. Do NOT call this as a method. This will allow it to handle undef values.
92              
93             my $retval = get_raw_address($letterobj);
94              
95             Given a letter object, get the string that looks like
96              
97             Parse::Gnaw::Blocks::Letter=ARRAY(0x850cea4)
98              
99             and return something like
100              
101             0x850cea4
102              
103             =cut
104             sub get_raw_address{
105 177     177 1 214 my ($ltrobj)=@_;
106              
107 177 100       419 unless(defined($ltrobj)){
108 40         85 return $BLANK;
109             }
110              
111 137         301 my $string=$ltrobj.'';
112 137 50       563 $string=~m{(\(0x[0-9a-f]+\))} or croak "could not get_raw_address";
113 137         317 my $addr=$1;
114              
115 137         385 return $addr;
116              
117             }
118              
119              
120             =head2 display
121              
122             print out a formatted version of letter object.
123              
124             =cut
125              
126             sub display {
127 59     59 1 81 my ($ltrobj)=@_;
128 59         1024 print "\n";
129 59         2030 print "\tletterobject: ".$ltrobj."\n";
130 59         2030 print "\tpayload: '".($ltrobj->[LETTER__DATA_PAYLOAD])."'\n";
131 59         1870 print "\tfrom: ".($ltrobj->[LETTER__WHERE_LETTER_CAME_FROM])."\n";
132 59         2466 print "\t"."connections:\n";
133              
134 59         149 my $self = get_raw_address($ltrobj);
135              
136 59         83 foreach my $conn (@{$ltrobj->[LETTER__CONNECTIONS]}){
  59         1237  
137 59         92 my $prev = $conn->[LETTER__CONNECTION_PREV];
138 59         72 my $next = $conn->[LETTER__CONNECTION_NEXT];
139 59         103 my $prev_addr = get_raw_address($prev);
140 59         109 my $next_addr = get_raw_address($next);
141              
142 59         2419 print "\t\t [ $prev_addr , $next_addr ]\n";
143              
144             }
145              
146              
147 59         937 print "\n";
148 59         181 return;
149             }
150              
151              
152              
153              
154             =head2 get_more_letters
155              
156             if a LETTER needs more letters, then call this and we'll have the linked list get more letters.
157             Note that $which will be either NEXTSTART or NEXTCONN
158              
159             =cut
160              
161             sub get_more_letters{
162             # $which will be "CONNECTIONS" or "NEXTSTART"
163 0     0 1 0 my($ltrobj,$which,$axis)=@_; # note: $axis will default to 0 if not supplied.
164 0         0 eval{
165 0         0 $ltrobj->get_linked_list()->get_more_letters($ltrobj,$which,$axis);
166             };
167 0 0       0 if($@){
168 0         0 croak "$@ ";
169             }
170             }
171              
172              
173             =head2 Connections verus Next Starting Position
174             If we want to parse a 2-D array of text, we have to step through each starting position
175             and try to match the regular expression to the string. The regular expression can match
176             through any connection between letters.
177              
178             For example, a simple 2D list could be interconnected vertically and horizontally like this:
179              
180             1---2---3
181             | | |
182             | | |
183             | | |
184             4---5---6
185             | | |
186             | | |
187             | | |
188             7---8---9
189              
190             Or it could be connected on diagonals as well:
191              
192             1---2---3
193             |\ /|\ /|
194             | X | X |
195             |/ \|/ \|
196             4---5---6
197             |\ /|\ /|
198             | X | X |
199             |/ \|/ \|
200             7---8---9
201              
202             As we try to fit a regular expression to the linked list, we will follow the CONNECTIONS
203             to figure out what letters are in sequential order.
204              
205             As we parse, if we're at letter "3", this can connect to 2, 6, and possibly 5.
206             But if starting from "3" does not yeild a match, then we need to move to the next starting position,
207             which could be "4". 4 doesn't connect to 3, but it is the next starting position after 3.
208              
209              
210              
211             simple 3D list might be connected horizontally and vertically like this:
212              
213             1----2----3
214             |\ |\ |\
215             | 4--+-5--+-6
216             | | | | | |
217             7-+--8-+--9 |
218             \| \| \|
219             a----b----c
220              
221              
222             The "starting position" order could be 1->2->3->4->5->6->7->8->9->a->b->c
223              
224             Note that 3 is not CONNECTED to 4, but if we try 3 as a starting position
225             and it fails, then after 3 the NEXT STARTING POSITION is 4.
226              
227             The NextStartingPosition and the ConnectionsBetweenLetters are two different concepts
228             that are built into the data structures of the linked list and the letters.
229              
230             And they are accessed through several methods:
231              
232             Connections:
233              
234             We can create a connection between two letters with:
235             link_two_letters_via_interconnection
236             And we can get the next connection with:
237             next_connection
238              
239              
240             Starting Positions:
241              
242             We can create a link between letters for starting connections with:
243             link_two_letters_via_next_start
244             We can traverse from one starting position to the next with:
245             advance_start_position
246              
247              
248              
249             =cut
250              
251              
252             =head2 link_two_letters_via_next_start
253              
254             $first->link_two_letters_via_next_start($second);
255              
256             Create a link so that after $first, the next starting position is $second.
257              
258             =cut
259             sub link_two_letters_via_next_start{
260 418     418 1 534 my ($firstltr,$nextltr)=@_;
261 418         528 $firstltr->[LETTER__NEXT_START]=$nextltr;
262 418         1109 $nextltr->[LETTER__PREVIOUS_START]=$firstltr;
263              
264             }
265              
266             =head2 advance_start_position
267              
268             Advance (move) the starting position to the next spot.
269              
270             my $second = $first->advance_start_position();
271              
272             We tried to match the regular expression starting from $first, but it didn't match.
273             So, now we want to advance to the $second starting position and try from there.
274              
275             If nextstart points to end or null or whatever, then get more letters.
276              
277             =cut
278             sub advance_start_position{
279 0     0 1 0 my $ltrobj=shift(@_);
280              
281 0 0 0     0 if(
282             # if it is undef or 0 or "false" in any perl sense of false
283             (not($ltrobj->[LETTER__NEXT_START]))
284              
285             # or if it points to the LAST POINTER of the linked list object
286             or ($ltrobj->[LETTER__NEXT_START] eq $ltrobj->[LETTER__LINKED_LIST]->[LIST__LAST_START])
287             ){
288 0         0 $ltrobj->get_more_letters("START_POSITION");
289             }
290 0         0 return $ltrobj->[LETTER__NEXT_START];
291             }
292              
293              
294             =head2 link_two_letters_via_interconnection
295              
296             $first->link_two_letters_via_interconnection($second,$axis);
297              
298             Create a linkage between $first and $second so that they are INTERCONNECTED
299             to be treated as sequential letters for parsing purposes.
300              
301             The $axis defaults to 0. It represents whatever axis your linked list structure needs.
302             For example, one axis could be the "vertical" axis. In that example, $first could be thought
303             of as being "up" from $second. And $second could be thought of as "down" from $first.
304              
305             =cut
306              
307             sub link_two_letters_via_interconnection{
308 329     329 1 441 my ($thisltr, $nextltr, $axis)=@_; # axis optional and defaults to 0
309              
310             #warn "link_two_letters_via_interconnection";
311             #if(defined($thisltr)){$thisltr->display();}
312             #if(defined($nextltr)){$nextltr->display();}
313              
314              
315 329   100     872 $axis||=0;
316              
317 329 50       714 if ($axis>($thisltr->[LETTER__LINKED_LIST]->[LIST__CONNECTIONS_MINUS_ONE])){
318 0         0 my $max=$thisltr->[LETTER__LINKED_LIST]->[LIST__CONNECTIONS_MINUS_ONE];
319 0         0 croak "ERROR: axis greater than max number of axis for letter (axis is $axis)(max is $max)";
320             }
321              
322             # initially we have START->LAST
323             # when we add letter "A", we end up with START->A->LAST,
324             # this is fine for starting position connectoin
325             # but parsing interconnection does not connect to FIRSTSTART or LASTSTART.
326             # FIRST and LAST are placeholders and should never be parsed.
327 329         420 my $firststart=$thisltr->[LETTER__LINKED_LIST]->[LIST__FIRST_START];
328 329         381 my $laststart =$thisltr->[LETTER__LINKED_LIST]->[LIST__LAST_START];
329 329 50 33     4318 if(
      33        
      33        
      33        
      33        
330             not(defined($thisltr))
331             or not(defined($nextltr))
332             or ($thisltr eq $firststart)
333             or ($thisltr eq $laststart)
334             or ($nextltr eq $firststart)
335             or ($nextltr eq $laststart)
336             ){
337             # do nothing. Do not create parsing interconnection to FIRSTSTART or LASTSTART markers.
338             } else {
339             # both letters are valid letters, interconnect them.
340 329         493 $thisltr->[LETTER__CONNECTIONS]->[$axis]->[LETTER__CONNECTION_NEXT]=$nextltr;
341 329         1398 $nextltr->[LETTER__CONNECTIONS]->[$axis]->[LETTER__CONNECTION_PREV]=$thisltr;
342             }
343             }
344              
345              
346             =head2 advance_to_next_connection
347              
348             my $next_letter = $curr_letter->advance_to_next_connection($overalldirectionforrule);
349              
350             We are at $curr_letter, trying to fit the regular expression to string.
351             The next letter will be returned by advance_to_next_connection($axis)
352             where axis is which index into the array to look for the connection.
353              
354             =cut
355              
356             sub advance_to_next_connection {
357 0     0 1 0 my ($ltrobj)=@_;
358            
359 0         0 my $llist = $ltrobj->[LETTER__LINKED_LIST];
360            
361 0         0 my $axis =$llist->[LIST__HEADING_DIRECTION_INDEX];
362 0         0 my $prevnext=$llist->[LIST__HEADING_PREVNEXT_INDEX];
363              
364             #warn "axis "; print Dumper $axis;
365             #warn "prevnext "; print Dumper $prevnext;
366              
367 0 0       0 if ($ltrobj->[LETTER__CONNECTIONS]->[$axis]->[$prevnext]){
368 0         0 return $ltrobj->[LETTER__CONNECTIONS]->[$axis]->[$prevnext];
369             } else {
370 0         0 $ltrobj->get_more_letters("CONNECTIONS", $axis, $prevnext);
371 0         0 return $ltrobj->[LETTER__CONNECTIONS]->[$axis]->[$prevnext];
372             }
373             }
374              
375              
376              
377              
378              
379             =head2 get_list_of_connecting_letters
380              
381             return a list of possible letters to try based on parsing connections array for this letter
382             and any other rules you want to use for your grammar.
383              
384             By default, this class method will return an array of any connected letter that is not already consumed.
385              
386             You can override this behaviour by redefining the method to do whatever you want.
387             You could, for example, require that the connections only go in a straight line.
388             Or you could, as a counter example, allow any connection, including letters that
389             have been marked as "consumed" and allow them to be used again and again.
390              
391             You might even allow the current letter to be used multiple times for multiple rules without advancing.
392              
393             =cut
394              
395             sub get_list_of_connecting_letters{
396              
397 66     66 1 104 my($ltrobj)=@_;
398              
399 66         119 my $arrayref = [];
400              
401 66         90 my $size = scalar(@{$ltrobj->[LETTER__CONNECTIONS]});
  66         166  
402              
403 66         1117 for(my $firstindex=0; $firstindex<$size; $firstindex++) {
404 102         217 my $connection_array_ref = $ltrobj->[LETTER__CONNECTIONS]->[$firstindex];
405            
406 102         600 foreach my $secondindex (LETTER__CONNECTION_NEXT, LETTER__CONNECTION_PREV){
407              
408 204         274 my $nextletter = $connection_array_ref->[$secondindex];
409              
410              
411 204 100 66     1606 if(defined($nextletter) and ($nextletter) and ($nextletter->[LETTER__LETTER_HAS_BEEN_CONSUMED]==0) ){
      100        
412 113         314 push(@$arrayref, $nextletter);
413             }
414             }
415             }
416              
417 66         260 return (@$arrayref);
418             }
419              
420              
421             =head2 delete
422              
423             delete this letter and all previous letters
424              
425             work your way back until we get to the first_start position.
426              
427             Note: this assumes that object connections are symmetrical.
428              
429             if A connects to B at dimension 3, then B connects to B at dimension 3 in the opposite direction.
430              
431             =cut
432              
433             sub delete{
434 0     0 1   my ($ltrobj)=@_;
435              
436             # if $thisobj is firststart or laststart, then return. leave the markers alone.
437 0 0         return if($ltrobj eq $ltrobj->[LETTER__LINKED_LIST]->[LIST__FIRST_START]);
438 0 0         return if($ltrobj eq $ltrobj->[LETTER__LINKED_LIST]->[LIST__LAST_START]);
439              
440              
441             # look at all connections and make sure no one points to $thisobj.
442             # want $thisobj reference count to go to zero so it will be garbage collected.
443             # Note that this assumes one level of symmetry: that the only thing that points
444             # to $thisobj are the letters connected to $thisobj.
445             # The assumption is that nothing connects to A unless A also connects to IT.
446             # so if we go through all the connections for $thisobj, then we'll find and delte
447             # all the connections TO $thisobj.
448 0           foreach my $dimension (0 .. scalar(@{$ltrobj->[LETTER__CONNECTIONS]})) {
  0            
449 0           foreach my $direction ( LETTER__CONNECTION_NEXT, LETTER__CONNECTION_PREV){
450 0           my $otherobj=$ltrobj->[LETTER__CONNECTIONS]->[$dimension]->[$direction];
451 0 0 0       if(defined($otherobj) and ref($otherobj)){
452             # delete anything in $otherobj connections that equals $thisobj
453             # note this assumes another level of symmetry.
454             # i.e. if A points to B at dimension 3, direction 0,
455             # then B points to A at dimension 3, direction 1.
456 0 0         my $inversedirection=($direction == LETTER__CONNECTION_NEXT)
457             ? LETTER__CONNECTION_PREV : LETTER__CONNECTION_NEXT;
458              
459             # delete the connection from $otherobj to $thisobj. Set it to undef.
460 0           $otherobj->[LETTER__CONNECTIONS]->[$dimension]->[$inversedirection]=undef;
461             }
462             }
463             }
464              
465              
466             # get the previous_start letter
467 0           my $prevstart=$ltrobj->[LETTER__PREVIOUS_START];
468              
469             # get the nextstart letter from thisobj
470 0           my $nextstart=$ltrobj->[LETTER__NEXT_START];
471              
472              
473             # if linked list currstart points to thisobj, then have ll currstart point to nextstart.
474 0 0         if($ltrobj eq $ltrobj->[LETTER__LINKED_LIST]->[LIST__CURR_START]){
475 0           $ltrobj->[LETTER__LINKED_LIST]->[LIST__CURR_START] = $nextstart;
476             }
477              
478              
479              
480             # if prevstart is something, then it's nextstart points to thisobj, delete that reference
481             # have prevstart letter point to nextstart letter so that we still have a sequence of some kind.
482             # if we continue going back through prevstart, then firststart should eventually end up
483             # pointing to the nextstart letter, adn we'll still be in the correct order.
484 0 0 0       if( defined($prevstart) and (ref($prevstart))){
485 0           $prevstart->[LETTER__NEXT_START] = $nextstart;
486             }
487              
488             # return the previous_start letter. User can loop until we return first_start.
489 0           return $prevstart;
490              
491             }
492              
493              
494              
495              
496             1;
497