1implement Diff; 2 3# diff - differential file comparison 4# 5# Uses an algorithm due to Harold Stone, which finds 6# a pair of longest identical subsequences in the two 7# files. 8# 9# The major goal is to generate the match vector J. 10# J[i] is the index of the line in file1 corresponding 11# to line i file0. J[i] = 0 if there is no 12# such line in file1. 13# 14# Lines are hashed so as to work in core. All potential 15# matches are located by sorting the lines of each file 16# on the hash (called value). In particular, this 17# collects the equivalence classes in file1 together. 18# Subroutine equiv replaces the value of each line in 19# file0 by the index of the first element of its 20# matching equivalence in (the reordered) file1. 21# To save space equiv squeezes file1 into a single 22# array member in which the equivalence classes 23# are simply concatenated, except that their first 24# members are flagged by changing sign. 25# 26# Next the indices that point into member are unsorted into 27# array class according to the original order of file0. 28# 29# The cleverness lies in routine stone. This marches 30# through the lines of file0, developing a vector klist 31# of "k-candidates". At step i a k-candidate is a matched 32# pair of lines x,y (x in file0 y in file1) such that 33# there is a common subsequence of lenght k 34# between the first i lines of file0 and the first y 35# lines of file1, but there is no such subsequence for 36# any smaller y. x is the earliest possible mate to y 37# that occurs in such a subsequence. 38# 39# Whenever any of the members of the equivalence class of 40# lines in file1 matable to a line in file0 has serial number 41# less than the y of some k-candidate, that k-candidate 42# with the smallest such y is replaced. The new 43# k-candidate is chained (via pred) to the current 44# k-1 candidate so that the actual subsequence can 45# be recovered. When a member has serial number greater 46# that the y of all k-candidates, the klist is extended. 47# At the end, the longest subsequence is pulled out 48# and placed in the array J by unravel. 49# 50# With J in hand, the matches there recorded are 51# check'ed against reality to assure that no spurious 52# matches have crept in due to hashing. If they have, 53# they are broken, and "jackpot " is recorded--a harmless 54# matter except that a true match for a spuriously 55# mated line may now be unnecessarily reported as a change. 56# 57# Much of the complexity of the program comes simply 58# from trying to minimize core utilization and 59# maximize the range of doable problems by dynamically 60# allocating what is needed and reusing what is not. 61# The core requirements for problems larger than somewhat 62# are (in words) 2*length(file0) + length(file1) + 63# 3*(number of k-candidates installed), typically about 64# 6n words for files of length n. 65# 66# 67 68include "sys.m"; 69 sys: Sys; 70 71include "bufio.m"; 72 bufio : Bufio; 73Iobuf : import bufio; 74 75include "draw.m"; 76 draw: Draw; 77include "readdir.m"; 78 readdir : Readdir; 79include "string.m"; 80 str : String; 81include "arg.m"; 82 83Diff : module 84{ 85 init: fn(ctxt: ref Draw->Context, argv: list of string); 86}; 87 88stderr: ref Sys->FD; 89 90mode : int; # '\0', 'e', 'f', 'h' 91bflag : int; # ignore multiple and trailing blanks 92rflag : int; # recurse down directory trees 93mflag : int; # pseudo flag: doing multiple files, one dir 94 95REG, 96BIN: con iota; 97 98HALFINT : con 16; 99Usage : con "usage: diff [ -efbwr ] file1 ... file2"; 100 101cand : adt { 102 x : int; 103 y : int; 104 pred : int; 105}; 106 107line : adt { 108 serial : int; 109 value : int; 110}; 111 112out : ref Iobuf; 113file := array[2] of array of line; 114sfile := array[2] of array of line; # shortened by pruning common prefix and suffix 115slen := array[2] of int; 116ilen := array[2] of int; 117pref, suff, clen : int; # length of prefix and suffix 118firstchange : int; 119clist : array of cand; # merely a free storage pot for candidates 120J : array of int; # will be overlaid on class 121ixold, ixnew : array of int; 122input := array[2] of ref Iobuf ; 123file1, file2 : string; 124tmpname := array[] of {"/tmp/diff1", "/tmp/diff2"}; 125whichtmp : int; 126anychange := 0; 127 128init(nil: ref Draw->Context, args: list of string) 129{ 130 sys = load Sys Sys->PATH; 131 stderr = sys->fildes(2); 132 draw = load Draw Draw->PATH; 133 bufio = load Bufio Bufio->PATH; 134 readdir = load Readdir Readdir->PATH; 135 str = load String String->PATH; 136 if (bufio==nil) 137 fatal(sys->sprint("cannot load %s: %r", Bufio->PATH)); 138 if (readdir==nil) 139 fatal(sys->sprint("cannot load %s: %r", Readdir->PATH)); 140 if (str==nil) 141 fatal(sys->sprint("cannot load %s: %r", String->PATH)); 142 arg := load Arg Arg->PATH; 143 if (arg==nil) 144 fatal(sys->sprint("cannot load %s: %r", Arg->PATH)); 145 fsb, tsb : Sys->Dir; 146 arg->init(args); 147 while((o := arg->opt()) != 0) 148 case o { 149 'e' or 'f' => 150 mode = o; 151 'w' => 152 bflag = 2; 153 'b' => 154 bflag = 1; 155 'r' => 156 rflag = 1; 157 'm' => 158 mflag = 1; 159 * => 160 fatal(Usage); 161 } 162 tmp := arg->argv(); 163 arg = nil; 164 j := len tmp; 165 if (j < 2) 166 fatal(Usage); 167 arr := array[j] of string; 168 for(i:=0;i<j;i++){ 169 arr[i]= hd tmp; 170 tmp = tl tmp; 171 } 172 173 (i,tsb)=sys->stat(arr[j-1]); 174 if (i == -1) 175 fatal(sys->sprint("can't stat %s: %r", arr[j-1])); 176 if (j > 2) { 177 if (!(tsb.qid.qtype&Sys->QTDIR)) 178 fatal(Usage); 179 mflag = 1; 180 } 181 else { 182 (i,fsb)=sys->stat(arr[0]); 183 if (i == -1) 184 fatal(sys->sprint("can't stat %s: %r", arr[0])); 185 if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) 186 mflag = 1; 187 } 188 out=bufio->fopen(sys->fildes(1),Bufio->OWRITE); 189 for (i = 0; i < j-1; i++) { 190 diff(arr[i], arr[j-1], 0); 191 rmtmpfiles(); 192 } 193 rmtmpfiles(); 194 out.flush(); 195 if (anychange) 196 raise "fail:some"; 197} 198 199############################# diffreg from here .... 200 201# shellsort CACM #201 202 203sort(a : array of line, n : int) 204{ 205 w : line; 206 m := 0; 207 for (i := 1; i <= n; i *= 2) 208 m = 2*i - 1; 209 for (m /= 2; m != 0; m /= 2) { 210 for (j := 1; j <= n-m ; j++) { 211 ai:=j; 212 aim:=j+m; 213 do { 214 if (a[aim].value > a[ai].value || 215 a[aim].value == a[ai].value && 216 a[aim].serial > a[ai].serial) 217 break; 218 w = a[ai]; 219 a[ai] = a[aim]; 220 a[aim] = w; 221 aim=ai; 222 ai-=m; 223 } while (ai > 0 && aim >= ai); 224 } 225 } 226} 227 228unsort(f : array of line, l : int) : array of int 229{ 230 i : int; 231 a := array[l+1] of int; 232 for(i=1;i<=l;i++) 233 a[f[i].serial] = f[i].value; 234 return a; 235} 236 237prune() 238{ 239 for(pref=0;pref< ilen[0]&&pref< ilen[1]&& 240 file[0][pref+1].value==file[1][pref+1].value; 241 pref++ ) ; 242 for(suff=0;suff< ilen[0]-pref&&suff< ilen[1]-pref&& 243 file[0][ilen[0]-suff].value==file[1][ilen[1]-suff].value; 244 suff++) ; 245 for(j:=0;j<2;j++) { 246 sfile[j] = file[j][pref:]; 247 slen[j]= ilen[j]-pref-suff; 248 for(i:=0;i<=slen[j];i++) 249 sfile[j][i].serial = i; 250 } 251} 252 253equiv(a: array of line, n:int , b: array of line, m: int, c : array of int) 254{ 255 i := 1; 256 j := 1; 257 while(i<=n && j<=m) { 258 if(a[i].value < b[j].value) 259 a[i++].value = 0; 260 else if(a[i].value == b[j].value) 261 a[i++].value = j; 262 else 263 j++; 264 } 265 while(i <= n) 266 a[i++].value = 0; 267 b[m+1].value = 0; # huh ? 268 j = 1; 269 while(j <= m) { 270 c[j] = -b[j].serial; 271 while(b[j+1].value == b[j].value) { 272 j++; 273 c[j] = b[j].serial; 274 } 275 j++; 276 } 277 c[j] = -1; 278} 279 280newcand(x, y, pred : int) : int 281{ 282 if (clen==len clist){ 283 q := array[clen*2] of cand; 284 q[0:]=clist; 285 clist= array[clen*2] of cand; 286 clist[0:]=q; 287 q=nil; 288 } 289 clist[clen].x=x; 290 clist[clen].y=y; 291 clist[clen].pred=pred; 292 return clen++; 293} 294 295search(c : array of int, k,y : int) : int 296{ 297 if(clist[c[k]].y < y) # quick look for typical case 298 return k+1; 299 i := 0; 300 j := k+1; 301 while((l:=(i+j)/2) > i) { 302 t := clist[c[l]].y; 303 if(t > y) 304 j = l; 305 else if(t < y) 306 i = l; 307 else 308 return l; 309 } 310 return l+1; 311} 312 313stone(a : array of int ,n : int, b: array of int , c : array of int) : int 314{ 315 oldc, oldl, tc, l ,y : int; 316 k := 0; 317 c[0] = newcand(0,0,0); 318 for(i:=1; i<=n; i++) { 319 j := a[i]; 320 if(j==0) 321 continue; 322 y = -b[j]; 323 oldl = 0; 324 oldc = c[0]; 325 do { 326 if(y <= clist[oldc].y) 327 continue; 328 l = search(c, k, y); 329 if(l!=oldl+1) 330 oldc = c[l-1]; 331 if(l<=k) { 332 if(clist[c[l]].y <= y) 333 continue; 334 tc = c[l]; 335 c[l] = newcand(i,y,oldc); 336 oldc = tc; 337 oldl = l; 338 } else { 339 c[l] = newcand(i,y,oldc); 340 k++; 341 break; 342 } 343 } while((y=b[j+=1]) > 0); 344 } 345 return k; 346} 347 348unravel(p : int) 349{ 350 for(i:=0; i<=ilen[0]; i++) { 351 if (i <= pref) 352 J[i] = i; 353 else if (i > ilen[0]-suff) 354 J[i] = i+ ilen[1]-ilen[0]; 355 else 356 J[i] = 0; 357 } 358 for(q:=clist[p];q.y!=0;q=clist[q.pred]) 359 J[q.x+pref] = q.y+pref; 360} 361 362output() 363{ 364 i1: int; 365 m := ilen[0]; 366 J[0] = 0; 367 J[m+1] = ilen[1]+1; 368 if (mode != 'e') { 369 for (i0 := 1; i0 <= m; i0 = i1+1) { 370 while (i0 <= m && J[i0] == J[i0-1]+1) 371 i0++; 372 j0 := J[i0-1]+1; 373 i1 = i0-1; 374 while (i1 < m && J[i1+1] == 0) 375 i1++; 376 j1 := J[i1+1]-1; 377 J[i1] = j1; 378 change(i0, i1, j0, j1); 379 } 380 } 381 else { 382 for (i0 := m; i0 >= 1; i0 = i1-1) { 383 while (i0 >= 1 && J[i0] == J[i0+1]-1 && J[i0]) 384 i0--; 385 j0 := J[i0+1]-1; 386 i1 = i0+1; 387 while (i1 > 1 && J[i1-1] == 0) 388 i1--; 389 j1 := J[i1-1]+1; 390 J[i1] = j1; 391 change(i1 , i0, j1, j0); 392 } 393 } 394 if (m == 0) 395 change(1, 0, 1, ilen[1]); 396 out.flush(); 397} 398 399diffreg(f,t : string) 400{ 401 k : int; 402 403 (b0, b0type) := prepare(0, f); 404 if (b0==nil) 405 return; 406 (b1, b1type) := prepare(1, t); 407 if (b1==nil) { 408 b0=nil; 409 return; 410 } 411 if (b0type == BIN || b1type == BIN) { 412 if (cmp(b0, b1)) { 413 out.puts(sys->sprint("Binary files %s %s differ\n", f, t)); 414 anychange = 1; 415 } 416 b0 = nil; 417 b1 = nil; 418 return; 419 } 420 clen=0; 421 prune(); 422 file[0]=nil; 423 file[1]=nil; 424 sort(sfile[0],slen[0]); 425 sort(sfile[1],slen[1]); 426 member := array[slen[1]+2] of int; 427 equiv(sfile[0], slen[0],sfile[1],slen[1], member); 428 class:=unsort(sfile[0],slen[0]); 429 sfile[0]=nil; 430 sfile[1]=nil; 431 klist := array[slen[0]+2] of int; 432 clist = array[1] of cand; 433 k = stone(class, slen[0], member, klist); 434 J = array[ilen[0]+2] of int; 435 unravel(klist[k]); 436 clist=nil; 437 klist=nil; 438 class=nil; 439 member=nil; 440 ixold = array[ilen[0]+2] of int; 441 ixnew = array[ilen[1]+2] of int; 442 443 b0.seek(big 0, 0); 444 b1.seek(big 0, 0); 445 check(b0, b1); 446 output(); 447 ixold=nil; 448 ixnew=nil; 449 b0=nil; 450 b1=nil; 451} 452 453######################## diffio starts here... 454 455 456# hashing has the effect of 457# arranging line in 7-bit bytes and then 458# summing 1-s complement in 16-bit hunks 459 460readhash(bp : ref Iobuf) : int 461{ 462 sum := 1; 463 shift := 0; 464 buf := bp.gets('\n'); 465 if (buf == nil) 466 return 0; 467 buf = buf[0:len buf -1]; 468 p := 0; 469 case bflag { 470 # various types of white space handling 471 0 => 472 while (p< len buf) { 473 sum += (buf[p] << (shift &= (HALFINT-1))); 474 p++; 475 shift += 7; 476 } 477 1 => 478 479 # coalesce multiple white-space 480 481 for (space := 0; p< len buf; p++) { 482 if (buf[p]==' ' || buf[p]=='\t') { 483 space++; 484 continue; 485 } 486 if (space) { 487 shift += 7; 488 space = 0; 489 } 490 sum += (buf[p] << (shift &= (HALFINT-1))); 491 p++; 492 shift += 7; 493 } 494 * => 495 496 # strip all white-space 497 498 while (p< len buf) { 499 if (buf[p]==' ' || buf[p]=='\t') { 500 p++; 501 continue; 502 } 503 sum += (buf[p] << (shift &= (HALFINT-1))); 504 p++; 505 shift += 7; 506 } 507 } 508 return sum; 509} 510 511prepare(i : int, arg : string) : (ref Iobuf, int) 512{ 513 h : int; 514 bp := bufio->open(arg,Bufio->OREAD); 515 if (bp==nil) { 516 error(sys->sprint("cannot open %s: %r", arg)); 517 return (nil, 0); 518 } 519 buf := array[1024] of byte; 520 n :=bp.read(buf, len buf); 521 str1 := string buf[0:n]; 522 for (j:=0;j<len str1 -2;j++) 523 if (str1[j] == Sys->UTFerror) 524 return (bp, BIN); 525 bp.seek(big 0, Sys->SEEKSTART); 526 p := array[4] of line; 527 for (j = 0; h = readhash(bp); p[j].value = h){ 528 j++; 529 if (j+3>=len p){ 530 newp:=array[len p*2] of line; 531 newp[0:]=p[0:]; 532 p=array[len p*2] of line; 533 p=newp; 534 newp=nil; 535 } 536 } 537 ilen[i]=j; 538 file[i] = p; 539 input[i] = bp; 540 if (i == 0) { 541 file1 = arg; 542 firstchange = 0; 543 } 544 else 545 file2 = arg; 546 return (bp, REG); 547} 548 549squishspace(buf : string) : string 550{ 551 q:=0; 552 p:=0; 553 for (space := 0; q<len buf; q++) { 554 if (buf[q]==' ' || buf[q]=='\t') { 555 space++; 556 continue; 557 } 558 if (space && bflag == 1) { 559 buf[p] = ' '; 560 p++; 561 space = 0; 562 } 563 buf[p]=buf[q]; 564 p++; 565 } 566 buf=buf[0:p]; 567 return buf; 568} 569 570 571# need to fix up for unexpected EOF's 572 573ftell(b: ref Iobuf): int 574{ 575 return int b.offset(); 576} 577 578check(bf, bt : ref Iobuf) 579{ 580 fbuf, tbuf : string; 581 f:=1; 582 t:=1; 583 ixold[0] = ixnew[0] = 0; 584 for (; f < ilen[0]; f++) { 585 fbuf = bf.gets('\n'); 586 if (fbuf!=nil) 587 fbuf=fbuf[0:len fbuf -1]; 588 ixold[f] = ftell(bf); 589 if (J[f] == 0) 590 continue; 591 tbuflen: int; 592 do { 593 tbuf = bt.gets('\n'); 594 if (tbuf!=nil) 595 tbuf=tbuf[0:len tbuf -1]; 596 tbuflen = len array of byte tbuf; 597 ixnew[t] = ftell(bt); 598 } while (t++ < J[f]); 599 if (bflag) { 600 fbuf = squishspace(fbuf); 601 tbuf = squishspace(tbuf); 602 } 603 if (len fbuf != len tbuf || fbuf!=tbuf) 604 J[f] = 0; 605 } 606 while (t < ilen[1]) { 607 tbuf = bt.gets('\n'); 608 if (tbuf!=nil) 609 tbuf=tbuf[0:len tbuf -1]; 610 ixnew[t] = ftell(bt); 611 t++; 612 } 613} 614 615range(a, b : int, separator : string) 616{ 617 if (a>b) 618 out.puts(sys->sprint("%d", b)); 619 else 620 out.puts(sys->sprint("%d", a)); 621 if (a < b) 622 out.puts(sys->sprint("%s%d", separator, b)); 623} 624 625fetch(f : array of int, a,b : int , bp : ref Iobuf, s : string) 626{ 627 buf : string; 628 bp.seek(big f[a-1], 0); 629 while (a++ <= b) { 630 buf=bp.gets('\n'); 631 out.puts(s); 632 out.puts(buf); 633 } 634} 635 636change(a, b, c, d : int) 637{ 638 if (a > b && c > d) 639 return; 640 anychange = 1; 641 if (mflag && firstchange == 0) { 642 out.puts(sys->sprint( "diff %s %s\n", file1, file2)); 643 firstchange = 1; 644 } 645 if (mode != 'f') { 646 range(a, b, ","); 647 if (a>b) 648 out.putc('a'); 649 else if (c>d) 650 out.putc('d'); 651 else 652 out.putc('c'); 653 if (mode != 'e') 654 range(c, d, ","); 655 } 656 else { 657 if (a>b) 658 out.putc('a'); 659 else if (c>d) 660 out.putc('d'); 661 else 662 out.putc('c'); 663 range(a, b, " "); 664 } 665 out.putc('\n'); 666 if (mode == 0) { 667 fetch(ixold, a, b, input[0], "< "); 668 if (a <= b && c <= d) 669 out.puts("---\n"); 670 } 671 if (mode==0) 672 fetch(ixnew, c, d, input[1], "> "); 673 else 674 fetch(ixnew, c, d, input[1], ""); 675 676 if (mode != 0 && c <= d) 677 out.puts(".\n"); 678} 679 680 681######################### diffdir starts here ...... 682 683scandir(name : string) : array of string 684{ 685 (db,nitems):= readdir->init(name,Readdir->NAME); 686 cp := array[nitems] of string; 687 for(i:=0;i<nitems;i++) 688 cp[i]=db[i].name; 689 return cp; 690} 691 692 693diffdir(f, t : string, level : int) 694{ 695 df, dt : array of string; 696 fb, tb : string; 697 i:=0; 698 j:=0; 699 df = scandir(f); 700 dt = scandir(t); 701 while ((i<len df) || (j<len dt)) { 702 if ((j==len dt) || (i<len df && df[i] < dt[j])) { 703 if (mode == 0) 704 out.puts(sys->sprint("Only in %s: %s\n", f, df[i])); 705 i++; 706 continue; 707 } 708 if ((i==len df) || (j<len dt && df[i] > dt[j])) { 709 if (mode == 0) 710 out.puts(sys->sprint("Only in %s: %s\n", t, dt[j])); 711 j++; 712 continue; 713 } 714 fb=sys->sprint("%s/%s", f, df[i]); 715 tb=sys->sprint("%s/%s", t, dt[j]); 716 diff(fb, tb, level+1); 717 i++; j++; 718 } 719} 720 721cmp(b0, b1: ref Iobuf): int 722{ 723 b0.seek(big 0, Sys->SEEKSTART); 724 b1.seek(big 0, Sys->SEEKSTART); 725 buf0 := array[1024] of byte; 726 buf1 := array[1024] of byte; 727 for (;;) { 728 n0 := b0.read(buf0, len buf0); 729 n1 := b1.read(buf1, len buf1); 730 731 if (n0 != n1) 732 return 1; 733 734 if (n0 == 0) 735 return 0; 736 737 for (i := 0; i < n0; i++) 738 if (buf0[i] != buf1[i]) 739 return 1; 740 } 741} 742 743################## main from here..... 744 745REGULAR_FILE(s : Sys->Dir) : int 746{ 747 # both pipes and networks contain non-zero-length files 748 # which are not seekable. 749 return (s.qid.qtype&Sys->QTDIR) == 0 && 750 s.dtype != '|' && 751 s.dtype != 'I'; 752# && s.length > 0; device files have zero length. 753} 754 755rmtmpfiles() 756{ 757 while (whichtmp > 0) { 758 whichtmp--; 759 sys->remove(tmpname[whichtmp]); 760 } 761} 762 763mktmpfile(inputf : ref Sys->FD) : (string, Sys->Dir) 764{ 765 i, j : int; 766 sb : Sys->Dir; 767 p : string; 768 buf := array[8192] of byte; 769 770 p = tmpname[whichtmp++]; 771 fd := sys->create(p, Sys->OWRITE, 8r600); 772 if (fd == nil) { 773 error(sys->sprint("cannot create %s: %r", p)); 774 return (nil, sb); 775 } 776 while ((i = sys->read(inputf, buf, len buf)) > 0) { 777 if ((i = sys->write(fd, buf, i)) < 0) 778 break; 779 } 780 (j,sb)=sys->fstat(fd); 781 if (i < 0 || j < 0) { 782 error(sys->sprint("cannot read/write %s: %r", p)); 783 return (nil, sb); 784 } 785 return (p, sb); 786} 787 788 789statfile(file : string) : (string,Sys->Dir) 790{ 791 (ret,sb):=sys->stat(file); 792 if (ret==-1) { 793 if (file != "-" || sys->fstat(sys->fildes(0)).t0 == -1){ 794 error(sys->sprint("cannot stat %s: %r", file)); 795 return (nil,sb); 796 } 797 (file, sb) = mktmpfile(sys->fildes(0)); 798 } 799 else if (!REGULAR_FILE(sb) && !(sb.qid.qtype&Sys->QTDIR)) { 800 if ((i := sys->open(file, Sys->OREAD)) == nil) { 801 error(sys->sprint("cannot open %s: %r", file)); 802 return (nil, sb); 803 } 804 (file, sb) = mktmpfile(i); 805 } 806 return (file,sb); 807} 808 809diff(f, t : string, level : int) 810{ 811 fp,tp,p,rest,fb,tb : string; 812 fsb, tsb : Sys->Dir; 813 (fp,fsb) = statfile(f); 814 if (fp == nil) 815 return; 816 (tp,tsb) = statfile(t); 817 if (tp == nil) 818 return; 819 if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) { 820 if (rflag || level == 0) 821 diffdir(fp, tp, level); 822 else 823 out.puts(sys->sprint("Common subdirectories: %s and %s\n", fp, tp)); 824 } 825 else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb)){ 826 diffreg(fp, tp); 827 } else { 828 if (!(fsb.qid.qtype&Sys->QTDIR)) { 829 (p,rest)=str->splitr(f,"/"); 830 if (rest!=nil) 831 p = rest; 832 tb=sys->sprint("%s/%s", tp, p); 833 diffreg(fp, tb); 834 } 835 else { 836 (p,rest)=str->splitr(t,"/"); 837 if (rest!=nil) 838 p = rest; 839 fb=sys->sprint("%s/%s", fp, p); 840 diffreg(fb, tp); 841 } 842 } 843} 844 845fatal(s: string) 846{ 847 sys->fprint(stderr, "diff: %s\n", s); 848 raise "fail:error"; 849} 850 851error(s: string) 852{ 853 sys->fprint(stderr, "diff: %s\n", s); 854} 855