hautils

[hare] Set of POSIX utilities
Log | Files | Refs | README | LICENSE

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 };