wc.ha (3258B)
1 use ascii; 2 use encoding::utf8; 3 use fmt; 4 use getopt; 5 use io; 6 use main; 7 use os; 8 use strings; 9 use memio; 10 11 type items = enum uint { 12 BYTES = 1 << 0, 13 CHARS = 1 << 1, 14 LINES = 1 << 2, 15 WORDS = 1 << 3, 16 DEFAULT = BYTES | LINES | WORDS, 17 }; 18 19 type counts = struct { 20 bytes: uint, 21 chars: uint, 22 lines: uint, 23 words: uint, 24 }; 25 26 export fn utilmain() (main::error | void) = { 27 const help: []getopt::help = [ 28 "word, line, and byte or character count", 29 ('c', "count bytes"), 30 ('m', "count characters"), 31 ('l', "count lines"), 32 ('w', "count words"), 33 "[file...]", 34 ]; 35 const cmd = getopt::parse(os::args, help...); 36 defer getopt::finish(&cmd); 37 38 let mode: items = 0; 39 if (len(cmd.opts) == 0) { 40 mode = items::DEFAULT; 41 }; 42 43 for (let i = 0z; i < len(cmd.opts); i += 1) { 44 const opt = cmd.opts[i]; 45 switch (opt.0) { 46 case 'c' => 47 if (mode & items::CHARS != 0) { 48 main::usage(help, 'c', 'm'); 49 }; 50 mode |= items::BYTES; 51 case 'm' => 52 if (mode & items::BYTES != 0) { 53 main::usage(help, 'c', 'm'); 54 }; 55 mode |= items::CHARS; 56 case 'l' => 57 mode |= items::LINES; 58 case 'w' => 59 mode |= items::WORDS; 60 case => abort(); 61 }; 62 }; 63 64 if (len(cmd.args) == 0) { 65 const result = count(os::stdin, mode)?; 66 print(mode, &result, "")?; 67 } else { 68 let totals = counts { ... }; 69 for (let i = 0z; i < len(cmd.args); i += 1) { 70 // TODO: Mention which file failed on error 71 const in = os::open(cmd.args[i])?; 72 defer io::close(in)!; 73 const result = count(in, mode)?; 74 print(mode, &result, cmd.args[i])?; 75 totals.bytes += result.bytes; 76 totals.chars += result.chars; 77 totals.words += result.words; 78 totals.lines += result.lines; 79 }; 80 if (len(cmd.args) > 1) { 81 print(mode, &totals, "total")?; 82 }; 83 }; 84 }; 85 86 fn count(in: io::handle, mode: items) (counts | main::error) = { 87 static let buf: [os::BUFSZ]u8 = [0...]; 88 let result = counts { ... }; 89 for (true) { 90 const n = match (io::read(in, buf[..])?) { 91 case let n: size => 92 yield n; 93 case io::EOF => 94 break; 95 }; 96 for (let i = 0z; i < n; i += 1) { 97 switch (buf[i]) { 98 case '\n' => 99 result.lines += 1; 100 case ' ', '\t' => 101 // TODO: This is not exactly correct 102 result.words += 1; 103 case => void; 104 }; 105 result.bytes += 1; 106 }; 107 if (mode & items::CHARS > 0) { 108 countchars(&result, buf[..n]); 109 }; 110 }; 111 return result; 112 }; 113 114 fn countchars(result: *counts, buf: []u8) void = { 115 const dec = utf8::decode(buf); 116 for (true) match (utf8::next(&dec)) { 117 case let r: rune => 118 result.chars += 1; 119 case utf8::invalid => 120 fmt::fatal("Error: input has invalid UTF-8 sequence"); 121 case utf8::more => 122 abort(); // TODO 123 case void => 124 break; 125 }; 126 }; 127 128 fn print(mode: items, result: *counts, path: str) (void | main::error) = { 129 static let fmtbuf: [32]u8 = [0...]; 130 let buf = memio::fixed(fmtbuf); 131 if (mode & items::LINES > 0) { 132 memio::concat(&buf, "{0: 7} ")!; 133 }; 134 if (mode & items::WORDS > 0) { 135 memio::concat(&buf, "{1: 7} ")!; 136 }; 137 if (mode & items::BYTES > 0) { 138 memio::concat(&buf, "{2: 7} ")!; 139 }; 140 if (mode & items::CHARS > 0) { 141 memio::concat(&buf, "{3: 7} ")!; 142 }; 143 if (path != "") { 144 memio::concat(&buf, "{4}")!; 145 }; 146 const format = strings::rtrim(memio::string(&buf)!); 147 fmt::printfln(format, result.lines, result.words, 148 result.bytes, result.chars, path)?; 149 };