chiark / gitweb /
auditor wip halfway reorg @o
[dgit-junk.git] / i18n-diff-auditor
1 #!/usr/bin/perl -w
2 use strict;
3 use Carp;
4 use Data::Dumper;
5
6 my @d = <>;
7 unshift @d, "# dummy line to make line 1 index 1 in \@d\n";
8
9 our $i_last_l_ok = -1;
10 our $count_i_last_l_ok;
11
12 sub l_ok ($) {
13     my ($i) = @_;
14
15     if ($i == $i_last_l_ok) {
16         confess $i if $count_i_last_l_ok++ > 50;
17     } else {
18         $count_i_last_l_ok = 0;
19         $i_last_l_ok = $i;
20     }
21
22     return unless $i < @d;
23     $_ = $d[$i];
24     #print STDERR "L $i\n";
25     1;
26 }
27
28 sub l ($) {
29     my ($i) = @_;
30     confess $i unless l_ok $i;
31 };
32
33 our $perlop_text = <<'END'; # c&p from man perlop
34            left        terms and list operators (leftward)
35            left        ->
36            nonassoc    ++ --
37            right       **
38            right       ! ~ \ and unary + and -
39            left        =~ !~
40            left        * / % x
41            left        + - .
42            left        << >>
43            nonassoc    named unary operators
44            nonassoc    < > <= >= lt gt le ge
45            nonassoc    == != <=> eq ne cmp ~~
46            left        &
47            left        | ^
48            left        &&
49            left        || //
50            nonassoc    ..  ...
51            right       ?:
52            right       = += -= *= etc. goto last next redo dump
53            left        , =>
54            nonassoc    list operators (rightward)
55            right       not
56            left        and
57            left        or xor
58
59            **=    +=    *=    &=    &.=    <<=    &&=
60                   -=    /=    |=    |.=    >>=    ||=
61                   .=    %=    ^=    ^.=           //=
62                         x=
63 END
64
65 our $perlop_re;
66
67 sub prep_perlop () {
68     my @ops;
69     foreach (split /\n/, $perlop_text) {
70         next unless m{\S};
71         s{\s+$}{};
72         s{^\s+}{};
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};
77         s{ and unary.*}{};
78         s{ etc\. }{ };
79         s{\?\:}{ ? : };
80         foreach my $op (split /\s+/) {
81             next unless length $op;
82             next if $op =~ m{^\w+$};
83             $op =~ s/\W/\\$&/g;
84             push @ops, $op;
85         }
86     }
87     $perlop_re = '(?: '.(join ' | ', @ops).' )';
88     $perlop_re = qr{$perlop_re}x;
89     #print STDERR "$perlop_re\n";
90 }
91
92 prep_perlop();
93
94 our ($ifilehead, $ihunkhead, $ichunkstart, $ichunkend);
95 our ($before, $after);
96
97 sub semiparse ($) {
98     ($_) = @_;
99     my @o;
100     # entries contain
101     #   T     type
102     #   E     exact input text (does not contain here doc contents)
103     #   P     something to print in messages
104     #   V     value, only for: heredoc string
105     #   Q     quote characcter, only for: heredoc string
106     for (;;) {
107         s{^\s+}{};
108         if (s{^[\$\@\%]?[_0-9a-zA-Z]+}{}) {
109             push @o, { T => 'ident', E => $&, P => $& };
110         } elsif (s{^\<\<('?)([A-Z_]+)\1}{}) {
111             my ($q,$d) = ($1,$2);
112             push @o, { T => 'heredoc', Q => $q, Delim => $d,
113                        E => $&, P => "<<$q$d..." };
114             s{^
115                 (             .* \n    )
116                 ( (?: (?! $d) .* \n )* )
117                           $d     \n
118               }{ $1 }x or die "missing end of here doc $d\n";
119             $o[$#o]{V} = $2;
120         } elsif (s{^ (["'])( (?: [^\\'"]
121                                | \\ [^"']
122                                | (?! \1 ) [^"]
123                               )*
124                        \1 )}{}x) {
125             my ($q,$v) = ($1,$2);
126             push @o, { T => 'string', Q => $q, V => $v,
127                        I => $&, P => "$q-string" };
128         } elsif (s{^$perlop_re|\;}{}) {
129             push @o, { T => 'op', E => $&, I => $& };
130         } elsif (s/[[{(]//) {
131             push @o, { T => 'bra', E => $&, I => $& };
132         } elsif (s/[]})]//) {
133             push @o, { T => 'ket', E => $&, I => $& };
134         } elsif (s/^( [\$\@\%] )( \{ )//x) {
135             push @o, { T => 'deref', E => $1, I => $1 },
136                      { T => 'bra',   E => $2, I => $1 };
137         } elsif (s/^ [\$\@\%] [^[^{] //x) {
138             push @o, { T => 'specvar', E => $&, I => $& };
139         } elsif (!length) {
140             last;
141         } else {
142             m{^.{0,10}};
143             die "cannot tokenise \`$&'";
144         }
145     }
146     # coalesce concatenated strings
147     return @o;
148 }           
149
150 sub analyse_chunk_core () {
151     die "plain deletion\n" unless defined $after;
152     die "plain insertion\n" unless defined $before;
153     my @before = semiparse $before;
154     my @after = semiparse $after;
155     my $next_something = sub {
156         my ($ary,$var,$what) = @_;
157         die "ran out of $what\n" unless @$ary;
158         $$var = shift @$ary;
159     };
160     my $next_before = sub { $next_something->(\@before, \$x, 'before'); };
161     my $next_after  = sub { $next_something->(\@after , \$y, 'after' ); };
162     sub $is_string = sub { $_[0]{T} =~ m/heredoc|string/; };
163     for (;;) {
164         last unless @before or @after;
165         $next_before->();
166         $next_after->();
167         next if $x->{I} eq $y->{I};
168         my $string_changed;
169         if ($y->{I} eq '__' or $y->{I} eq '_f') {
170             $next_after->();
171             die "__ on non-string\n"     unless Sis_string($y);
172             die "__ on was non-string\n" unless $is_string($x);
173             if ($y->{Q} ne "'") {
174                 die "var subst in new str\n" if $y->{V} =~ m{(?<!\\) [\$\@]};
175             }
176             eval {
177                 die "__ string changed\n"       unless $y->{V} eq $x->{V};
178                 die "__ string quote changed\n" unless $y->{Q} eq $x->{Q};
179             };
180             $string_changed = $@;
181         }
182         if ($y->{I} eq '__') {
183             $_ = $y->{V};
184             die "percent in __ ' string\n" if m{\%};
185             die $string_changed if length $not_unchanged;
186             next;
187         }
188         if ($y->{I} eq 'f_') {
189             my $fmt = $y->{V};
190             die "no percent in _f string\n" unless $fmt =~ m{\%};
191             next unless $string_changed;
192             die "f_ old string '-quoted\n" if length $x->{V};
193             my $xs = $x->{V};
194             my $exactly = sub {
195                 my ($lit) = @_;
196                 my $xl = substr($xs, 0, length($lit));
197                 die "exactly mismatch in $lit\n" unless $xl eq $lit;
198                 $xs = substr($xs, length($lit));
199             };
200             for (;;) {
201                 if ($fmt !~ m{\%[^\%]}) {
202                     $exactly->($fmt);
203                     $fmt = '';
204                     last;
205                 }
206                 $exactly->($`);
207                 $fmt = $';
208                 if ($& eq '%%') { $exactly('%'); next; }
209                 elsif ($& ne '%s') { die "unhandled %-subst $&\n"; }
210                 $next_after->();
211                 die "expected comma, got $y->{T}\n" unless $y->{I} eq ',';
212                 if ($old =~ m{^\@}) {
213                     $next_after->();
214                     die "\@... => not string" unless $is_string($y);
215                     $exactly->($y->{V});
216                     next;
217                 }
218                 my $bras = 0;
219                 for (;;) {
220                     if (!$bra and !@after) {
221                         last;
222                     }
223                     $next_after->();
224                     if (!$bra and
225                         (grep { $y->{I} eq $_ } qw( or xor and not ; : )
226                          or $y->{T} eq 'ket'
227                         )) {
228                         unshift @after, $ys;
229                         last;
230                     }
231                     $xs =~ s{^\s+}{};
232                     $exactly->{$y->{I});
233                     if ($y->{T} eq 'bra' or $y->{L} eq '?') {
234                         $bras++;
235                     } elsif ($y->{T} eq 'ket' or $y->{L} eq ':') {
236                         die "too many kets at $y->{L}\n" unless $bras;
237                         $bras--;
238                     }
239                 }
240             }
241             next;
242         }
243         die "mismatch 
244
245                     $exact->
246                 
247                 if ($fmt =~ m{
248                 if 
249         
250             if ($y->{Q} eq $x->{Q} && $y->
251
252             $y = shift @after;
253     print Dumper($ichunkstart, $ichunkend, \@before, \@after);
254     flush STDOUT;
255 }
256
257 sub analyse_chunk () {
258     for (;;) {
259         eval { analyse_chunk_core(); };
260         return unless length $@;
261         if ($@ =~ m{^missing end of here doc (\S+)\n}) {
262             # fudge this
263             $before .= "\n$1\n";
264             $after .= "\n$1\n";
265             next;
266         } else {
267             die $@;
268         }
269     }
270 }
271
272 for ($ifilehead = 0; l_ok $ifilehead; $ifilehead++) {
273     m{^diff} or next;
274     while (l_ok $ifilehead and m{^diff|^index|^---|^\Q+++\E}) { $ifilehead++ }
275     $ihunkhead = $ifilehead;
276     while (l_ok $ihunkhead) {
277         m{^\@\@} or confess "$ihunkhead $_ ?";
278         my $i = $ihunkhead + 1;
279         for (; ; $i++) {
280             if (!l_ok $i or m{^ } or m{^\@\@}) {
281                 if (defined $ichunkstart) {
282                     $ichunkend = $i;
283                     eval { analyse_chunk(); 1; };
284                     if (length $@) {
285                         print Dumper('REPORT',
286                                      $ichunkstart, $ichunkend,
287                                      $before, $after,
288                                      $@);
289                     }
290                     $ichunkstart = $ichunkend = $before = $after = undef;
291                 }
292                 l_ok $i or last;
293                 m{^\@\@} and last;
294             } elsif (m{^[-+]}) {
295                 my $which = $& eq '-' ? \$before : \$after;
296                 $ichunkstart //= $i;
297                 $$which //= '';
298                 $$which .= $';
299             } else {
300                 confess "$i $_ ?";
301             }
302         }
303         $ichunkend = $i;
304         $ihunkhead = $i;
305     }
306 }