7 unshift @d, "# dummy line to make line 1 index 1 in \@d\n";
10 our $count_i_last_l_ok;
15 if ($i == $i_last_l_ok) {
16 confess $i if $count_i_last_l_ok++ > 50;
18 $count_i_last_l_ok = 0;
22 return unless $i < @d;
24 #print STDERR "L $i\n";
30 confess $i unless l_ok $i;
33 our $perlop_text = <<'END'; # c&p from man perlop
34 left terms and list operators (leftward)
38 right ! ~ \ and unary + and -
43 nonassoc named unary operators
44 nonassoc < > <= >= lt gt le ge
45 nonassoc == != <=> eq ne cmp ~~
52 right = += -= *= etc. goto last next redo dump
54 nonassoc list operators (rightward)
59 **= += *= &= &.= <<= &&=
69 foreach (split /\n/, $perlop_text) {
73 s{^(?: left | right | nonassoc ) \s+}{}x;
74 next if m{^terms and list operators};
75 next if m{^named unary};
76 next if m{^list operators};
80 foreach my $op (split /\s+/) {
81 next unless length $op;
82 next if $op =~ m{^\w+$};
87 $perlop_re = '(?: '.(join ' | ', @ops).' )';
88 $perlop_re = qr{$perlop_re}x;
89 #print STDERR "$perlop_re\n";
94 our ($ifilehead, $ifirsthunkhead);
95 our ($ihunkhead, $ihunkend);
96 our ($ichunkstart, $ichunkend);
97 our ($before, $after);
104 # E exact input text (does not contain here doc contents)
105 # P something to print in messages
106 # V value, only for: heredoc string
107 # Q quote characcter, only for: heredoc string
110 if (s{^[\$\@\%]?[_0-9a-zA-Z]+}{}) {
111 push @o, { T => 'ident', E => $&, P => $& };
112 } elsif (s{^\<\<(['"]?)([A-Z_]+)\1}{}) {
113 my ($q,$d) = ($1,$2);
115 push @o, { T => 'heredoc', Q => $q, Delim => $d,
116 E => $&, P => "<<$q$d..." };
119 ( (?: (?! $d) .* \n )* )
121 }{ $1 }x or die "missing end of here doc $d\n";
123 } elsif (s{^ (["'])( (?: [^\\'"]
128 my ($q,$v) = ($1,$2);
129 push @o, { T => 'string', E => $&, P => "$q-string",
131 } elsif (s{^$perlop_re|\;}{}) {
132 push @o, { T => 'op', E => $&, P => $& };
133 } elsif (s/[[{(]//) {
134 push @o, { T => 'bra', E => $&, P => $& };
135 } elsif (s/[]})]//) {
136 push @o, { T => 'ket', E => $&, P => $& };
137 } elsif (s/^( [\$\@\%] )( \{ )//x) {
138 push @o, { T => 'deref', E => $1, P => $1 },
139 { T => 'bra', E => $2, P => $2 };
140 } elsif (s/^ [\$\@\%] [^[^{] //x) {
141 push @o, { T => 'specvar', E => $&, P => $& };
146 die "cannot tokenise \`$&'";
149 # coalesce concatenated strings
153 sub analyse_chunk_core () {
154 die "plain deletion\n" unless defined $after;
155 die "plain insertion\n" unless defined $before;
156 my @xs = semiparse $before;
157 my @ys = semiparse $after;
158 my $next_something = sub {
159 my ($ary,$var,$what) = @_;
160 die "ran out of $what\n" unless @$ary;
164 my $next_x = sub { $next_something->(\@xs, \$x, 'before'); };
165 my $next_y = sub { $next_something->(\@ys, \$y, 'after' ); };
166 my $is_string = sub { $_[0]{T} =~ m/heredoc|string/; };
168 last unless @xs or @ys;
171 next if $x->{E} eq $y->{E};
174 if ($ye eq '__' or $ye eq 'f_') {
176 die "__ on non-string $y->{P}\n" unless $is_string->($y);
177 die "__ on was non-string $y->{P}\n" unless $is_string->($x);
178 if ($y->{Q} ne "'") {
179 die "var subst in new string\n"
180 if $y->{V} =~ m{(?<!\\) [\$\@]};
183 die "__ string changed\n" unless $y->{V} eq $x->{V};
184 die "__ string quote changed\n" unless $y->{Q} eq $x->{Q};
186 $string_changed = $@;
190 die "percent $& in __ ' string\n" if m{\%};
191 die $string_changed if length $string_changed;
196 die "no percent in _f string\n" unless $fmt =~ m{\%};
197 next unless $string_changed;
198 die "f_ old string '-quoted\n" if length $x->{V};
202 my $xl = substr($xs, 0, length($lit));
203 die "exactly mismatch in $lit\n" unless $xl eq $lit;
204 $xs = substr($xs, length($lit));
207 if ($fmt !~ m{\%[^\%]}) {
214 if ($& eq '%%') { $exactly->('%'); next; }
215 elsif ($& ne '%s') { die "unhandled %-subst $&\n"; }
217 die "expected comma, got $y->{P}\n" unless $y->{E} eq ',';
220 die "\@... => not string" unless $is_string->($y);
221 die "\@... => $y->{P}" if $y->{Q} ne '"';
227 if (!$bras and !@ys) {
232 (grep { $y->{E} eq $_ } qw( or xor and not ; : )
240 if ($y->{T} eq 'bra' or $y->{L} eq '?') {
242 } elsif ($y->{T} eq 'ket' or $y->{L} eq ':') {
243 die "too many kets at $y->{L}\n" unless $bras;
250 die "mismatch $x->{P} => $y->{P}\n";
254 sub analyse_chunk () {
256 eval { analyse_chunk_core(); };
257 return unless length $@;
258 if ($@ =~ m{^missing end of here doc (\S+)\n}) {
270 our $last_filehead = -1;
272 sub report_on_hunk () {
273 return unless @report;
274 if ($last_filehead != $ifilehead) {
275 foreach (my $i=$ifilehead; $i<$ifirsthunkhead; $i++) {
278 $last_filehead = $ifilehead;
280 my $dummy_r = { S => (scalar @d)+1, E => (scalar @d)+1 };
282 for (my $i=$ihunkhead; ; $i++) {
284 $r //= shift @report;
286 last if $i < $r->{E};
287 confess unless $r->{Done} == 03;
291 last unless $i<$ihunkend;
305 confess unless $r = $dummy_r;
308 for ($ifilehead = 0; l_ok $ifilehead; $ifilehead++) {
310 $ifirsthunkhead = $ifilehead;
311 while (l_ok $ifirsthunkhead and
312 m{^diff|^index|^---|^\Q+++\E}) {
315 $ihunkhead = $ifirsthunkhead;
316 while (l_ok $ihunkhead) {
317 m{^\@\@} or confess "$ihunkhead $_ ?";
318 my $i = $ihunkhead + 1;
320 if (!l_ok $i or m{^ } or m{^\@\@}) {
321 if (defined $ichunkstart) {
323 eval { analyse_chunk(); 1; };
325 push @report, { M => $@,
329 $ichunkstart = $ichunkend = $before = $after = undef;
334 my $which = $& eq '-' ? \$before : \$after;