File Coverage

blib/lib/MarpaX/Languages/ECMAScript/AST/Grammar/ECMAScript_262_5/Program.pm
Criterion Covered Total %
statement 30 179 16.7
branch 0 72 0.0
condition 0 42 0.0
subroutine 10 25 40.0
pod 4 4 100.0
total 44 322 13.6


line stmt bran cond sub pod time code
1             #
2             # Here is the exhaustive list of difficulties with the ECMAScript grammar:
3             #
4             # * The list of reserved keywords is context sensitive (depend on strict mode)
5             # * The source CAN be not ok v.s. semi-colon: automatic semi-colon insertion will then happen
6             # * Allowed separators is contextual (sometimes no line terminator is allowed)
7             # * RegularExpressionLiteral ambiguity with AssignmentExpression or MultiplicativeExpression
8             #
9 1     1   4 use strict;
  1         2  
  1         38  
10 1     1   5 use warnings FATAL => 'all';
  1         1  
  1         54  
11              
12             package MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Program;
13 1     1   414 use parent qw/MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Base/;
  1         244  
  1         4  
14 1     1   519 use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Program::Semantics;
  1         3  
  1         62  
15 1     1   576 use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::RegularExpressionLiteral;
  1         2  
  1         32  
16 1     1   6 use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::StringLiteral;
  1         1  
  1         18  
17 1     1   383 use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::NumericLiteral;
  1         2  
  1         31  
18 1     1   5 use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses;
  1         2  
  1         24  
19 1     1   4 use MarpaX::Languages::ECMAScript::AST::Exceptions qw/:all/;
  1         1  
  1         193  
20             #use Log::Any qw/$log/;
21 1     1   6 use SUPER;
  1         1  
  1         64  
22              
23             # ABSTRACT: ECMAScript-262, Edition 5, lexical program grammar written in Marpa BNF
24              
25             our $VERSION = '0.019'; # VERSION
26              
27             our $WhiteSpace = qr/(?:[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsWhiteSpace}])/;
28             our $LineTerminator = qr/(?:[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsLineTerminator}])/;
29             our $SingleLineComment = qr/(?:\/\/[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsSourceCharacterButNotLineTerminator}]*)/;
30             our $MultiLineComment = qr/(?:(?:\/\*)(?:(?:[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsSourceCharacterButNotStar}]+|\*(?![\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsSourceCharacterButNotSlash}]))*)(?:\*\/))/;
31             our $UnicodeLetter = qr/[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsUnicodeLetter}]/;
32             our $HexDigit4 = qr/[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsHexDigit}]{4}/;
33              
34             our $S = qr/(?:$WhiteSpace|$LineTerminator|$SingleLineComment|$MultiLineComment)/;
35             our $isPostLineTerminatorLength = qr/\G$S+/;
36             our $isPreSLength = qr/\G$S+/;
37             our $isRcurly = qr/\G$S*\}/;
38             our $isEnd = qr/\G$S*$/;
39             our $isDecimalDigit = qr/\G[\p{MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::CharacterClasses::IsDecimalDigit}]/;
40             our $isIdentifierStart = qr/\G(?:$UnicodeLetter|\$|_|\\u$HexDigit4)/;
41             our @Keyword =
42             qw/break do instanceof typeof
43             case else new var
44             catch finally return void
45             continue for switch while
46             debugger function this with
47             default if throw
48             delete in try/;
49              
50             our @FutureReservedWord =
51             qw/class enum extends super
52             const export import/;
53              
54             our @FutureReservedWordStrict =
55             qw/implements let private public yield
56             interface package protected static/;
57              
58             our @NullLiteral = qw/null/;
59              
60             our @BooleanLiteral = qw/true false/;
61              
62             #
63             # Force eventual higher priority
64             #
65             our %PRIORITY = (FUNCTION => 2);
66              
67             our $grammar_content = do {local $/; };
68              
69             #
70             # It is clearer to have reserved words in an array. But for efficienvy the hash is better,
71             # so I create on-the-fly associated hashes using the arrays. Convention is: the lexeme name of
72             # a reserved word is this word, but in capital letters (since none of them is in capital letter)
73             #
74             our %Keyword = map {($_, uc($_))} @Keyword;
75             our %FutureReservedWord = map {($_, uc($_))} @FutureReservedWord;
76             our %FutureReservedWordStrict = map {($_, uc($_))} @FutureReservedWordStrict;
77             our %NullLiteral = map {($_, uc($_))} @NullLiteral;
78             our %BooleanLiteral = map {($_, uc($_))} @BooleanLiteral;
79              
80             #
81             # ... And we inject in the grammar those that exist (FutureReservedWord do not)
82             #
83             $grammar_content .= "\n";
84             # ... Priorities
85             map {$grammar_content .= ":lexeme ~ <$_> priority => " . ($PRIORITY{$_} || 1) . "\n"} values %Keyword;
86             map {$grammar_content .= ":lexeme ~ <$_> priority => " . ($PRIORITY{$_} || 1) . "\n"} values %NullLiteral;
87             map {$grammar_content .= ":lexeme ~ <$_> priority => " . ($PRIORITY{$_} || 1) . "\n"} values %BooleanLiteral;
88             # ... Definition
89             map {$grammar_content .= uc($_) . " ~ '$_'\n"} @Keyword;
90             map {$grammar_content .= uc($_) . " ~ '$_'\n"} @NullLiteral;
91             map {$grammar_content .= uc($_) . " ~ '$_'\n"} @BooleanLiteral;
92             #
93             # Injection of grammars.
94             #
95             our $StringLiteral = MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::StringLiteral->new();
96             our $RegularExpressionLiteral = MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::RegularExpressionLiteral->new();
97             our $NumericLiteral = MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Lexical::NumericLiteral->new();
98             $grammar_content .= $StringLiteral->extract;
99             $grammar_content .= $NumericLiteral->extract;
100             $grammar_content .= $RegularExpressionLiteral->extract;
101              
102             #
103             # For convenience in the IDENTIFIER$ lexeme callback, we merge Keyword, FutureReservedWord, NullLiteral, BooleanLiteral into
104             # a single hash ReservedWord.
105             #
106             our %ReservedWord = map {$_ => 1} (keys %Keyword, keys %FutureReservedWord, keys %NullLiteral, keys %BooleanLiteral);
107              
108              
109              
110             sub make_grammar_content {
111 0     0 1   my ($class) = @_;
112 0           return $grammar_content;
113             }
114              
115              
116             sub make_semantics_package {
117 0     0 1   my ($class) = @_;
118 0           return join('::', $class, 'Semantics');
119             }
120              
121              
122             sub spacesAny {
123 0     0 1   my $self = shift;
124 0 0         if (@_) {
125 0           $self->{_spacesAny} = shift;
126             }
127 0           return $self->{_spacesAny};
128             }
129              
130              
131             sub parse {
132 0     0 1   my ($self, $source, $impl) = @_;
133             #
134             # Because of Automatic Semicolon Insertion that may happen at the end,
135             # a space is appended to a copy of the source to be parsed.
136             #
137 0           $self->{programCompleted} = 0;
138 0           $source .= ' ';
139 0           return $self->SUPER($source, $impl,
140             {
141             callback => \&_eventCallback,
142             callbackargs => [ $self ],
143             failure => \&_failureCallback,
144             failureargs => [ $self ],
145             end => \&_endCallback,
146             endargs => [ $self ],
147             });
148             }
149              
150             sub _eventCallback {
151 0     0     my ($self, $source, $pos, $max, $impl) = @_;
152              
153             #
154             # $pos is the exact position where SLIF stopped because of an event
155             #
156 0           my $rc = $pos;
157 0           my %lastLexeme = ();
158 0           my $lastLexemeDoneb = 0;
159              
160             #
161             # Cache of some call results
162             #
163 0           $self->{_isIdentifierStart} = {};
164 0           $self->{_isDecimalDigit} = {};
165 0           $self->{_preSLength} = {};
166 0           $self->{_isEnd} = {};
167 0           $self->{_postLineTerminatorLength} = {};
168              
169 0           foreach (@{$impl->events()}) {
  0            
170 0           my ($name) = @{$_};
  0            
171             #
172             # Events are always in this order:
173             #
174             # ---------------------------------
175             # 1. Completion events first (XXX$)
176             # ---------------------------------
177             #
178 0 0 0       if ($name eq 'Program$') {
    0          
    0          
    0          
    0          
    0          
    0          
179             #
180             # Program$ will happen rarely, so even it does cost it is ok to do so
181             #
182 0   0       $self->{programCompleted} = ($self->{_isEnd}->{$pos} //= $self->_isEnd($source, $pos, $impl));
183             }
184             elsif ($name eq 'NumericLiteral$') {
185             #
186             # The source character immediately following a NumericLiteral must not be
187             # an IdentifierStart or DecimalDigit
188             #
189 0 0 0       if (($self->{_isIdentifierStart}->{$pos} //= $self->_isIdentifierStart($source, $pos, $impl)) ||
      0        
      0        
190             ($self->{_isDecimalDigit}->{$pos} //= $self->_isDecimalDigit($source, $pos, $impl))) {
191 0           my ($start, $end) = $impl->last_completed_range('NumericLiteral');
192 0           my $lastNumericLiteral = $impl->range_to_string($start, $end);
193 0           SyntaxError(error => "NumericLiteral $lastNumericLiteral must not be immediately followed by an IdentifierStart or DecimalDigit");
194             }
195             }
196             elsif ($name eq 'IDENTIFIER$') {
197 0 0         if (! $lastLexemeDoneb) {
198 0           $self->getLastLexeme(\%lastLexeme, $impl);
199 0           $lastLexemeDoneb = 1;
200             }
201 0 0         if (exists($ReservedWord{$lastLexeme{value}})) {
202 0           SyntaxError(error => "Identifier $lastLexeme{value} is a reserved word");
203             }
204             }
205             #
206             # ------------------------
207             # 2. nulled events (XXX[])
208             # ------------------------
209             #
210             # ------------------------------------
211             # 3. prediction events (^XXX or ^^XXX)
212             # ------------------------------------
213             #
214             elsif ($name eq '^INVISIBLE_SEMICOLON') {
215             #
216             # In the AST, we explicitely associate the ';' to the missing semicolon
217             #
218 0 0         if (! $lastLexemeDoneb) {
219 0           $self->getLastLexeme(\%lastLexeme, $impl);
220 0           $lastLexemeDoneb = 1;
221             }
222 0   0       my $Slength = ($self->{_preSLength}->{$rc} //= $self->_preSLength($source, $rc, $impl));
223 0           $self->_insertInvisibleSemiColon($impl, $rc, $Slength);
224 0           $rc += $Slength;
225             }
226             #
227             # ^PLUSPLUS_POSTFIX, ^MINUSMINUS_POSTFIX
228             # --------------------------------------
229             elsif ($name eq '^PLUSPLUS_POSTFIX' || $name eq '^MINUSMINUS_POSTFIX') {
230 0 0         if (! $lastLexemeDoneb) {
231 0           $self->getLastLexeme(\%lastLexeme, $impl);
232 0           $lastLexemeDoneb = 1;
233             }
234 0           my $postLineTerminatorPos = $lastLexeme{start} + $lastLexeme{length};
235 0   0       my $postLineTerminatorLength = ($self->{_postLineTerminatorLength}->{$postLineTerminatorPos} //= $self->_postLineTerminatorLength($source, $postLineTerminatorPos, $impl));
236 0           my $asi = 0;
237 0 0         if ($postLineTerminatorLength > 0) {
238 0 0         if (! $impl->lexeme_read('SEMICOLON', $postLineTerminatorPos, $postLineTerminatorLength, ';')) {
239 0           SyntaxError(error => "SEMICOLON lexeme_read failure at position $rc");
240             }
241 0           $asi = 1;
242             }
243 0           my $lname = $name;
244 0           substr($lname, 0, 1, '');
245 0 0         my $lvalue = ($lname eq 'PLUSPLUS_POSTFIX') ? '++' : '--';
246             #
247             # The value does not change if ASI was performed. But the name, yes.
248             #
249 0 0         if ($asi) {
250 0 0         $lname = ($lname eq 'PLUSPLUS_POSTFIX') ? 'PLUSPLUS' : 'MINUSMINUS';
251             }
252 0 0         if (! $impl->lexeme_read($lname, $rc, 2, $lvalue)) {
253 0           SyntaxError(error => "$lname lexeme_read failure at position $rc");
254             }
255 0           $rc += 2;
256             }
257             #
258             # ^^DIV (because of REGULAREXPRESSIONLITERAL that can eat it)
259             # -----------------------------------------------------------
260             elsif ($name eq '^^DIV') {
261 0   0       my $realpos = $rc + ($self->{_preSLength}->{$rc} //= $self->_preSLength($source, $rc, $impl));
262 0 0 0       if (index($source, '/', $realpos) == $realpos &&
      0        
      0        
263             index($source, '/=', $realpos) != $realpos &&
264             index($source, '//', $realpos) != $realpos &&
265             index($source, '/*', $realpos) != $realpos) {
266 0 0         if (! $impl->lexeme_read('DIV', $realpos, 1, '/')) {
267 0           SyntaxError(error => "DIV lexeme_read failure at position $rc");
268             }
269 0           $rc = $realpos + 1;
270             }
271             }
272             #
273             # ^^DIVASSIGN (because of REGULAREXPRESSIONLITERAL that can eat it)
274             # ------------------------------------------------------------------
275             elsif ($name eq '^^DIVASSIGN') {
276 0   0       my $realpos = $rc + ($self->{_preSLength}->{$rc} //= $self->_preSLength($source, $rc, $impl));
277 0 0 0       if (index($source, '/=', $realpos) == $realpos &&
      0        
278             index($source, '//', $realpos) != $realpos &&
279             index($source, '/*', $realpos) != $realpos) {
280 0 0         if (! $impl->lexeme_read('DIVASSIGN', $realpos, 2, '/=')) {
281 0           SyntaxError(error => "DIVASSIGN lexeme_read failure at position $rc");
282             }
283 0           $rc = $realpos + 2;
284             }
285             }
286             }
287              
288             #
289             # Remove cache
290             #
291 0           delete($self->{_isIdentifierStart});
292 0           delete($self->{_isDecimalDigit});
293 0           delete($self->{_preSLength});
294 0           delete($self->{_isEnd});
295 0           delete($self->{_postLineTerminatorLength});
296              
297             #if ($rc != $pos) {
298             # $log->tracef('[_eventCallback] Resuming at position %d (was %d when called)', $rc, $pos);
299             #}
300              
301 0           return $rc;
302             }
303              
304             sub _postLineTerminatorLength {
305             # my ($self, $source, $pos, $impl) = @_;
306              
307 0     0     my $rc = 0;
308              
309 0           my $prevpos = pos($_[1]);
310 0           pos($_[1]) = $_[2];
311              
312             #
313             # Take care: the separator is: _WhiteSpace | _LineTerminator | _SingleLineComment | _MultiLineComment
314             # where a _MultiLineComment that contains a _LineTerminator is considered equivalent to a _LineTerminator
315             #
316             # This is why if we find a separator just before $pos, we check again the presence of _LineTerminator in the match
317             #
318 0 0         if ($_[1] =~ $isPostLineTerminatorLength) {
319 0           my $length = $+[0] - $-[0];
320 0 0         if (substr($_[1], $-[0], $length) =~ /$LineTerminator/) {
321 0           $rc = $length;
322             }
323             }
324              
325             #if ($rc > 0) {
326             # $log->tracef('[_postLineTerminatorLength] Found postLineTerminator of length %d', $rc);
327             #}
328              
329 0           pos($_[1]) = $prevpos;
330              
331 0           return $rc;
332             }
333              
334             sub _preSLength {
335             # my ($self, $source, $pos, $impl) = @_;
336              
337 0     0     my $rc = 0;
338              
339 0           my $prevpos = pos($_[1]);
340 0           pos($_[1]) = $_[2];
341              
342 0 0         if ($_[1] =~ $isPreSLength) {
343 0           my $length = $+[0] - $-[0];
344 0           $rc = $length;
345             }
346              
347             #if ($rc > 0) {
348             # $log->tracef('[_preSLength] Found S of length %d', $rc);
349             #}
350              
351 0           pos($_[1]) = $prevpos;
352              
353 0           return $rc;
354             }
355              
356             sub _isRcurly {
357             # my ($self, $source, $pos, $impl) = @_;
358              
359 0     0     my $rc = 0;
360              
361 0           my $prevpos = pos($_[1]);
362 0           pos($_[1]) = $_[2];
363              
364 0 0         if ($_[1] =~ $isRcurly) {
365 0           $rc = 1;
366             }
367              
368             #if ($rc) {
369             # $log->tracef('[_isRcurly] Found \'}\'');
370             #}
371              
372 0           pos($_[1]) = $prevpos;
373              
374 0           return $rc;
375             }
376              
377             sub _isIdentifierStart {
378             # my ($self, $source, $pos, $impl) = @_;
379              
380 0     0     my $rc = 0;
381              
382 0           my $prevpos = pos($_[1]);
383 0           pos($_[1]) = $_[2];
384              
385 0 0         if ($_[1] =~ $isIdentifierStart) {
386 0           $rc = 1;
387             }
388              
389             #if ($rc) {
390             # $log->tracef('[_isIdentifierStart] Found \'%s\'', $&);
391             #}
392              
393 0           pos($_[1]) = $prevpos;
394              
395 0           return $rc;
396             }
397              
398             sub _isDecimalDigit {
399             # my ($self, $source, $pos, $impl) = @_;
400              
401 0     0     my $rc = 0;
402              
403 0           my $prevpos = pos($_[1]);
404 0           pos($_[1]) = $_[2];
405              
406 0 0         if ($_[1] =~ $isDecimalDigit) {
407 0           $rc = 1;
408             }
409              
410             #if ($rc) {
411             # $log->tracef('[_isDecimalDigit] Found \'%s\'', $&);
412             #}
413              
414 0           pos($_[1]) = $prevpos;
415              
416 0           return $rc;
417             }
418              
419             sub _isEnd {
420             # my ($self, $source, $pos, $impl) = @_;
421              
422 0     0     my $grammar = $_[0]->spacesAny->{grammar};
423 0           my $impl = $_[0]->spacesAny->{impl};
424 0           $grammar->parse($_[1], $impl, $_[2]);
425 0           return $grammar->endReached;
426             }
427              
428             sub _insertSemiColon {
429 0     0     my ($self, $impl, $pos, $length) = @_;
430              
431 0 0         if (! $impl->lexeme_read('SEMICOLON', $pos, $length, ';')) {
432 0           SyntaxError(error => "Automatic Semicolon Insertion not allowed at position $pos");
433             }
434             }
435              
436             sub _insertInvisibleSemiColon {
437 0     0     my ($self, $impl, $pos, $length) = @_;
438              
439 0 0         if (! $impl->lexeme_read('INVISIBLE_SEMICOLON', $pos, $length, ';')) {
440 0           SyntaxError(error => "Automatic Invisible Semicolon Insertion not allowed at position $pos");
441             }
442             }
443              
444             sub _failureCallback {
445 0     0     my ($self, $source, $pos, $max, $impl) = @_;
446              
447             #
448             # The position of failure is exactly the end of the very last lexeme
449             #
450 0           my %lastLexeme = ();
451 0           $self->getLastLexeme(\%lastLexeme, $impl);
452 0           my $rc = $lastLexeme{start} + $lastLexeme{length};
453              
454             #
455             # Automatic Semicolon Insertion rules apply here
456             #
457             # 1. When, as the program is parsed from left to right, a token
458             # (called the offending token) is encountered that is not allowed
459             # by any production of the grammar, then a semicolon is automatically
460             # inserted before the offending token if one or more of the following conditions is true:
461             # - The offending token is separated from the previous token by at least one LineTerminator.
462             # - The offending token is }.
463             #
464 0           my $length = 0;
465 0 0         if (($length = $self->_postLineTerminatorLength($source, $rc, $impl)) > 0) {
    0          
466 0           $self->_insertSemiColon($impl, $rc, $length);
467 0           $rc += $length;
468             } elsif ($self->_isRcurly($source, $rc, $impl)) {
469 0           $self->_insertSemiColon($impl, $rc, 1);
470             } else {
471 0           SyntaxError();
472             }
473              
474 0           return $rc;
475             }
476              
477             sub _endCallback {
478 0     0     my ($self, $source, $pos, $max, $impl) = @_;
479              
480 0 0         if ($self->{programCompleted}) {
481 0           return;
482             }
483              
484             #
485             # Automatic Semicolon Insertion rules apply here
486             #
487             # 2. When, as the program is parsed from left to right, the end of the input stream of tokens
488             # is encountered and the parser is unable to parse the input token stream as a single complete ECMAScript Program
489             # then a semicolon is automatically inserted at the end of the input stream.
490             #
491 0 0         if (! $self->{programCompleted}) {
492 0           my %lastLexeme = ();
493 0           $self->getLastLexeme(\%lastLexeme, $impl);
494 0           my $lastValidPos = $lastLexeme{start} + $lastLexeme{length};
495 0           $self->_insertSemiColon($impl, $lastValidPos, 1);
496 0           my $haveProgramCompletion = grep {$_ eq 'Program$'} map {$_->[0]} @{$impl->events};
  0            
  0            
  0            
497 0 0         if (! $haveProgramCompletion) {
498 0           SyntaxError(error => "Incomplete program");
499             }
500             }
501             }
502              
503              
504             1;
505              
506             =pod
507              
508             =encoding UTF-8
509              
510             =head1 NAME
511              
512             MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Program - ECMAScript-262, Edition 5, lexical program grammar written in Marpa BNF
513              
514             =head1 VERSION
515              
516             version 0.019
517              
518             =head1 SYNOPSIS
519              
520             use strict;
521             use warnings FATAL => 'all';
522             use MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Program;
523              
524             my $grammar = MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Program->new();
525              
526             my $grammar_content = $grammar->content();
527             my $grammar_option = $grammar->grammar_option();
528             my $recce_option = $grammar->recce_option();
529              
530             =head1 DESCRIPTION
531              
532             This modules returns describes the ECMAScript 262, Edition 5 lexical program grammar written in Marpa BNF, as of L. This module inherits the methods from MarpaX::Languages::ECMAScript::AST::Grammar::ECMAScript_262_5::Base package.
533              
534             =head1 SUBROUTINES/METHODS
535              
536             =head2 make_grammar_content($class)
537              
538             Returns the grammar. This will be injected in the Program's grammar.
539              
540             =head2 semantics_package($class)
541              
542             Class method that returns Program default recce semantics_package. These semantics are adding ruleId to all values, and execute eventually StringLiteral lexical grammar.
543              
544             =head2 spacesAny($self, $spacesAny)
545              
546             Getter/Setter of a SpacesAny grammar implementation, used internally by the Program grammar.
547              
548             =head2 parse($self, $source, $impl)
549              
550             Parse the source given as $source using implementation $impl.
551              
552             =head1 SEE ALSO
553              
554             L
555              
556             =head1 AUTHOR
557              
558             Jean-Damien Durand
559              
560             =head1 COPYRIGHT AND LICENSE
561              
562             This software is copyright (c) 2013 by Jean-Damien Durand.
563              
564             This is free software; you can redistribute it and/or modify it under
565             the same terms as the Perl 5 programming language system itself.
566              
567             =cut
568              
569             __DATA__