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 j1:=0; 207 m := 0; 208 for (i := 1; i <= n; i *= 2) 209 m = 2*i - 1; 210 for (m /= 2; m != 0; m /= 2) { 211 for (j := 1; j <= n-m ; j++) { 212 ai:=j; 213 aim:=j+m; 214 do { 215 if (a[aim].value > a[ai].value || 216 a[aim].value == a[ai].value && 217 a[aim].serial > a[ai].serial) 218 break; 219 w = a[ai]; 220 a[ai] = a[aim]; 221 a[aim] = w; 222 aim=ai; 223 ai-=m; 224 } while (ai > 0 && aim >= ai); 225 } 226 } 227} 228 229unsort(f : array of line, l : int) : array of int 230{ 231 i : int; 232 a := array[l+1] of int; 233 for(i=1;i<=l;i++) 234 a[f[i].serial] = f[i].value; 235 return a; 236} 237 238prune() 239{ 240 for(pref=0;pref< ilen[0]&&pref< ilen[1]&& 241 file[0][pref+1].value==file[1][pref+1].value; 242 pref++ ) ; 243 for(suff=0;suff< ilen[0]-pref&&suff< ilen[1]-pref&& 244 file[0][ilen[0]-suff].value==file[1][ilen[1]-suff].value; 245 suff++) ; 246 for(j:=0;j<2;j++) { 247 sfile[j] = file[j][pref:]; 248 slen[j]= ilen[j]-pref-suff; 249 for(i:=0;i<=slen[j];i++) 250 sfile[j][i].serial = i; 251 } 252} 253 254equiv(a: array of line, n:int , b: array of line, m: int, c : array of int) 255{ 256 i := 1; 257 j := 1; 258 while(i<=n && j<=m) { 259 if(a[i].value < b[j].value) 260 a[i++].value = 0; 261 else if(a[i].value == b[j].value) 262 a[i++].value = j; 263 else 264 j++; 265 } 266 while(i <= n) 267 a[i++].value = 0; 268 b[m+1].value = 0; # huh ? 269 j = 1; 270 while(j <= m) { 271 c[j] = -b[j].serial; 272 while(b[j+1].value == b[j].value) { 273 j++; 274 c[j] = b[j].serial; 275 } 276 j++; 277 } 278 c[j] = -1; 279} 280 281newcand(x, y, pred : int) : int 282{ 283 if (clen==len clist){ 284 q := array[clen*2] of cand; 285 q[0:]=clist; 286 clist= array[clen*2] of cand; 287 clist[0:]=q; 288 q=nil; 289 } 290 clist[clen].x=x; 291 clist[clen].y=y; 292 clist[clen].pred=pred; 293 return clen++; 294} 295 296search(c : array of int, k,y : int) : int 297{ 298 if(clist[c[k]].y < y) # quick look for typical case 299 return k+1; 300 i := 0; 301 j := k+1; 302 while((l:=(i+j)/2) > i) { 303 t := clist[c[l]].y; 304 if(t > y) 305 j = l; 306 else if(t < y) 307 i = l; 308 else 309 return l; 310 } 311 return l+1; 312} 313 314stone(a : array of int ,n : int, b: array of int , c : array of int) : int 315{ 316 oldc, oldl, tc, l ,y : int; 317 k := 0; 318 c[0] = newcand(0,0,0); 319 for(i:=1; i<=n; i++) { 320 j := a[i]; 321 if(j==0) 322 continue; 323 y = -b[j]; 324 oldl = 0; 325 oldc = c[0]; 326 do { 327 if(y <= clist[oldc].y) 328 continue; 329 l = search(c, k, y); 330 if(l!=oldl+1) 331 oldc = c[l-1]; 332 if(l<=k) { 333 if(clist[c[l]].y <= y) 334 continue; 335 tc = c[l]; 336 c[l] = newcand(i,y,oldc); 337 oldc = tc; 338 oldl = l; 339 } else { 340 c[l] = newcand(i,y,oldc); 341 k++; 342 break; 343 } 344 } while((y=b[j+=1]) > 0); 345 } 346 return k; 347} 348 349unravel(p : int) 350{ 351 for(i:=0; i<=ilen[0]; i++) { 352 if (i <= pref) 353 J[i] = i; 354 else if (i > ilen[0]-suff) 355 J[i] = i+ ilen[1]-ilen[0]; 356 else 357 J[i] = 0; 358 } 359 for(q:=clist[p];q.y!=0;q=clist[q.pred]) 360 J[q.x+pref] = q.y+pref; 361} 362 363output() 364{ 365 i1: int; 366 m := ilen[0]; 367 J[0] = 0; 368 J[m+1] = ilen[1]+1; 369 if (mode != 'e') { 370 for (i0 := 1; i0 <= m; i0 = i1+1) { 371 while (i0 <= m && J[i0] == J[i0-1]+1) 372 i0++; 373 j0 := J[i0-1]+1; 374 i1 = i0-1; 375 while (i1 < m && J[i1+1] == 0) 376 i1++; 377 j1 := J[i1+1]-1; 378 J[i1] = j1; 379 change(i0, i1, j0, j1); 380 } 381 } 382 else { 383 for (i0 := m; i0 >= 1; i0 = i1-1) { 384 while (i0 >= 1 && J[i0] == J[i0+1]-1 && J[i0]) 385 i0--; 386 j0 := J[i0+1]-1; 387 i1 = i0+1; 388 while (i1 > 1 && J[i1-1] == 0) 389 i1--; 390 j1 := J[i1-1]+1; 391 J[i1] = j1; 392 change(i1 , i0, j1, j0); 393 } 394 } 395 if (m == 0) 396 change(1, 0, 1, ilen[1]); 397 out.flush(); 398} 399 400diffreg(f,t : string) 401{ 402 k : int; 403 404 (b0, b0type) := prepare(0, f); 405 if (b0==nil) 406 return; 407 (b1, b1type) := prepare(1, t); 408 if (b1==nil) { 409 b0=nil; 410 return; 411 } 412 if (b0type == BIN || b1type == BIN) { 413 if (cmp(b0, b1)) { 414 out.puts(sys->sprint("Binary files %s %s differ\n", f, t)); 415 anychange = 1; 416 } 417 b0 = nil; 418 b1 = nil; 419 return; 420 } 421 clen=0; 422 prune(); 423 file[0]=nil; 424 file[1]=nil; 425 sort(sfile[0],slen[0]); 426 sort(sfile[1],slen[1]); 427 member := array[slen[1]+2] of int; 428 equiv(sfile[0], slen[0],sfile[1],slen[1], member); 429 class:=unsort(sfile[0],slen[0]); 430 sfile[0]=nil; 431 sfile[1]=nil; 432 klist := array[slen[0]+2] of int; 433 clist = array[1] of cand; 434 k = stone(class, slen[0], member, klist); 435 J = array[ilen[0]+2] of int; 436 unravel(klist[k]); 437 clist=nil; 438 klist=nil; 439 class=nil; 440 member=nil; 441 ixold = array[ilen[0]+2] of int; 442 ixnew = array[ilen[1]+2] of int; 443 444 b0.seek(big 0, 0); 445 b1.seek(big 0, 0); 446 check(b0, b1); 447 output(); 448 ixold=nil; 449 ixnew=nil; 450 b0=nil; 451 b1=nil; 452} 453 454######################## diffio starts here... 455 456 457# hashing has the effect of 458# arranging line in 7-bit bytes and then 459# summing 1-s complement in 16-bit hunks 460 461readhash(bp : ref Iobuf) : int 462{ 463 sum := 1; 464 shift := 0; 465 buf := bp.gets('\n'); 466 if (buf == nil) 467 return 0; 468 buf = buf[0:len buf -1]; 469 p := 0; 470 case bflag { 471 # various types of white space handling 472 0 => 473 while (p< len buf) { 474 sum += (buf[p] << (shift &= (HALFINT-1))); 475 p++; 476 shift += 7; 477 } 478 1 => 479 480 # coalesce multiple white-space 481 482 for (space := 0; p< len buf; p++) { 483 if (buf[p]==' ' || buf[p]=='\t') { 484 space++; 485 continue; 486 } 487 if (space) { 488 shift += 7; 489 space = 0; 490 } 491 sum += (buf[p] << (shift &= (HALFINT-1))); 492 p++; 493 shift += 7; 494 } 495 * => 496 497 # strip all white-space 498 499 while (p< len buf) { 500 if (buf[p]==' ' || buf[p]=='\t') { 501 p++; 502 continue; 503 } 504 sum += (buf[p] << (shift &= (HALFINT-1))); 505 p++; 506 shift += 7; 507 } 508 } 509 return sum; 510} 511 512prepare(i : int, arg : string) : (ref Iobuf, int) 513{ 514 h : int; 515 bp := bufio->open(arg,Bufio->OREAD); 516 if (bp==nil) { 517 error(sys->sprint("cannot open %s: %r", arg)); 518 return (nil, 0); 519 } 520 buf := array[1024] of byte; 521 n :=bp.read(buf, len buf); 522 str1 := string buf[0:n]; 523 for (j:=0;j<len str1 -2;j++) 524 if (str1[j] == Sys->UTFerror) 525 return (bp, BIN); 526 bp.seek(big 0, Sys->SEEKSTART); 527 p := array[4] of line; 528 for (j = 0; h = readhash(bp); p[j].value = h){ 529 j++; 530 if (j+3>=len p){ 531 newp:=array[len p*2] of line; 532 newp[0:]=p[0:]; 533 p=array[len p*2] of line; 534 p=newp; 535 newp=nil; 536 } 537 } 538 ilen[i]=j; 539 file[i] = p; 540 input[i] = bp; 541 if (i == 0) { 542 file1 = arg; 543 firstchange = 0; 544 } 545 else 546 file2 = arg; 547 return (bp, REG); 548} 549 550squishspace(buf : string) : string 551{ 552 q:=0; 553 p:=0; 554 for (space := 0; q<len buf; q++) { 555 if (buf[q]==' ' || buf[q]=='\t') { 556 space++; 557 continue; 558 } 559 if (space && bflag == 1) { 560 buf[p] = ' '; 561 p++; 562 space = 0; 563 } 564 buf[p]=buf[q]; 565 p++; 566 } 567 buf=buf[0:p]; 568 return buf; 569} 570 571 572# need to fix up for unexpected EOF's 573 574ftell(b: ref Iobuf): int 575{ 576 return int b.offset(); 577} 578 579check(bf, bt : ref Iobuf) 580{ 581 fbuf, tbuf : string; 582 f:=1; 583 t:=1; 584 ixold[0] = ixnew[0] = 0; 585 for (; f < ilen[0]; f++) { 586 fbuf = bf.gets('\n'); 587 if (fbuf!=nil) 588 fbuf=fbuf[0:len fbuf -1]; 589 ixold[f] = ftell(bf); 590 if (J[f] == 0) 591 continue; 592 tbuflen: int; 593 do { 594 tbuf = bt.gets('\n'); 595 if (tbuf!=nil) 596 tbuf=tbuf[0:len tbuf -1]; 597 tbuflen = len array of byte tbuf; 598 ixnew[t] = ftell(bt); 599 } while (t++ < J[f]); 600 if (bflag) { 601 fbuf = squishspace(fbuf); 602 tbuf = squishspace(tbuf); 603 } 604 if (len fbuf != len tbuf || fbuf!=tbuf) 605 J[f] = 0; 606 } 607 while (t < ilen[1]) { 608 tbuf = bt.gets('\n'); 609 if (tbuf!=nil) 610 tbuf=tbuf[0:len tbuf -1]; 611 ixnew[t] = ftell(bt); 612 t++; 613 } 614} 615 616range(a, b : int, separator : string) 617{ 618 if (a>b) 619 out.puts(sys->sprint("%d", b)); 620 else 621 out.puts(sys->sprint("%d", a)); 622 if (a < b) 623 out.puts(sys->sprint("%s%d", separator, b)); 624} 625 626fetch(f : array of int, a,b : int , bp : ref Iobuf, s : string) 627{ 628 buf : string; 629 bp.seek(big f[a-1], 0); 630 while (a++ <= b) { 631 buf=bp.gets('\n'); 632 out.puts(s); 633 out.puts(buf); 634 } 635} 636 637change(a, b, c, d : int) 638{ 639 if (a > b && c > d) 640 return; 641 anychange = 1; 642 if (mflag && firstchange == 0) { 643 out.puts(sys->sprint( "diff %s %s\n", file1, file2)); 644 firstchange = 1; 645 } 646 if (mode != 'f') { 647 range(a, b, ","); 648 if (a>b) 649 out.putc('a'); 650 else if (c>d) 651 out.putc('d'); 652 else 653 out.putc('c'); 654 if (mode != 'e') 655 range(c, d, ","); 656 } 657 else { 658 if (a>b) 659 out.putc('a'); 660 else if (c>d) 661 out.putc('d'); 662 else 663 out.putc('c'); 664 range(a, b, " "); 665 } 666 out.putc('\n'); 667 if (mode == 0) { 668 fetch(ixold, a, b, input[0], "< "); 669 if (a <= b && c <= d) 670 out.puts("---\n"); 671 } 672 if (mode==0) 673 fetch(ixnew, c, d, input[1], "> "); 674 else 675 fetch(ixnew, c, d, input[1], ""); 676 677 if (mode != 0 && c <= d) 678 out.puts(".\n"); 679} 680 681 682######################### diffdir starts here ...... 683 684scandir(name : string) : array of string 685{ 686 (db,nitems):= readdir->init(name,Readdir->NAME); 687 cp := array[nitems] of string; 688 for(i:=0;i<nitems;i++) 689 cp[i]=db[i].name; 690 return cp; 691} 692 693 694diffdir(f, t : string, level : int) 695{ 696 df, dt : array of string; 697 fb, tb : string; 698 i:=0; 699 j:=0; 700 df = scandir(f); 701 dt = scandir(t); 702 while ((i<len df) || (j<len dt)) { 703 if ((j==len dt) || (i<len df && df[i] < dt[j])) { 704 if (mode == 0) 705 out.puts(sys->sprint("Only in %s: %s\n", f, df[i])); 706 i++; 707 continue; 708 } 709 if ((i==len df) || (j<len dt && df[i] > dt[j])) { 710 if (mode == 0) 711 out.puts(sys->sprint("Only in %s: %s\n", t, dt[j])); 712 j++; 713 continue; 714 } 715 fb=sys->sprint("%s/%s", f, df[i]); 716 tb=sys->sprint("%s/%s", t, dt[j]); 717 diff(fb, tb, level+1); 718 i++; j++; 719 } 720} 721 722cmp(b0, b1: ref Iobuf): int 723{ 724 b0.seek(big 0, Sys->SEEKSTART); 725 b1.seek(big 0, Sys->SEEKSTART); 726 buf0 := array[1024] of byte; 727 buf1 := array[1024] of byte; 728 for (;;) { 729 n0 := b0.read(buf0, len buf0); 730 n1 := b1.read(buf1, len buf1); 731 732 if (n0 != n1) 733 return 1; 734 735 if (n0 == 0) 736 return 0; 737 738 for (i := 0; i < n0; i++) 739 if (buf0[i] != buf1[i]) 740 return 1; 741 } 742} 743 744################## main from here..... 745 746REGULAR_FILE(s : Sys->Dir) : int 747{ 748 # both pipes and networks contain non-zero-length files 749 # which are not seekable. 750 return (s.qid.qtype&Sys->QTDIR) == 0 && 751 s.dtype != '|' && 752 s.dtype != 'I'; 753# && s.length > 0; device files have zero length. 754} 755 756rmtmpfiles() 757{ 758 while (whichtmp > 0) { 759 whichtmp--; 760 sys->remove(tmpname[whichtmp]); 761 } 762} 763 764mktmpfile(inputf : ref Sys->FD) : (string, Sys->Dir) 765{ 766 i, j : int; 767 sb : Sys->Dir; 768 p : string; 769 buf := array[8192] of byte; 770 771 p = tmpname[whichtmp++]; 772 fd := sys->create(p, Sys->OWRITE, 8r600); 773 if (fd == nil) { 774 error(sys->sprint("cannot create %s: %r", p)); 775 return (nil, sb); 776 } 777 while ((i = sys->read(inputf, buf, len buf)) > 0) { 778 if ((i = sys->write(fd, buf, i)) < 0) 779 break; 780 } 781 (j,sb)=sys->fstat(fd); 782 if (i < 0 || j < 0) { 783 error(sys->sprint("cannot read/write %s: %r", p)); 784 return (nil, sb); 785 } 786 return (p, sb); 787} 788 789 790statfile(file : string) : (string,Sys->Dir) 791{ 792 (ret,sb):=sys->stat(file); 793 if (ret==-1) { 794 if (file == "-") { 795 (ret,sb)= sys->fstat(sys->fildes(0)); 796 if (ret == -1) { 797 error(sys->sprint("cannot stat %s: %r", file)); 798 return (nil,sb); 799 } 800 } 801 (file, sb) = mktmpfile(sys->fildes(0)); 802 } 803 else if (!REGULAR_FILE(sb) && !(sb.qid.qtype&Sys->QTDIR)) { 804 if ((i := sys->open(file, Sys->OREAD)) == nil) { 805 error(sys->sprint("cannot open %s: %r", file)); 806 return (nil, sb); 807 } 808 (file, sb) = mktmpfile(i); 809 } 810 return (file,sb); 811} 812 813diff(f, t : string, level : int) 814{ 815 fp,tp,p,rest,fb,tb : string; 816 fsb, tsb : Sys->Dir; 817 (fp,fsb) = statfile(f); 818 if (fp == nil) 819 return; 820 (tp,tsb) = statfile(t); 821 if (tp == nil) 822 return; 823 if ((fsb.qid.qtype&Sys->QTDIR) && (tsb.qid.qtype&Sys->QTDIR)) { 824 if (rflag || level == 0) 825 diffdir(fp, tp, level); 826 else 827 out.puts(sys->sprint("Common subdirectories: %s and %s\n", fp, tp)); 828 } 829 else if (REGULAR_FILE(fsb) && REGULAR_FILE(tsb)){ 830 diffreg(fp, tp); 831 } else { 832 if (!(fsb.qid.qtype&Sys->QTDIR)) { 833 (p,rest)=str->splitr(f,"/"); 834 if (rest!=nil) 835 p = rest; 836 tb=sys->sprint("%s/%s", tp, p); 837 diffreg(fp, tb); 838 } 839 else { 840 (p,rest)=str->splitr(t,"/"); 841 if (rest!=nil) 842 p = rest; 843 fb=sys->sprint("%s/%s", fp, p); 844 diffreg(fb, tp); 845 } 846 } 847} 848 849fatal(s: string) 850{ 851 sys->fprint(stderr, "diff: %s\n", s); 852 raise "fail:error"; 853} 854 855error(s: string) 856{ 857 sys->fprint(stderr, "diff: %s\n", s); 858} 859