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