7 open DEBUG, ">/dev/null" or die $!;
9 GetOptions("debug|D" => sub { open DEBUG, ">&2" or die $!; }
15 push @{ $debug[$i] }, $s;
19 unshift @d, "# dummy line to make line 1 index 1 in \@d\n";
21 our $i_last_l_ok = -1;
22 our $count_i_last_l_ok;
27 if ($i == $i_last_l_ok) {
28 confess $i if $count_i_last_l_ok++ > 50;
30 $count_i_last_l_ok = 0;
34 return unless $i < @d;
36 #print STDERR "L $i\n";
42 confess $i unless l_ok $i;
45 our $perlop_text = <<'END'; # c&p from man perlop
46 left terms and list operators (leftward)
50 right ! ~ \ and unary + and -
55 nonassoc named unary operators
56 nonassoc < > <= >= lt gt le ge
57 nonassoc == != <=> eq ne cmp ~~
64 right = += -= *= etc. goto last next redo dump
66 nonassoc list operators (rightward)
71 **= += *= &= &.= <<= &&=
81 foreach (split /\n/, $perlop_text) {
85 s{^(?: left | right | nonassoc ) \s+}{}x;
86 next if m{^terms and list operators};
87 next if m{^named unary};
88 next if m{^list operators};
92 foreach my $op (split /\s+/) {
93 next unless length $op;
94 next if $op =~ m{^\w+$};
99 $perlop_re = '(?: '.(join ' | ', @ops).' )';
100 $perlop_re = qr{$perlop_re}x;
101 #print STDERR "$perlop_re\n";
106 our ($ifilehead, $ifirsthunkhead);
107 our ($ihunkhead, $ihunkend);
108 our ($ichunkstart, $ichunkend);
109 our ($before, $after);
111 sub is_string ($) { $_[0]{T} =~ m/heredoc|string/; };
118 # E exact input text (does not contain here doc contents)
119 # P something to print in messages
120 # V value, only for: heredoc string
121 # Q quote characcter, only for: heredoc string
124 if (s{^[\$\@\%]?[_0-9a-zA-Z]+}{}) {
125 push @o, { T => 'ident', E => $&, P => $& };
126 } elsif (s{^\<\<(['"]?)([A-Z_]+)\1}{}) {
127 my ($q,$d) = ($1,$2);
129 push @o, { T => 'heredoc', Q => $q, Delim => $d,
130 E => $&, P => "<<$q$d$q" };
133 ( (?: (?! $d) .* \n )* )
135 }{ $1 }x or die "missing end of here doc $d\n";
137 } elsif (s{^ (["'])( (?: [^\\'"]
142 my ($q,$v) = ($1,$2);
143 push @o, { T => 'string', E => $&, P => "$q-string",
145 } elsif (s{^$perlop_re|^\;}{}) {
146 push @o, { T => 'op', E => $&, P => $& };
147 } elsif (s/[[{(]//) {
148 push @o, { T => 'bra', E => $&, P => $& };
149 } elsif (s/[]})]//) {
150 push @o, { T => 'ket', E => $&, P => $& };
151 } elsif (s/^( [\$\@\%] )( \{ )//x) {
152 push @o, { T => 'deref', E => $1, P => $1 },
153 { T => 'bra', E => $2, P => $2 };
154 } elsif (s/^ [\$\@\%] [^[^{] //x) {
155 push @o, { T => 'specvar', E => $&, P => $& };
160 die "cannot tokenise \`$&'";
163 for (my $i=@o-2; $i>0; --$i) {
164 next unless $o[$i+1]{E} eq '.';
165 my @inputs = @o[$i, $i+2];
166 next if grep { !is_string($_) } @inputs;
167 my $q = $inputs[0]{Q};
168 next if grep { $_->{Q} ne $q } @inputs;
169 my $new = { T => 'joinedstrings',
170 E => (join '.', map { $_->{E} } @inputs),
171 P => (join '.', map { $_->{P} } @inputs),
172 V => (join '', map { $_->{V} } @inputs),
175 @o = (@o[0..$i-1], $new, @o[$i+3..$#o]);
176 print STDERR Dumper(\@o);
178 debug $ichunkstart, "semiparsed: ".join ' ', map { $_->{P} } @o;
182 sub analyse_chunk_core () {
183 die "plain deletion\n" unless defined $after;
184 die "plain insertion\n" unless defined $before;
185 my @xs = semiparse $before;
186 my @ys = semiparse $after;
187 my $next_something = sub {
188 my ($ary,$var,$what) = @_;
189 die "ran out of $what\n" unless @$ary;
193 my $next_x = sub { $next_something->(\@xs, \$x, 'before'); };
194 my $next_y = sub { $next_something->(\@ys, \$y, 'after' ); };
196 last unless @xs or @ys;
199 next if $x->{E} eq $y->{E};
202 if ($ye eq '__' or $ye eq 'f_') {
204 die "__ on non-string $y->{P}\n" unless is_string($y);
205 die "__ on was non-string $y->{P}\n" unless is_string($x);
206 if ($y->{Q} ne "'") {
207 die "var subst in new string\n"
208 if $y->{V} =~ m{(?<!\\) [\$\@]};
211 die "__ string changed\n" unless $y->{V} eq $x->{V};
212 die "__ string quote changed\n" unless $y->{Q} eq $x->{Q};
214 $string_changed = $@;
218 die "percent $& in __ ' string\n" if m{\%};
219 die $string_changed if length $string_changed;
224 die "no percent in _f string\n" unless $fmt =~ m{\%};
225 next unless $string_changed;
226 die "f_ old string '-quoted\n" if $x->{Q} ne '"';
229 my ($lit, $what) = @_;
230 my $xl = substr($xs, 0, length($lit));
232 debug $ichunkstart, "not exactly x: $xl";
233 debug $ichunkstart, "not exactly y: $lit";
234 my $next = @ys ? $ys[0]{P} : '(end)';
235 die "string contents mismatch near $what before $next\n";
237 $xs = substr($xs, length($lit));
240 if ($fmt !~ m{\%[^\%]}) {
241 $exactly->($fmt, '(tail)');
245 $exactly->($`, '(literal)');
247 if ($& eq '%%') { $exactly->('%', '%%'); next; }
248 elsif ($& ne '%s') { die "unhandled %-subst $&\n"; }
250 die "expected comma, got $y->{P}\n" unless $y->{E} eq ',';
253 die "\@... => not string" unless is_string($y);
254 die "\@... => $y->{P}" if $y->{Q} ne '"';
255 $exactly->($y->{V}, $y->{P});
260 if (!$bras and !@ys) {
265 (grep { $y->{E} eq $_ } qw( or xor and not ; : )
272 #debug $ichunkstart, "TOKEN $y->{P}\n";
273 $exactly->($y->{E}, $y->{P});
274 if ($y->{T} eq 'bra' or $y->{E} eq '?') {
276 } elsif ($y->{T} eq 'ket' or $y->{E} eq ':') {
277 die "too many kets at $y->{E}\n" unless $bras;
284 die "mismatch $x->{P} => $y->{P}\n";
288 sub analyse_chunk () {
290 eval { analyse_chunk_core(); };
291 return unless length $@;
292 if ($@ =~ m{^missing end of here doc (\S+)\n}) {
304 our $last_filehead = -1;
306 sub report_on_hunk () {
307 return unless @report;
308 if ($last_filehead != $ifilehead) {
309 foreach (my $i=$ifilehead; $i<$ifirsthunkhead; $i++) {
312 $last_filehead = $ifilehead;
314 my $dummy_r = { S => (scalar @d)+1, E => (scalar @d)+1 };
316 for (my $i=$ihunkhead; ; $i++) {
318 $r //= shift @report;
320 last if $i < $r->{E};
321 confess unless $r->{Done} == 03;
325 last unless $i<$ihunkend;
327 foreach my $ds (@{ $debug[$i] }) {
343 confess unless $r = $dummy_r;
346 for ($ifilehead = 0; l_ok $ifilehead; $ifilehead++) {
348 $ifirsthunkhead = $ifilehead;
349 while (l_ok $ifirsthunkhead and
350 m{^diff|^index|^---|^\Q+++\E}) {
353 $ihunkhead = $ifirsthunkhead;
354 while (l_ok $ihunkhead) {
355 m{^\@\@} or confess "$ihunkhead $_ ?";
356 my $i = $ihunkhead + 1;
358 if (!l_ok $i or m{^ } or m{^\@\@}) {
359 if (defined $ichunkstart) {
361 eval { analyse_chunk(); 1; };
363 push @report, { M => $@,
367 $ichunkstart = $ichunkend = $before = $after = undef;
372 my $which = $& eq '-' ? \$before : \$after;