lepa

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

commit d5165f64c63b582d0e65e33a1f1f0f0644853669
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Mon,  2 May 2022 16:41:16 +0200

Initial commit

Diffstat:
AREADME | 16++++++++++++++++
Aexample.txt | 9+++++++++
Alepa.ha | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 240 insertions(+), 0 deletions(-)

diff --git a/README b/README @@ -0,0 +1,16 @@ +This simple program, which I wrote mainly as an excuse to try out the Hare +programming language [1], reads a list of letter pairs from a file, then +prompts the user for commands. The interface is similar to that of ed(1). +The main command 'r' starts a random training session, which consists of +the program printing a random letter pair, waiting for the use to type +enter, print the text associated with the letter pair, waiting again and +repeating until EOF (Ctrl+D) is encountered. + +To try it out run: + +hare run lepa.ha example.txt + +Then type 'r' followed by Enter to start the random training session. +Other commands include '?' (help) and 'q' (quit). + +[1] https://harelang.org diff --git a/example.txt b/example.txt @@ -0,0 +1,9 @@ +# This line is a comment. Comments and white lines are ignored. + +AB Abacus +BB BB King +CG Cigarette +HR Hare +BD Beedi (TODO: change this, too similar to CG) + +# And so on... diff --git a/lepa.ha b/lepa.ha @@ -0,0 +1,215 @@ +// Usage: lepa FILE +// This is a simple program that reads from FILE lines starting with a letter +// pair, that is a word consisting of two capital letters such as XY, saves +// them in a table and lets the user access and visualize them in different +// ways. It is intended to be a tool to train a memory scheme for letter pair +// (e.g. for memory sports), but I mostly wrote it to try out Hare. + +use ascii; +use bufio; +use encoding::utf8; +use fmt; +use fs; +use io; +use math::random; +use os; +use strings; +use time; + +type notlp = !void; + +def N: u32 = 26; +let table: [N][N]str = [[""...]...]; +const commands: [_](str, *fn() void, str) = [ + ("r", &dorand, "Start random training session"), + ("?", &printhelp, "Print help"), + ("q", &quit, "Quit"), +]; + +// Start random training session +fn dorand() void = { + let pairs: [N*N](u32, u32) = [(0, 0)...]; + let k = makeallpairs(pairs[0..N*N]); + let ran = math::random::init(time::unix(time::now(0)): u64); + for (true) { + const p = pairs[math::random::u32n(&ran, k)]; + fmt::printfln("{}", pairstr(p))!; + if (waitline()) return; + fmt::printfln("{}", table[p.0][p.1])!; + if (waitline()) return; + }; +}; + +// Print help +fn printhelp() void = { + fmt::printfln("Available commands:")!; + for (let i: size = 0; i < len(commands); i += 1) + fmt::printfln("{}\t{}", commands[i].0, commands[i].2)!; +}; + +// Quit +fn quit() void = os::exit(0); + +fn mainloop() void = { + for (true) { + let line: str = match(bufio::scanline(os::stdin)) { + case io::error => + fmt::fatal("Error during io"); + case io::EOF => + fmt::printfln("^D")!; + os::exit(0); + case let line: []u8 => + yield strings::fromutf8(line); + }; + let i: size = 0; + for (i < len(commands); i += 1) { + if (line == commands[i].0) { + commands[i].1(); + break; + }; + }; + if (i == len(commands)) + fmt::printfln("?")!; + free(line); + }; +}; + +// Determines if c is one of the N characters declared as letters +fn isvalidle(c: rune) bool = ascii::isupper(c); + +// Index corresponding to character c in the table +fn index(c: rune) u32 = (c: u32) - ('A': u32); + +// Character corresponding to index i in the table +fn rundex(i: u32) rune = (i + ('A': u32)): rune; + +// Rune to string +// TODO is there really no standard library function for this? +fn runetostring(r: rune) str = { + let arr: []u8 = encoding::utf8::encoderune(r); + return strings::fromutf8(arr); +}; + +// Pair of indexes to string +fn pairstr(p: (u32, u32)) str = { + const s1 = strings::dup(runetostring(rundex(p.0: u32))); + const s2 = strings::dup(runetostring(rundex(p.1: u32))); + return strings::concat(s1, s2); +}; + +// Read the table of letter pairs from file +fn read(file: (io::file | fs::error)) void = { + const f = match(file) { + case let f: io::file => + yield f; + case let e: fs::error => + fmt::fatal("Error reading file: {}", fs::strerror(e)); + }; + const lines = match(io::drain(f)) { + case let lines: []u8 => + yield strings::fromutf8(lines); + case io::error => + fmt::fatal("Error reading table"); + }; + const lines = strings::split(lines, "\n"); + + for (let i = 0z; i < len(lines); i += 1) { + const runes: []rune = strings::runes(lines[i]); + if (len(lines[i]) == 0 || runes[0] == '#') + continue; + const c1 = runes[0]; + const c2 = runes[1]; + if (!(isvalidle(c1) && isvalidle(c2))) + fmt::fatal("Error reading line: {}", lines[i]); + let j: size = 2; + for (j < len(runes) && ascii::isspace(runes[j])) + j += 1; + const word: str = strings::sub(lines[i], j, strings::end); + if (len(word) > 0) + table[index(c1)][index(c2)] = strings::dup(word); + free(runes); + }; + free(lines); +}; + +// Make list of all non-empty pairs +fn makeallpairs(allpairs: [](u32, u32)) u32 = { + let k: u32 = 0; + for (let i: u32 = 0; i < N; i += 1) { + for (let j: u32 = 0; j < N; j += 1) { + if (table[i][j] != "") { + allpairs[k] = (i, j); + k += 1; + }; + }; + }; + return k; +}; + +// Print one entry of the tabl +fn printentry(p: (str | (u32, u32))) (void | notlp) = { + const ind: (u32, u32) = match(p) { + case let pair: (u32, u32) => + yield pair; + case let s: str => + yield if (len(s) != 2) { + return notlp; + } else { + const runes = strings::runes(s); + yield (index(runes[0]), index(runes[1])); + }; + }; + const i = ind.0; + const j = ind.1; + if (table[i][j] != "") { + const r1: rune = rundex(i: u32); + const r2: rune = rundex(j: u32); + fmt::printfln("{}{}:\t{}", r1, r2, table[i][j])!; + }; +}; + +// Print one line of the table +fn printline(c: (u32 | rune)) void = { + const i: u32 = match(c) { + case let i: u32 => + yield i; + case let r: rune => + yield index(r); + }; + fmt::printfln("{}:", rundex(i))!; + for (let j: size = 0; j < len(table[i]); j += 1) { + // TODO: not ignore error? + printentry((i: u32, j: u32))!; + }; +}; + +// Print all non-empty letter pairs +fn printall() void = { + fmt::println("List of pairs:")!; + for (let i: size = 0; i < len(table); i += 1) { + printline(i: u32); + fmt::println("")!; + }; +}; + +// Read a line from standard input, return true if EOF +fn waitline() bool = { + match(bufio::scanline(os::stdin)) { + case io::error => + fmt::fatal("Error during io"); + case []u8 => + return false; + case io::EOF => + fmt::printfln("^D")!; + return true; + }; +}; + +export fn main() void = { + if (len(os::args) != 2) + fmt::fatal("Usage: {} file", os::args[0]); + + read(os::open(os::args[1])); + + mainloop(); +};