lepa

A letter-pair practice tool for memory sports or similar
git clone https://git.tronto.net/lepa
Download | Log | Files | Refs | README

lepa.ha (5278B)


      1 // Usage: lepa FILE
      2 // This is a simple program that reads from FILE lines starting with a letter
      3 // pair, that is a word consisting of two capital letters such as XY, saves
      4 // them in a table and lets the user access and visualize them in different
      5 // ways. It is intended to be a tool to train a memory scheme for letter pair
      6 // (e.g. for memory sports), but I mostly wrote it to try out Hare.
      7 
      8 use ascii;
      9 use bufio;
     10 use encoding::utf8;
     11 use fmt;
     12 use fs;
     13 use io;
     14 use math::random;
     15 use os;
     16 use strings;
     17 use time;
     18 
     19 type notlp = !void;
     20 
     21 def N: u32 = 26;
     22 let table: [N][N]str = [[""...]...];
     23 const commands: [_](str, *fn() void, str) = [
     24 	("r", &dorand, "Start random training session"),
     25 	("?", &printhelp, "Print help"),
     26 	("q", &quit, "Quit"),
     27 ];
     28 
     29 // Start random training session
     30 fn dorand() void = {
     31 	let pairs: [N*N](u32, u32) = [(0, 0)...];
     32 	let k = makeallpairs(pairs[0..N*N]);
     33 	let ran = math::random::init(time::unix(time::now(0)): u64);
     34 	for (true) {
     35 		const p = pairs[math::random::u32n(&ran, k)];
     36 		fmt::printfln("{}", pairstr(p))!;
     37 		if (waitline()) return;
     38 		fmt::printfln("{}", table[p.0][p.1])!;
     39 		if (waitline()) return;
     40 	};
     41 };
     42 
     43 // Print help
     44 fn printhelp() void = {
     45 	fmt::printfln("Available commands:")!;
     46 	for (let i: size = 0; i < len(commands); i += 1)
     47 		fmt::printfln("{}\t{}", commands[i].0, commands[i].2)!;
     48 };
     49 
     50 // Quit
     51 fn quit() void = os::exit(0);
     52 
     53 fn mainloop() void = {
     54 	for (true) {
     55 		let line: str = match(bufio::scanline(os::stdin)) {
     56 		case io::error =>
     57 			fmt::fatal("Error during io");
     58 		case io::EOF =>
     59 			fmt::printfln("^D")!;
     60 			os::exit(0);
     61 		case let line: []u8 =>
     62 			yield strings::fromutf8(line);
     63 		};
     64 		let i: size = 0;
     65 		for (i < len(commands); i += 1) {
     66 			if (line == commands[i].0) {
     67 				commands[i].1();
     68 				break;
     69 			};
     70 		};
     71 		if (i == len(commands))
     72 			fmt::printfln("?")!;
     73 		free(line);
     74 	};
     75 };
     76 
     77 // Determines if c is one of the N characters declared as letters
     78 fn isvalidle(c: rune) bool = ascii::isupper(c);
     79 
     80 // Index corresponding to character c in the table
     81 fn index(c: rune) u32 = (c: u32) - ('A': u32);
     82 
     83 // Character corresponding to index i in the table
     84 fn rundex(i: u32) rune = (i + ('A': u32)): rune;
     85 
     86 // Rune to string
     87 // TODO is there really no standard library function for this?
     88 fn runetostring(r: rune) str = {
     89 	let arr: []u8 = encoding::utf8::encoderune(r);
     90 	return strings::fromutf8(arr);
     91 };
     92 
     93 // Pair of indexes to string
     94 fn pairstr(p: (u32, u32)) str = {
     95 	const s1 = strings::dup(runetostring(rundex(p.0: u32)));
     96 	const s2 = strings::dup(runetostring(rundex(p.1: u32)));
     97 	return strings::concat(s1, s2);
     98 };
     99 
    100 // Read the table of letter pairs from file
    101 fn read(file: (io::file | fs::error)) void = {
    102 	const f = match(file) {
    103 	case let f: io::file =>
    104 		yield f;
    105 	case let e: fs::error =>
    106 		fmt::fatal("Error reading file: {}", fs::strerror(e));
    107 	};
    108 	const lines = match(io::drain(f)) {
    109 	case let lines: []u8 =>
    110 		yield strings::fromutf8(lines);
    111 	case io::error =>
    112 		fmt::fatal("Error reading table");
    113 	};
    114 	const lines = strings::split(lines, "\n");
    115 
    116 	for (let i = 0z; i < len(lines); i += 1) {
    117 		const runes: []rune = strings::runes(lines[i]);
    118 		if (len(lines[i]) == 0 || runes[0] == '#')
    119 			continue;
    120 		const c1 = runes[0];
    121 		const c2 = runes[1];
    122 		if (!(isvalidle(c1) && isvalidle(c2)))
    123 			fmt::fatal("Error reading line: {}", lines[i]);
    124 		let j: size = 2;
    125 		for (j < len(runes) && ascii::isspace(runes[j]))
    126 			j += 1;
    127 		const word: str = strings::sub(lines[i], j, strings::end);
    128 		if (len(word) > 0)
    129 			table[index(c1)][index(c2)] = strings::dup(word);
    130 		free(runes);
    131 	};
    132 	free(lines);
    133 };
    134 
    135 // Make list of all non-empty pairs
    136 fn makeallpairs(allpairs: [](u32, u32)) u32 = {
    137 	let k: u32 = 0;
    138 	for (let i: u32 = 0; i < N; i += 1) {
    139 		for (let j: u32 = 0; j < N; j += 1) {
    140 			if (table[i][j] != "") {
    141 				allpairs[k] = (i, j);
    142 				k += 1;
    143 			};
    144 		};
    145 	};
    146 	return k;
    147 };
    148 
    149 // Print one entry of the tabl
    150 fn printentry(p: (str | (u32, u32))) (void | notlp) = {
    151 	const ind: (u32, u32) = match(p) {
    152 	case let pair: (u32, u32) =>
    153 		yield pair;
    154 	case let s: str =>
    155 		yield if (len(s) != 2) {
    156 			return notlp;
    157 		} else {
    158 			const runes = strings::runes(s);
    159 			yield (index(runes[0]), index(runes[1]));
    160 		};
    161 	};
    162 	const i = ind.0;
    163 	const j = ind.1;
    164 	if (table[i][j] != "") {
    165 		const r1: rune = rundex(i: u32);
    166 		const r2: rune = rundex(j: u32);
    167 		fmt::printfln("{}{}:\t{}", r1, r2, table[i][j])!;
    168 	};
    169 };
    170 
    171 // Print one line of the table
    172 fn printline(c: (u32 | rune)) void = {
    173 	const i: u32 = match(c) {
    174 	case let i: u32 =>
    175 		yield i;
    176 	case let r: rune =>
    177 		yield index(r);
    178 	};
    179 	fmt::printfln("{}:", rundex(i))!;
    180 	for (let j: size = 0; j < len(table[i]); j += 1) {
    181 		// TODO: not ignore error?
    182 		printentry((i: u32, j: u32))!;
    183 	};
    184 };
    185 
    186 // Print all non-empty letter pairs
    187 fn printall() void = {
    188 	fmt::println("List of pairs:")!;
    189 	for (let i: size = 0; i < len(table); i += 1) {
    190 		printline(i: u32);
    191 		fmt::println("")!;
    192 	};
    193 };
    194 
    195 // Read a line from standard input, return true if EOF
    196 fn waitline() bool = {
    197 	match(bufio::scanline(os::stdin)) {
    198 	case io::error =>
    199 		fmt::fatal("Error during io");
    200 	case []u8 =>
    201 		return false;
    202 	case io::EOF =>
    203 		fmt::printfln("^D")!;
    204 		return true;
    205 	};
    206 };
    207 
    208 export fn main() void = {
    209 	if (len(os::args) != 2)
    210 		fmt::fatal("Usage: {} file", os::args[0]);
    211 
    212 	read(os::open(os::args[1]));
    213 
    214 	mainloop();
    215 };