implement Wc; # # wc -- count things in utf-encoded text files # Bugs: # The only white space characters recognized are ' ', '\t' and '\n', even though # ISO 10646 has many more blanks scattered through it. # Should count characters that cannot occur in any rune (hex f0-ff) separately. # Should count non-canonical runes (e.g. hex c1,80 instead of hex 40). # include "sys.m"; sys: Sys; include "draw.m"; Wc: module { init: fn(ctxt: ref Draw->Context, args: list of string); }; NBUF: con 8*1024; stderr: ref Sys->FD; nline, tnline, pline: int; nword, tnword, pword: int; nchar, tnchar, pchar: int; nbadr, tnbadr, pbadr: int; nbyte, tnbyte, pbyte: int; init(nil: ref Draw->Context, argv: list of string) { sys = load Sys Sys->PATH; stderr = sys->fildes(2); for(argv = tl argv; argv != nil; argv = tl argv){ arg := hd argv; if(len arg < 2 || arg[0] != '-' || arg[1] == '-') break; for(i := 1; i < len arg; i++){ case arg[i]{ 'l' => pline++; 'w' => pword++; 'c' => pchar++; 'e' => pbadr++; 'b' => pbyte++; * => sys->fprint(stderr, "usage: wc [-lwcbe] [file ...]\n"); exit; } } } if(pline+pword+pchar+pbadr+pbyte == 0) pline = pword = pchar = 1; argc := len argv; if(argc == 0) count(sys->fildes(0), ""); else{ for(; argv != nil; argv = tl argv){ name := hd argv; f := sys->open(name, sys->OREAD); if(f == nil) sys->fprint(stderr, "wc: can't open %s: %r\n", name); else{ count(f, name); tnline += nline; tnword += nword; tnchar += nchar; tnbadr += nbadr; tnbyte += nbyte; f = nil; } } if(argc > 1) report(tnline, tnword, tnchar, tnbadr, tnbyte, "total"); } exit; } report(nline, nword, nchar, nbadr, nbyte: int, fname: string) { line := ""; if(pline) line += sys->sprint(" %7d", nline); if(pword) line += sys->sprint(" %7d", nword); if(pchar) line += sys->sprint(" %7d", nchar); if(pbadr) line += sys->sprint(" %7d", nbadr); if(pbyte) line += sys->sprint(" %7d", nbyte); if(fname != nil) line += sys->sprint(" %s", fname); sys->print("%s\n", line[1:]); } # # How it works. Start in statesp. Each time we read a character, # increment various counts, and do state transitions according to the # following table. If we're not in statesp or statewd when done, the # file ends with a partial rune. # | character # state |09,20| 0a |00-7f|80-bf|c0-df|e0-ef|f0-ff # -------+-----+-----+-----+-----+-----+-----+----- # statesp|ASP |ASPN |AWDW |AWDWX|AC2W |AC3W |AWDWX # statewd|ASP |ASPN |AWD |AWDX |AC2 |AC3 |AWDX # statec2|ASPX |ASPNX|AWDX |AWDR |AC2X |AC3X |AWDX # statec3|ASPX |ASPNX|AWDX |AC2R |AC2X |AC3X |AWDX # # actions AC2, # enter statec2 AC2R, # enter statec2, don't count a rune AC2W, # enter statec2, count a word AC2X, # enter statec2, count a bad rune AC3, # enter statec3 AC3W, # enter statec3, count a word AC3X, # enter statec3, count a bad rune ASP, # enter statesp ASPN, # enter statesp, count a newline ASPNX, # enter statesp, count a newline, count a bad rune ASPX, # enter statesp, count a bad rune AWD, # enter statewd AWDR, # enter statewd, don't count a rune AWDW, # enter statewd, count a word AWDWX, # enter statewd, count a word, count a bad rune AWDX: # enter statewd, count a bad rune con byte iota; statesp := array[256] of{ # looking for the start of a word AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 00-07 AWDW, ASP, ASPN, AWDW, AWDW, AWDW, AWDW, AWDW, # 08-0f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 10-17 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 18-1f ASP, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 20-27 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 28-2f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 30-37 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 38-3f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 40-47 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 48-4f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 50-57 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 58-5f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 60-67 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 68-6f AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 70-77 AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, AWDW, # 78-7f AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 80-87 AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 88-8f AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 90-97 AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# 98-9f AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a0-a7 AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# a8-af AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b0-b7 AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# b8-bf AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c0-c7 AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # c8-cf AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d0-d7 AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, AC2W, # d8-df AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e0-e7 AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, AC3W, # e8-ef AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f0-f7 AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,AWDWX,# f8-ff }; statewd := array[256] of { # looking for the next character in a word AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 00-07 AWD, ASP, ASPN, AWD, AWD, AWD, AWD, AWD, # 08-0f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 10-17 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 18-1f ASP, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 20-27 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 28-2f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 30-37 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 38-3f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 40-47 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 48-4f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 50-57 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 58-5f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 60-67 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 68-6f AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 70-77 AWD, AWD, AWD, AWD, AWD, AWD, AWD, AWD, # 78-7f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 80-87 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 88-8f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 90-97 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 98-9f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a0-a7 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # a8-af AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b0-b7 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # b8-bf AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c0-c7 AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # c8-cf AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d0-d7 AC2, AC2, AC2, AC2, AC2, AC2, AC2, AC2, # d8-df AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e0-e7 AC3, AC3, AC3, AC3, AC3, AC3, AC3, AC3, # e8-ef AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff }; statec2 := array[256] of { # looking for 10xxxxxx to complete a rune AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07 AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 80-87 AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 88-8f AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 90-97 AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # 98-9f AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a0-a7 AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # a8-af AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b0-b7 AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, AWDR, # b8-bf AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7 AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7 AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7 AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff }; statec3 := array[256] of { # looking for 10xxxxxx,10xxxxxx to complete a rune AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 00-07 AWDX, ASPX, ASPNX,AWDX, AWDX, AWDX, AWDX, AWDX, # 08-0f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 10-17 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 18-1f ASPX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 20-27 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 28-2f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 30-37 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 38-3f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 40-47 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 48-4f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 50-57 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 58-5f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 60-67 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 68-6f AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 70-77 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # 78-7f AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 80-87 AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 88-8f AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 90-97 AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # 98-9f AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a0-a7 AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # a8-af AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b0-b7 AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, AC2R, # b8-bf AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c0-c7 AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # c8-cf AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d0-d7 AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, AC2X, # d8-df AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e0-e7 AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, AC3X, # e8-ef AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f0-f7 AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, AWDX, # f8-ff }; buf := array[NBUF] of byte; count(f: ref Sys->FD, name: string) { state := statesp; nline = nword = nchar = nbadr = nbyte = 0; n := 0; for(;;){ n = sys->read(f, buf, NBUF); if(n <= 0) break; nbyte += n; nchar += n; # might be too large, gets decreased later i := 0; do{ case int state[int buf[i++]]{ int AC2 => state = statec2; int AC2R => state = statec2; nchar--; int AC2W => state = statec2; nword++; int AC2X => state = statec2; nbadr++; int AC3 => state = statec3; int AC3W => state = statec3; nword++; int AC3X => state = statec3; nbadr++; int ASP => state = statesp; int ASPN => state = statesp; nline++; int ASPNX => state = statesp; nline++; nbadr++; int ASPX => state = statesp; nbadr++; int AWD => state = statewd; int AWDR => state = statewd; nchar--; int AWDW => state = statewd; nword++; int AWDWX => state = statewd; nword++; nbadr++; int AWDX => state = statewd; nbadr++; } }while(i < n); } if(state!=statesp && state!=statewd) nbadr++; if(n < 0) sys->fprint(stderr, "error reading %s: %r\n", name); report(nline, nword, nchar, nbadr, nbyte, name); }