File Coverage

blib/lib/ChordPro/Chords/Parser.pm
Criterion Covered Total %
statement 502 629 79.8
branch 159 260 61.1
condition 64 139 46.0
subroutine 77 101 76.2
pod 0 10 0.0
total 802 1139 70.4


line stmt bran cond sub pod time code
1             #! perl
2              
3 79     79   995 use v5.26;
  79         303  
4 79     79   445 use utf8;
  79         196  
  79         437  
5 79     79   1999 use Carp;
  79         194  
  79         4524  
6 79     79   530 use feature qw( signatures );
  79         179  
  79         6868  
7 79     79   590 no warnings "experimental::signatures";
  79         219  
  79         3686  
8              
9             # package ParserWatch;
10             #
11             # require Tie::Hash;
12             # our @ISA = qw( Tie::StdHash );
13             #
14             # sub STORE {
15             # if ( $_[1] !~ /^[[:alpha:]]+$/ ) {
16             # Carp::cluck("STORE $_[1] " . $_[2]);
17             # ::dump($_[2]);
18             # }
19             # $_[0]->{$_[1]} = $_[2];
20             # }
21              
22 79     79   565 use ChordPro;
  79         232  
  79         87027  
23              
24             my %parsers;
25              
26             # tie %parsers => 'ParserWatch';
27              
28             package ChordPro::Chords::Parser;
29              
30             # The parser analyses a chord and returns an object with the following
31             # attributes:
32             #
33             # name name as passed to the parser (e.g. Cism7)
34             #
35             # root textual decomposition: root part (e.g. Cis)
36             # qual textual decomposition: quality part (e.g. m)
37             # ext textual decomposition: extension part (e.g. 7)
38             # bass textual decomposition: bass part (a root)
39             #
40             # system notation system (common, nashville, user defined)
41             # root_canon canonical root (e.g. Cis => C#)
42             # root_ord root ordinal (e.g. Cis => 1)
43             # root_mod root modifier (e.g. is => # => +1)
44             # qual_canon canonical qualifier (e.g. m => -)
45             # ext_canon canonical extension (e.g. sus => sus4)
46             # bass_canon like root, for bass note
47             # bass_ord like root, for bass note
48             # bass_mod like root, for bass note
49             #
50             # The parsers are one of
51             # ChordPro::Chords::Parser::Common
52             # ChordPro::Chords::Parser::Nashville
53             # ChordPro::Chords::Parser::Roman
54             #
55             # The objects are one of
56             # ChordPro::Chord::Common
57             # ChordPro::Chord::Nashville
58             # ChordPro::Chord::Roman
59              
60             # Creates a parser based on the current (optionally augmented)
61             # context.
62             # Note that the appropriate way is to call
63             # ChordPro::Chords::Parser->get_parser.
64              
65 2     2 0 5 sub new ( $pkg, $init ) {
  2         4  
  2         5  
  2         4  
66              
67 2 50       11 Carp::confess("Missing config?") unless $::config;
68             # Use current config, optionally augmented by $init.
69 2   50     5 my $cfg = { %{$::config//{}}, %{$init//{}} };
  2   50     15  
  2         30  
70              
71             Carp::croak("Missing notes in parser creation")
72 2 50       12 unless $cfg->{notes};
73 2         5 my $system = $cfg->{notes}->{system};
74 2 50       7 Carp::croak("Missing notes system in parser creation")
75             unless $system;
76              
77 2 50       7 if ( $system eq "nashville" ) {
78 0         0 return ChordPro::Chords::Parser::Nashville->new($cfg);
79             }
80 2 50       8 if ( $system eq "roman" ) {
81 0         0 return ChordPro::Chords::Parser::Roman->new($cfg);
82             }
83 2         13 return ChordPro::Chords::Parser::Common->new($cfg);
84             }
85              
86             # The default parser has built-in support for common (dutch) note
87             # names.
88              
89 11504     11504 0 18543 sub default ( $pkg ) {
  11504         18955  
  11504         16166  
90              
91             return $parsers{common} //=
92             ChordPro::Chords::Parser::Common->new
93 11504   33     39972 ( { %{$::config},
  0         0  
94             "notes" =>
95             { "system" => "common",
96             "sharp" => [ "C", [ "C#", "Cis", "C♯" ],
97             "D", [ "D#", "Dis", "D♯" ],
98             "E",
99             "F", [ "F#", "Fis", "F♯" ],
100             "G", [ "G#", "Gis", "G♯" ],
101             "A", [ "A#", "Ais", "A♯" ],
102             "B",
103             ],
104             "flat" => [ "C",
105             [ "Db", "Des", "D♭" ], "D",
106             [ "Eb", "Es", "Ees", "E♭" ], "E",
107             "F",
108             [ "Gb", "Ges", "G♭" ], "G",
109             [ "Ab", "As", "Aes", "A♭" ], "A",
110             [ "Bb", "Bes", "B♭" ], "B",
111             ],
112             },
113             },
114             );
115             }
116              
117             # Cached version of the individual parser's parse_chord.
118 64811     64811 0 94203 sub parse ( $self, $chord ) {
  64811         95630  
  64811         98347  
  64811         89345  
119             #### $self->{chord_cache}->{$chord} //=
120 64811         141323 $self->parse_chord($chord);
121             }
122              
123             # Virtual.
124 0     0 0 0 sub parse_chord ( $self, $chord ) {
  0         0  
  0         0  
  0         0  
125 0         0 Carp::confess("Virtual method 'parse_chord' not defined");
126             }
127              
128             # Fetch a parser for a known system, with fallback.
129             # Default is a parser for the current config.
130 348     348 0 898 sub get_parser ( $self, $system = undef, $nofallback = undef ) {
  348         802  
  348         914  
  348         724  
  348         637  
131              
132 348   66     1625 $system //= $::config->{notes}->{system};
133 348 100       1936 return $parsers{$system} if $parsers{$system};
134              
135 210 100       1967 if ( $system eq "nashville" ) {
    100          
    50          
    50          
    0          
136 2   33     24 return $parsers{$system} //=
137             ChordPro::Chords::Parser::Nashville->new;
138             }
139             elsif ( $system eq "roman" ) {
140 2   33     29 return $parsers{$system} //=
141             ChordPro::Chords::Parser::Roman->new;
142             }
143             elsif ( $system ne $::config->{notes}->{system} ) {
144 0         0 my $p = ChordPro::Chords::Parser::Common->new
145             ( { notes => $system } );
146 0         0 return $parsers{$system} = $p;
147             }
148             elsif ( $system ) {
149 206         2192 my $p = ChordPro::Chords::Parser::Common->new;
150 206         786 $p->{system} = $system;
151 206         1109 return $parsers{$system} = $p;
152             }
153             elsif ( $nofallback ) {
154 0         0 return;
155             };
156              
157 0         0 Carp::confess("No parser for $system, falling back to default\n");
158 0   0     0 return $parsers{common} //= $self->default;
159             }
160              
161 4     4 0 11 sub have_parser ( $self, $system ) {
  4         8  
  4         9  
  4         6  
162 4         21 exists $parsers{$system};
163             }
164              
165             # The list of instantiated parsers.
166 0     0 0 0 sub parsers ( $self ) {
  0         0  
  0         0  
167 0         0 \%parsers;
168             }
169              
170 208     208 0 515 sub reset_parsers ( $self, @which ) {
  208         501  
  208         495  
  208         454  
171 208 50       1460 @which = keys(%parsers) unless @which;
172 208         2331 delete $parsers{$_} for @which;
173             }
174              
175             # The number of intervals for this note system.
176 371     371 0 650 sub intervals ( $self ) {
  371         618  
  371         542  
177 371         1051 $self->{intervals};
178             }
179              
180 0     0 0 0 sub simplify ( $self ) {
  0         0  
  0         0  
181 0         0 ref($self);
182             }
183              
184             ################ Parsing Common notated chords ################
185              
186             package ChordPro::Chords::Parser::Common;
187              
188             our @ISA = qw( ChordPro::Chords::Parser );
189              
190 79     79   51814 use Storable qw(dclone);
  79         322285  
  79         29347  
191              
192 208     208   610 sub new ( $pkg, $cfg = $::config ) {
  208         528  
  208         586  
  208         495  
193 208         1113 my $self = bless { chord_cache => {} } => $pkg;
194 208         731 bless $self => 'ChordPro::Chords::Parser::Common';
195 208         664 my $notes = $cfg->{notes};
196 208         1318 $self->load_notes($cfg);
197 208         1025 $self->{system} = $notes->{system};
198 208         792 $self->{target} = 'ChordPro::Chord::Common';
199 208         851 $self->{movable} = $notes->{movable};
200             warn("Chords: Created parser for ", $self->{system},
201             $cfg->{settings}->{chordnames} eq "relaxed"
202             ? ", relaxed" : "",
203 208 0       1043 "\n") if $::options->{verbose} > 1;
    50          
204 208         1127 return $parsers{$self->{system}} = $self;
205             }
206              
207 63791     63791   86571 sub parse_chord ( $self, $chord ) {
  63791         89596  
  63791         100293  
  63791         85304  
208              
209             my $info = { system => $self->{system},
210 63791         203465 parser => $self,
211             name => $chord };
212              
213 63791         106651 my $bass = "";
214 63791 100       339581 if ( $chord =~ m;^(.*)/($self->{n_pat})$; ) {
215 149         500 $chord = $1;
216 149         328 $bass = $2;
217             }
218              
219 63791         107999 my %plus;
220              
221             # Match chord.
222 63791 50 33     744748 if ( $chord eq "" && $bass ne "" ) {
    100 66        
    100 100        
    100 66        
223 0         0 $info->{rootless} = 1;
224             }
225             elsif ( $chord =~ /^$self->{c_pat}$/ ) {
226 79     79   41522 %plus = %+;
  79         29929  
  79         182506  
  39844         586679  
227 39844         149434 $info->{root} = $plus{root};
228             }
229             # Retry with relaxed pattern if requested.
230             elsif ( $self->{c_rpat}
231             && $::config->{settings}->{chordnames} eq "relaxed"
232             && $chord =~ /^$self->{c_rpat}$/ ) {
233 22         408 %plus = %+; # keep it outer
234 22 50       134 return unless $info->{root} = $plus{root};
235             }
236             # Not a chord. Try note name.
237             elsif ( $::config->{settings}->{notenames}
238             && ucfirst($chord) =~ /^$self->{n_pat}$/ ) {
239 6         23 $info->{root} = $chord;
240 6         17 $info->{isnote} = 1;
241             }
242             # Nope.
243             else {
244 23919         444119 return;
245             }
246              
247 39872         85687 bless $info => $self->{target};
248              
249 39872   100     96422 my $q = $plus{qual} // "";
250 39872         75908 $info->{qual} = $q;
251 39872 100 100     136146 $q = "-" if $q eq "m" || $q eq "min";
252 39872 100       79411 $q = "+" if $q eq "aug";
253 39872 100       74143 $q = "0" if $q eq "dim";
254 39872 100       74004 $q = "0" if $q eq "o";
255 39872         70108 $info->{qual_canon} = $q;
256              
257 39872   100     85463 my $x = $plus{ext} // "";
258 39872 100       84211 if ( !$info->{qual} ) {
259 19749 100       40040 if ( $x eq "maj" ) {
260 48         93 $x = "";
261             }
262             }
263 39872         75115 $info->{ext} = $x;
264 39872 100       77619 $x = "sus4" if $x eq "sus";
265 39872         96812 $info->{ext_canon} = $x;
266              
267             my $ordmod = sub {
268 40021     40021   80206 my ( $pfx ) = @_;
269 40021         77422 my $r = $info->{$pfx};
270 40021 100       80740 $r = ucfirst($r) if $info->{isnote};
271 40021 100       104109 if ( defined $self->{ns_tbl}->{$r} ) {
    50          
272 29204         81001 $info->{"${pfx}_ord"} = $self->{ns_tbl}->{$r};
273 29204 100       82762 $info->{"${pfx}_mod"} = defined $self->{nf_tbl}->{$r} ? 0 : 1;
274 29204         95612 $info->{"${pfx}_canon"} = $self->{ns_canon}->[$self->{ns_tbl}->{$r}];
275             }
276             elsif ( defined $self->{nf_tbl}->{$r} ) {
277 10817         30615 $info->{"${pfx}_ord"} = $self->{nf_tbl}->{$r};
278 10817         23466 $info->{"${pfx}_mod"} = -1;
279 10817         35296 $info->{"${pfx}_canon"} = $self->{nf_canon}->[$self->{nf_tbl}->{$r}];
280             }
281             else {
282 0         0 Carp::croak("CANT HAPPEN ($r)");
283 0         0 return;
284             }
285             #### $info->{isflat} = $info->{"${pfx}_mod"} < 0;
286 39872         184786 };
287              
288 39872 50       105778 $ordmod->("root") unless $info->is_rootless;
289              
290 39872 50       134717 cluck("BLESS info for $chord into ", $self->{target}, "\n")
291             unless ref($info) =~ /ChordPro::Chord::/;
292              
293 39872 100       103302 if ( $info->{bass} = $bass ) {
294 149 50       2679 if ( $bass =~ /^$self->{n_pat}$/ ) {
295 149         446 $ordmod->("bass");
296 149 50       400 if ( $info->is_rootless ) {
297 0         0 for ( qw( ord mod canon ) ) {
298 0         0 $info->{"root_$_"} = $info->{"bass_$_"};
299             }
300             }
301             }
302             }
303              
304 39872 50       174824 if ( $::config->{settings}->{'chords-canonical'} ) {
305 0         0 my $t = $info->{name};
306 0         0 $info->{name_canon} = $info->canonical;
307             warn("Parsing chord: \"$chord\" canon \"", $info->canonical, "\"\n" )
308 0 0 0     0 if $info->{name_canon} ne $t and $::config->{debug}->{chords};
309             }
310              
311 39872         591419 return $info;
312             }
313              
314             ################ Chords ################
315              
316             # The following additions are recognized for major chords.
317              
318             my $additions_maj =
319             {
320             map { $_ => $_ }
321             "",
322             "11",
323             "13",
324             "13#11",
325             "13#9",
326             "13b9",
327             "2",
328             "3",
329             "4",
330             "5",
331             "6",
332             "69",
333             "6add9",
334             "7",
335             "711",
336             "7add11",
337             "7#11",
338             "7#5",
339             "7#9",
340             "7#9#11",
341             "7#9#5",
342             "7#9b5",
343             "7alt",
344             "7b13",
345             "7b13sus",
346             "7b5",
347             "7b9",
348             "7b9#11",
349             "7b9#5",
350             "7b9#9",
351             "7b9b13",
352             "7b9b5",
353             "7b9sus",
354             "7-13",
355             "7-13sus",
356             "7-5",
357             "7\\+5",
358             "7-9",
359             "7-9#11",
360             "7-9#5",
361             "7-9#9",
362             "7-9-13",
363             "7-9-5",
364             "7-9sus",
365             "7sus",
366             "7susadd3",
367             "7\\+", # REGEXP!!!
368             "9",
369             "9\\+", # REGEXP!!!
370             "911",
371             "9#11",
372             "9#5",
373             "9b5",
374             "9-5",
375             "9sus",
376             "9add6",
377             ( map { ( "maj$_", "^$_" ) }
378             "",
379             "13",
380             "7",
381             "711",
382             "7#11",
383             "7#5",
384             ( map { "7sus$_" } "", "2", "4" ),
385             "9",
386             "911",
387             "9#11",
388             ),
389             "alt",
390             "h",
391             "h7",
392             "h9",
393             ( map { "add$_" } "2", "4", "9", "11" ),
394             ( map { "sus$_" } "", "2", "4", "9" ),
395             ( map { "6sus$_" } "", "2", "4" ),
396             ( map { "7sus$_" } "", "2", "4" ),
397             ( map { "13sus$_" } "", "2", "4" ),
398             };
399              
400             # The following additions are recognized for minor chords.
401              
402             my $additions_min =
403             {
404             map { $_ => $_ }
405             "",
406             "#5",
407             "11",
408             "13",
409             "6",
410             "69",
411             "7b5",
412             "7-5",
413             ( map { ( "$_", "maj$_", "^$_" ) }
414             "7",
415             "9",
416             ),
417             "9maj7", "9^7",
418             "add9",
419             "b6",
420             "#7",
421             ( map { "sus$_" } "", "4", "9" ),
422             ( map { "7sus$_" } "", "4" ),
423             };
424              
425             # The following additions are recognized for augmented chords.
426              
427             my $additions_aug =
428             {
429             map { $_ => $_ }
430             "",
431             "7",
432             };
433              
434             # The following additions are recognized for diminished chords.
435              
436             my $additions_dim =
437             {
438             map { $_ => $_ }
439             "",
440             "7",
441             };
442              
443             # Build tables and patterns from the "notes" element from the
444             # configuration.
445              
446 208     208   453 sub load_notes ( $self, $init ) {
  208         473  
  208         499  
  208         505  
447 208   50     576 my $cfg = { %{$::config//{}}, %{$init//{}} };
  208   50     2143  
  208         3743  
448 208         1225 my $n = $cfg->{notes};
449 208 50       956 Carp::confess("No notes?") unless $n->{system};
450 208         607 my ( @ns_canon, %ns_tbl, @nf_canon, %nf_tbl );
451              
452 208         549 my $rix = 0;
453 208         447 foreach my $root ( @{ $n->{sharp} } ) {
  208         900  
454 2496 100       7599 if ( UNIVERSAL::isa($root, 'ARRAY') ) {
455 1025         2759 $ns_canon[$rix] = $root->[0];
456 1025         6138 $ns_tbl{$_} = $rix foreach @$root;
457             }
458             else {
459 1471         3151 $ns_canon[$rix] = $root;
460 1471         3656 $ns_tbl{$root} = $rix;
461             }
462 2496         4062 $rix++;
463             }
464 208         889 $rix = 0;
465 208         554 foreach my $root ( @{ $n->{flat} } ) {
  208         914  
466 2496 100       6046 if ( UNIVERSAL::isa($root, 'ARRAY') ) {
467 1034         2765 $nf_canon[$rix] = $root->[0];
468 1034         6760 $nf_tbl{$_} = $rix foreach @$root;
469             }
470             else {
471 1462         2847 $nf_canon[$rix] = $root;
472 1462         2687 $nf_tbl{$root} = $rix;
473             }
474 2496         4053 $rix++;
475             }
476              
477             # Pattern to match note names.
478 208         969 my $n_pat = '(?:' ;
479 208         626 my @n;
480 208         1668 foreach ( keys %ns_tbl ) {
481 4521         7732 push( @n, $_ );
482             }
483 208         4169 foreach ( sort keys %nf_tbl ) {
484 4930 100       9560 next if $ns_tbl{$_};
485 3682         6450 push( @n, $_ );
486             }
487              
488 208         2594 $n_pat = '(?:' . join( '|', sort { length($b) <=> length($a) } @n ) . ')';
  32970         48163  
489              
490             # Pattern to match chord names.
491 208         675 my $c_pat;
492             # Accept root, qual, and only known extensions.
493 208         928 $c_pat = "(?" . $n_pat . ")";
494 208         857 $c_pat .= "(?:";
495 208         3320 $c_pat .= "(?-|min|m(?!aj))".
496             "(?" . join("|", keys(%$additions_min)) . ")|";
497 208         1921 $c_pat .= "(?\\+|aug)".
498             "(?" . join("|", keys(%$additions_aug)) . ")|";
499 208         1241 $c_pat .= "(?0|o|dim|h)".
500             "(?" . join("|", keys(%$additions_dim)) . ")|";
501 208         7737 $c_pat .= "(?)".
502             "(?" . join("|", keys(%$additions_maj)) . ")";
503 208         1401 $c_pat .= ")";
504 208         84789 $c_pat = qr/$c_pat/;
505 208         18757 $n_pat = qr/$n_pat/;
506              
507             # In relaxed form, we accept anything for extension.
508 208         1942 my $c_rpat = "(?" . $n_pat . ")";
509 208         883 $c_rpat .= "(?:(?-|min|m(?!aj)|\\+|aug|0|o|dim|)(?.*))";
510 208         22583 $c_rpat = qr/$c_rpat/;
511              
512             # Store in the object.
513 208         2459 $self->{n_pat} = $n_pat;
514 208         784 $self->{c_pat} = $c_pat;
515 208         632 $self->{c_rpat} = $c_rpat;
516 208         803 $self->{ns_tbl} = \%ns_tbl;
517 208         646 $self->{nf_tbl} = \%nf_tbl;
518 208         739 $self->{ns_canon} = \@ns_canon;
519 208         828 $self->{nf_canon} = \@nf_canon;
520 208         2384 $self->{intervals} = @ns_canon;
521             }
522              
523 882     882   1308 sub root_canon ( $self, $root, $sharp = 0, $minor = 0 ) {
  882         1232  
  882         1243  
  882         1402  
  882         1314  
  882         1213  
524 882 100       3369 ( $sharp ? $self->{ns_canon} : $self->{nf_canon} )->[$root];
525             }
526              
527             # Has chord diagrams.
528 181     181   348 sub has_diagrams ( $self ) { !$self->{movable} }
  181         339  
  181         278  
  181         676  
529              
530             # Movable notes system.
531 8     8   14 sub movable ( $self ) { $self->{movable} }
  8         15  
  8         9  
  8         36  
532              
533             ################ Parsing Nashville notated chords ################
534              
535             package ChordPro::Chords::Parser::Nashville;
536              
537             our @ISA = qw(ChordPro::Chords::Parser::Common);
538              
539 79     79   760 use Storable qw(dclone);
  79         176  
  79         27990  
540              
541             sub new {
542 2     2   8 my ( $pkg, $init ) = @_;
543 2         9 my $self = bless { chord_cache => {} } => $pkg;
544 2         22 $self->{system} = "nashville";
545 2         5 $self->{target} = 'ChordPro::Chord::Nashville';
546             warn("Chords: Created parser for ", $self->{system}, "\n")
547 2 50 33     11 if $::options->{verbose} && $::options->{verbose} > 1;
548 2         17 return $parsers{$self->{system}} = $self;
549             }
550              
551             my $n_pat = qr/(?[b#]?)(?[1-7])/;
552              
553             my %nmap = ( 1 => 0, 2 => 2, 3 => 4, 4 => 5, 5 => 7, 6 => 9, 7 => 11 );
554             my @nmap = ( 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6, 7, 1 );
555              
556             sub parse_chord {
557 408 50   408   1628 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
558 408         923 my ( $self, $chord ) = @_;
559              
560 79     79   644 $chord =~ tr/\x{266d}\x{266f}\x{0394}\x{f8}\x{b0}/b#^h0/;
  79         186  
  79         1430  
  408         1286  
561              
562 408         698 my $bass = "";
563 408 50       1417 if ( $chord =~ m;^(.*)/(.*); ) {
564 0         0 $chord = $1;
565 0         0 $bass = $2;
566             }
567              
568 408 50       4458 return unless $chord =~ /^$n_pat(?-|\+|0|o|aug|m(?!aj)|dim)?(?.*)$/;
569              
570             my $info = { system => "nashville",
571             parser => $self,
572             name => $_[1],
573             root => $+{root},
574 408         4104 };
575 408         1385 bless $info => $self->{target};
576              
577 408   100     2013 my $q = $+{qual} // "";
578 408         1059 $info->{qual} = $q;
579 408 50       1008 $q = "-" if $q eq "m";
580 408 50       797 $q = "+" if $q eq "aug";
581 408 50       734 $q = "0" if $q eq "dim";
582 408 50       713 $q = "0" if $q eq "o";
583 408         811 $info->{qual_canon} = $q;
584              
585 408   50     1551 my $x = $+{ext} // "";
586 408         920 $info->{ext} = $x;
587 408 50       781 $x = "sus4" if $x eq "sus";
588 408         1035 $info->{ext_canon} = $x;
589              
590             my $ordmod = sub {
591 408     408   824 my ( $pfx ) = @_;
592 408         902 my $r = 0 + $info->{$pfx};
593 408         1343 $info->{"${pfx}_ord"} = $nmap{$r};
594 408 100       2612 if ( $+{shift} eq "#" ) {
    100          
595 120         333 $info->{"${pfx}_mod"} = 1;
596 120         266 $info->{"${pfx}_ord"}++;
597             $info->{"${pfx}_ord"} = 0
598 120 50       313 if $info->{"${pfx}_ord"} >= 12;
599             }
600             elsif ( $+{shift} eq "b" ) {
601 120         313 $info->{"${pfx}_mod"} = -1;
602 120         241 $info->{"${pfx}_ord"}--;
603             $info->{"${pfx}_ord"} += 12
604 120 50       343 if $info->{"${pfx}_ord"} < 0;
605             }
606             else {
607 168         465 $info->{"${pfx}_mod"} = 0;
608             }
609 408         1402 $info->{"${pfx}_canon"} = $r;
610 408         2219 };
611              
612 408         1118 $ordmod->("root");
613              
614 408         868 $info->{bass} = $bass;
615 408 50       4076 return $info unless $bass;
616 0 0       0 return unless $bass =~ /^$n_pat$/;
617 0         0 $ordmod->("bass");
618              
619 0         0 return $info;
620             }
621              
622 0     0   0 sub load_notes { Carp::confess("OOPS") }
623              
624             sub root_canon {
625 12 50   12   41 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
626 12         30 my ( $self, $root, $sharp ) = @_;
627 79     79   480893 no warnings 'qw';
  79         234  
  79         17511  
628 12 50       45 $sharp
629             ? qw( 1 #1 2 #2 3 4 #4 5 #5 6 #6 7 )[$root]
630             : qw( 1 b2 2 b3 3 4 b5 5 b6 6 b7 7 )[$root]
631             }
632              
633             # Has chord diagrams.
634             sub has_diagrams {
635 2 50   2   11 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
636 2         7 0;
637             }
638              
639             # Movable notes system.
640             sub movable {
641 2 50   2   11 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
642 2         10 1;
643             }
644              
645             ################ Parsing Roman notated chords ################
646              
647             package ChordPro::Chords::Parser::Roman;
648              
649 79     79   699 use ChordPro;
  79         213  
  79         27313  
650              
651             our @ISA = qw(ChordPro::Chords::Parser::Common);
652              
653             sub new {
654 2     2   10 my ( $pkg, $init ) = @_;
655 2         10 my $self = bless { chord_cache => {} } => $pkg;
656 2         34 $self->{system} = "roman";
657 2         6 $self->{target} = 'ChordPro::Chord::Roman';
658             warn("Chords: Created parser for ", $self->{system}, "\n")
659 2 50 33     23 if $::options->{verbose} && $::options->{verbose} > 1;
660 2         17 return $parsers{$self->{system}} = $self;
661             }
662              
663             my $r_pat = qr/(?[b#]?)(?(?i)iii|ii|iv|i|viii|vii|vi|v)/;
664              
665             my %rmap = ( I => 0, II => 2, III => 4, IV => 5, V => 7, VI => 9, VII => 11 );
666              
667             sub parse_chord {
668 612 50   612   2397 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
669 612         1483 my ( $self, $chord ) = @_;
670              
671 612         1874 $chord =~ tr/\x{266d}\x{266f}\x{0394}\x{f8}\x{b0}/b#^h0/;
672              
673 612         1095 my $bass = "";
674 612 50       2028 if ( $chord =~ m;^(.*)/(.*); ) {
675 0         0 $chord = $1;
676 0         0 $bass = $2;
677             }
678              
679 612 50       6773 return unless $chord =~ /^$r_pat(?\+|0|o|aug|dim|h)?(?.*)$/;
680 612         5573 my $r = $+{shift}.$+{root};
681              
682 612         3677 my $info = { system => "roman",
683             parser => $self,
684             name => $_[1],
685             root => $r };
686 612         1543 bless $info => $self->{target};
687              
688 612   100     2838 my $q = $+{qual} // "";
689 612         1667 $info->{qual} = $q;
690 612 100       1915 $q = "-" if $r eq lc($r);
691 612 50       1397 $q = "+" if $q eq "aug";
692 612 50       1201 $q = "0" if $q eq "dim";
693 612 50       1187 $q = "0" if $q eq "o";
694 612         1111 $info->{qual_canon} = $q;
695              
696 612   50     2309 my $x = $+{ext} // "";
697 612         1614 $info->{ext} = $x;
698 612 50       1246 $x = "sus4" if $x eq "sus";
699 612 50       1065 $x = "^7" if $x eq "7+";
700 612         1736 $info->{ext_canon} = $x;
701              
702             my $ordmod = sub {
703 612     612   1259 my ( $pfx ) = @_;
704 612         1198 my $r = $info->{$pfx};
705 612         2567 $info->{"${pfx}_ord"} = $rmap{uc $r};
706 612 100       3577 if ( $+{shift} eq "#" ) {
    100          
707 180         444 $info->{"${pfx}_mod"} = 1;
708 180         391 $info->{"${pfx}_ord"}++;
709             $info->{"${pfx}_ord"} = 0
710 180 50       497 if $info->{"${pfx}_ord"} >= 12;
711             }
712             elsif ( $+{shift} eq "b" ) {
713 180         485 $info->{"${pfx}_mod"} = -1;
714 180         418 $info->{"${pfx}_ord"}--;
715             $info->{"${pfx}_ord"} += 12
716 180 50       660 if $info->{"${pfx}_ord"} < 0;
717             }
718             else {
719 252         676 $info->{"${pfx}_mod"} = 0;
720             }
721 612         1985 $info->{"${pfx}_canon"} = $r;
722 612         3397 };
723              
724 612         1703 $ordmod->("root");
725              
726 612         1361 $info->{bass} = uc $bass;
727 612 50       6276 return $info unless $bass;
728 0 0       0 return unless $bass =~ /^$r_pat$/;
729 0         0 $ordmod->("bass");
730              
731 0         0 return $info;
732             }
733              
734 0     0   0 sub load_notes { Carp::confess("OOPS") }
735              
736             sub root_canon {
737 12 50   12   42 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
738 12         27 my ( $self, $root, $sharp, $minor ) = @_;
739 12 50       24 return lc( $self->root_canon( $root, $sharp ) ) if $minor;
740 79     79   73423 no warnings 'qw';
  79         230  
  79         17606  
741 12 50       49 $sharp
742             ? qw( I #I II #II III IV #IV V #V VI #VI VII )[$root]
743             : qw( I bII II bIII III IV bV V bVI VI bVII VII )[$root]
744             }
745              
746             # Has chord diagrams.
747             sub has_diagrams {
748 2 50   2   12 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
749 2         6 0;
750             }
751              
752             # Movable notes system.
753             sub movable {
754 2 50   2   12 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
755 2         9 1;
756             }
757              
758             ################ Chord objects: Common ################
759              
760             package ChordPro::Chord::Base;
761              
762 79     79   667 use Storable qw(dclone);
  79         242  
  79         121815  
763              
764             sub new {
765 13508     13508   31573 my ( $pkg, $data ) = @_;
766 13508   66     39761 $pkg = ref($pkg) || $pkg;
767 13508         86913 bless { %$data } => $pkg;
768             }
769              
770             sub clone {
771 376 50   376   1482 Carp::confess("NMC") unless UNIVERSAL::isa($_[0],__PACKAGE__);
772 376         781 my ( $self ) = shift;
773 376         37642 dclone($self);
774             }
775              
776 40710     40710   113186 sub is_note { $_[0]->{isnote} };
777 0     0   0 sub is_flat { $_[0]->{isflat} };
778 942     942   2590 sub is_keyboard { $_[0]->{iskeyboard} };
779              
780             sub is_nc {
781 942     942   1887 my ( $self ) = @_;
782             # Keyboard...
783 942 50 0     2017 return 1 if $self->is_keyboard && !@{ $self->kbkeys // [1] };
  0   33     0  
784             # Strings...
785 942 100 100     1545 return unless @{ $self->frets // [] };
  942         2068  
786 780         1265 for ( @{ $self->frets } ) {
  780         1359  
787 1621 100       5320 return unless $_ < 0;
788             }
789 14         66 return 1; # all -1 => N.C.
790             }
791              
792             # Can be transposed/transcoded.
793             sub is_xpxc {
794 1339 100 66 1339   6725 defined($_[0]->{root}) || defined($_[0]->{bass}) || $_[0]->is_nc;
795             }
796              
797             sub has_diagram {
798 814     814   1538 my ( $self ) = @_;
799             ( $::config->{instrument}->{type} eq "keyboard" )
800 0   0     0 ? @{ $self->kbkeys // []}
801 814 50 50     1812 : @{ $self->frets // []};
  814         1489  
802             }
803              
804             # For convenience.
805 170244     170244   583775 sub is_chord { defined $_[0]->{root_ord} }
806 79090     79090   250684 sub is_rootless { $_[0]->{rootless} }
807 590     590   1962 sub is_annotation { 0 }
808 0     0   0 sub is_movable { $_[0]->{movable} }
809              
810             # Common accessors.
811             sub name {
812 6347     6347   12128 my ( $self, $np ) = @_;
813             Carp::confess("Double parens")
814 6347 50 33     14139 if $self->{parens} && $self->{name} =~ /^\(.*\)$/;
815 6347 50 33     41793 return $self->{name} if $np || !$self->{parens};
816 0         0 "(" . $self->{name} . ")";
817             }
818              
819 0     0   0 sub canon { $_[0]->{name_canon} }
820 4     4   25 sub root { $_[0]->{root} }
821 4     4   28 sub qual { $_[0]->{qual} }
822 4     4   24 sub ext { $_[0]->{ext} }
823 4     4   141 sub bass { $_[0]->{bass} }
824 3     3   2029 sub base { $_[0]->{base} }
825 2537     2537   9556 sub frets { $_[0]->{frets} }
826 1     1   10 sub fingers { $_[0]->{fingers} }
827 0     0   0 sub display { $_[0]->{display} }
828 0     0   0 sub format { $_[0]->{format} }
829 7     7   57 sub diagram { $_[0]->{diagram} }
830 71     71   201 sub parser { $_[0]->{parser} }
831              
832             sub strings {
833 0     0   0 $_[0]->{parser}->{intervals};
834             }
835              
836             sub kbkeys {
837 1 50 33 1   7 return $_[0]->{keys} if $_[0]->{keys} && @{$_[0]->{keys}};
  1         11  
838 0         0 $_[0]->{keys} = ChordPro::Chords::get_keys($_[0]);
839             }
840              
841 711     711   1058 sub flat_copy ( $self, $ret, $o, $pfx = "" ) {
  711         1071  
  711         1124  
  711         1038  
  711         1181  
  711         955  
842 711         3009 while ( my ( $k, $v ) = each %$o ) {
843 14226 100       24328 if ( $k eq "orig" ) {
844 10         60 $self->flat_copy( $ret, $v, "$k.$pfx");
845             }
846             else {
847 14216         48189 $ret->{"$pfx$k"} = $v;
848             }
849             }
850 711         1400 $ret;
851             }
852              
853 22     22   34 sub fix_musicsyms ( $self, $str ) {
  22         38  
  22         30  
  22         36  
854              
855 79     79   722 use ChordPro::Utils qw( splitmarkup );
  79         192  
  79         49202  
856              
857 22 50       52 return $str unless $::config->{settings}->{truesf};
858              
859 22         66 my @c = splitmarkup($str);
860 22         45 my $res = '';
861 22 100       70 push( @c, '' ) if @c % 2;
862 22         32 my $did = 0; # TODO: not for roman
863 22         46 while ( @c ) {
864 37         66 $_ = shift(@c);
865 37 100       90 if ( $did ) {
866 15         35 s/b/♭/g;
867             }
868             else {
869 22         81 s/(?<=[[:alnum:]])b/♭/g;
870 22         41 $did++;
871             }
872 37         86 s/#/♯/g;
873 37         114 $res .= $_ . shift(@c);
874             }
875 22         139 $res;
876             }
877              
878 0     0   0 sub simplify ( $self ) {
  0         0  
  0         0  
879 0         0 my $c = {};
880 0         0 for ( keys %$self ) {
881 0 0       0 next unless defined $self->{$_};
882 0 0       0 next if defined $c->{$_};
883 0 0 0     0 if ( UNIVERSAL::can( $self->{$_}, "simplify" ) ) {
    0          
884 0         0 $c->{$_} = $self->{$_}->simplify;
885             }
886 0         0 elsif ( ref($self->{$_}) eq 'ARRAY' && @{$self->{$_}} ) {
887 0         0 $c->{$_} = "[ " . join(" ", @{$self->{$_}}) . " ]";
  0         0  
888             }
889             else {
890 0         0 $c->{$_} = $self->{$_};
891             }
892             }
893 0         0 $c;
894             }
895              
896 0     0   0 sub dump ( $self ) {
  0         0  
  0         0  
897 0         0 ::dump($self->simplify);
898             }
899              
900             package ChordPro::Chord::Common;
901              
902             our @ISA = qw( ChordPro::Chord::Base );
903              
904 79     79   46704 use String::Interpolate::Named;
  79         115806  
  79         104674  
905              
906             # Show reconstructs the chord from its constituents.
907             # Result is canonical.
908             sub show {
909 0     0   0 Carp::croak("call canonical instead of show");
910             }
911              
912 515     515   764 sub canonical ( $self ) {
  515         755  
  515         691  
913 515         780 my $res;
914              
915             $res =
916             $self->is_rootless
917             ? ""
918             : $self->is_chord
919             ? $self->{parser}->root_canon( $self->{root_ord},
920             $self->{root_mod} >= 0,
921             $self->{qual} eq '-',
922             # !$self->is_flat ???
923             ) . $self->{qual} . $self->{ext}
924 515 50       1102 : $self->{name};
    50          
925              
926 515 100       1312 if ( $self->is_note ) {
927 4         19 return lcfirst($res);
928             }
929 511 100 66     1303 if ( $self->{bass} && $self->{bass} ne "" ) {
930             $res .= "/" .
931 3 50       14 ($self->{system} eq "roman" ? lc($self->{bass}) : $self->{bass});
932             }
933 511         1371 return $res;
934             }
935              
936             # Returns a representation indepent of notation system.
937 38554     38554   59171 sub agnostic ( $self ) {
  38554         59240  
  38554         52454  
938 38554 100 66     72855 return if $self->is_rootless || $self->is_note;
939             join( " ", "",
940             $self->{root_ord},
941             $self->{root_mod},
942             $self->{qual_canon},
943             $self->{ext_canon},
944 38548   100     215817 $self->{bass_ord} // () );
945             }
946              
947 382     382   661 sub transpose ( $self, $xpose, $dir = 0 ) {
  382         590  
  382         599  
  382         608  
  382         538  
948 382 100       870 return $self unless $xpose;
949 370 100       859 return $self unless $self->is_chord;
950 368   33     897 $dir //= $xpose <=> 0;
951              
952 368         893 my $info = $self->clone;
953 368         272621 my $p = $self->{parser};
954              
955 368 50       1378 unless ( $self->{rootless} ) {
956 368         1371 $info->{root_ord} = ( $self->{root_ord} + $xpose ) % $p->intervals;
957             $info->{root_canon} = $info->{root} =
958             $p->root_canon( $info->{root_ord},
959             $dir > 0,
960 368         1304 $info->{qual_canon} eq "-" );
961             }
962 368 50 66     1183 if ( $self->{bass} && $self->{bass} ne "" && $self->{bass} !~ /^\d+$/ ) {
      66        
963 3         11 $info->{bass_ord} = ( $self->{bass_ord} + $xpose ) % $p->intervals;
964             $info->{bass_canon} = $info->{bass} =
965 3         11 $p->root_canon( $info->{bass_ord}, $xpose > 0 );
966 3         8 $info->{bass_mod} = $dir;
967             }
968 368         609 $info->{root_mod} = $dir;
969 368         951 $info->{name} = $info->{name_canon} = $info->canonical;
970              
971 368         1840 delete $info->{$_} for qw( copy base frets fingers keys display );
972              
973 368         1297 return $info;
974             }
975              
976 143     143   243 sub transcode ( $self, $xcode, $key_ord = 0 ) {
  143         224  
  143         245  
  143         245  
  143         186  
977 143 100       462 return $self unless $xcode;
978 20 50       58 return $self unless $self->is_chord;
979 20 50       102 return $self if $self->{system} eq $xcode;
980 20         2013 my $info = $self->dclone;
981             #warn("_>_XCODE = $xcode, _SELF = $self->{system}, CHORD = $info->{name}");
982 20         14771 $info->{system} = $xcode;
983 20         112 my $p = $self->{parser}->get_parser($xcode);
984 20 50       69 die("OOPS ", $p->{system}, " $xcode") unless $p->{system} eq $xcode;
985 20         406 $info->{parser} = $p;
986 20 100 100     102 $info->{root_ord} -= $key_ord if $key_ord && $p->movable;
987             # $info->{$_} = $p->{$_} for qw( ns_tbl nf_tbl ns_canon nf_canon );
988             $info->{root_canon} = $info->{root} =
989             $p->root_canon( $info->{root_ord},
990             $info->{root_mod} >= 0,
991 20         158 $info->{qual_canon} eq "-" );
992 20 50 66     72 if ( $p->{system} eq "roman" && $info->{qual_canon} eq "-" ) {
993             # Minor quality is in the root name.
994 0         0 $info->{qual_canon} = $info->{qual} = "";
995             }
996 20 50 33     62 if ( $self->{bass} && $self->{bass} ne "" ) {
997 0 0 0     0 $info->{bass_ord} -= $key_ord if $key_ord && $p->movable;
998             $info->{bass_canon} = $info->{bass} =
999 0         0 $p->root_canon( $info->{bass_ord}, $info->{bass_mod} >= 0 );
1000             }
1001 20         57 $info->{name} = $info->{name_canon} = $info->canonical;
1002 20         41 $info->{system} = $p->{system};
1003 20         52 bless $info => $p->{target};
1004             # ::dump($info);
1005             #warn("_<_XCODE = $xcode, CHORD = ", $info->canonical);
1006 20         120 return $info;
1007             }
1008              
1009 701     701   1132 sub chord_display ( $self ) {
  701         1128  
  701         956  
1010              
1011             # $self->dump;
1012              
1013 701         1603 my $res = $self->name;
1014 701         1559 my $args = {};
1015 701   33     3492 $self->flat_copy( $args, $self->{display} // $self );
1016              
1017 701         2298 for my $fmt ( $::config->{settings}->{"chord-format"},
1018             $self->{format},
1019             $self->{chordformat} ) {
1020 2103 100       486442 next unless $fmt;
1021 713 100       1799 $args->{root} = lc($args->{root}) if $self->is_note;
1022 713         1689 $args->{formatted} = $res;
1023 713         2873 $res = interpolate( { args => $args }, $fmt );
1024             }
1025              
1026             # Substitute musical symbols if wanted.
1027 701 100       5370 return $::config->{settings}->{truesf} ? $self->fix_musicsyms($res) : $res;
1028             }
1029              
1030             ################ Chord objects: Nashville ################
1031              
1032             package ChordPro::Chord::Nashville;
1033              
1034             our @ISA = 'ChordPro::Chord::Base';
1035              
1036 79     79   794 use String::Interpolate::Named;
  79         223  
  79         39970  
1037              
1038 0     0   0 sub transpose ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1039              
1040             sub show {
1041 0     0   0 Carp::croak("call canonical instead of show");
1042             }
1043              
1044 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1045 0         0 my $res = $self->{root_canon} . $self->{qual} . $self->{ext};
1046 0 0 0     0 if ( $self->{bass} && $self->{bass} ne "" ) {
1047 0         0 $res .= "/" . lc($self->{bass});
1048             }
1049 0         0 return $res;
1050             }
1051              
1052 4     4   7 sub chord_display ( $self ) {
  4         5  
  4         7  
1053 4 50       11 if ( $self->{format} ) {
1054             ####TODO
1055 0   0     0 my $fmt = $self->{format} || $::config->{settings}->{"chord-format"};
1056 0 0       0 if ( $fmt ) {
1057 0         0 my $args = {};
1058 0         0 $self->flat_copy( $args, $self );
1059 0         0 return interpolate( { args => $args }, $fmt );
1060             }
1061             }
1062              
1063             my $res = $self->{root_canon} .
1064 4         14 "" . $self->{qual} . $self->{ext} . "";
1065 4 50 33     13 if ( $self->{bass} && $self->{bass} ne "" ) {
1066 0         0 $res .= "/" . lc($self->{bass}) . "";
1067             }
1068 4         14 return $res;
1069             }
1070              
1071             ################ Chord objects: Roman ################
1072              
1073             package ChordPro::Chord::Roman;
1074              
1075             our @ISA = 'ChordPro::Chord::Base';
1076              
1077 79     79   688 use String::Interpolate::Named;
  79         207  
  79         37609  
1078              
1079 0     0   0 sub transpose ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1080              
1081             sub show {
1082 0     0   0 Carp::croak("call canonical instead of show");
1083             }
1084              
1085 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1086 0         0 my $res = $self->{root_canon} . $self->{qual} . $self->{ext};
1087 0 0 0     0 if ( $self->{bass} && $self->{bass} ne "" ) {
1088 0         0 $res .= "/" . lc($self->{bass});
1089             }
1090 0         0 return $res;
1091             }
1092              
1093 4     4   8 sub chord_display ( $self ) {
  4         6  
  4         7  
1094 4 50       10 if ( $self->{format} ) {
1095             ####TODO
1096 0   0     0 my $fmt = $self->{format} || $::config->{settings}->{"chord-format"};
1097 0 0       0 if ( $fmt ) {
1098 0         0 my $args = {};
1099 0         0 $self->flat_copy( $args, $self );
1100 0         0 return interpolate( { args => $args }, $fmt );
1101             }
1102 0         0 return $self->canonical;
1103             }
1104              
1105 4         10 my $res = $self->{root_canon};
1106             $res .= "" . $self->{qual} . $self->{ext} . ""
1107 4 50       8 if $self->{qual};
1108 4 50 33     12 if ( $self->{bass} && $self->{bass} ne "" ) {
1109 0         0 $res .= "/" . lc($self->{bass}) . "";
1110             }
1111 4         14 return $res;
1112             }
1113              
1114             ################ Chord objects: Annotations ################
1115              
1116             package ChordPro::Chord::Annotation;
1117              
1118 79     79   718 use String::Interpolate::Named;
  79         199  
  79         58617  
1119              
1120             our @ISA = 'ChordPro::Chord::Base';
1121              
1122 0     0   0 sub transpose ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1123 0     0   0 sub transcode ( $self ) { $self }
  0         0  
  0         0  
  0         0  
1124              
1125 0     0   0 sub canonical ( $self ) {
  0         0  
  0         0  
1126 0         0 my $res = $self->{text};
1127 0         0 return $res;
1128             }
1129              
1130 2     2   6 sub chord_display ( $self ) {
  2         4  
  2         4  
1131 2         12 return interpolate( { args => $self }, $self->{text} );
1132             }
1133              
1134             # For convenience.
1135 0     0   0 sub is_chord ( $self ) { 0 };
  0         0  
  0         0  
  0         0  
1136 3     3   9 sub is_annotation ( $self ) { 1 };
  3         9  
  3         4  
  3         20  
1137              
1138             ################ Testing ################
1139              
1140             package main;
1141              
1142             unless ( caller ) {
1143             select(STDERR);
1144             binmode(STDERR, ':utf8');
1145             $::config = { settings => { chordnames => "strict" } };
1146             $::options = { verbose => 2 };
1147             foreach ( @ARGV ) {
1148             if ( $_ eq '-' ) {
1149             $::config = { settings => { chordnames => "relaxed" } };
1150             ChordPro::Chords::Parser->reset_parsers("common");
1151             next;
1152             }
1153             my $p0 = ChordPro::Chords::Parser->default;
1154             my $p1 = ChordPro::Chords::Parser->get_parser("common", 1);
1155             die unless $p0 eq $p1;
1156             my $p2 = ChordPro::Chords::Parser->get_parser("nashville", 1);
1157             my $p3 = ChordPro::Chords::Parser->get_parser("roman", 1);
1158             my $info = $p1->parse($_);
1159             $info = $p2->parse($_) if !$info && $p2;
1160             $info = $p3->parse($_) if !$info && $p3;
1161             print( "$_ => OOPS\n" ), next unless $info;
1162             print( "$_ ($info->{system}) =>" );
1163             print( " ", $info->transcode($_)->canonical, " ($_)" )
1164             for qw( common nashville roman );
1165             print( " '", $info->agnostic, "' (agnostic)\n" );
1166             print( "$_ =>" );
1167             print( " ", $info->transpose($_)->canonical, " ($_)" ) for -2..2;
1168             print( "\n" );
1169             # my $clone = $info->clone;
1170             # delete($clone->{parser});
1171             # print( ::dump($clone), "\n" );
1172             }
1173             }
1174              
1175             1;