diff options
| author | user <user@clank> | 2026-09-24 22:45:04 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-24 22:45:04 +0200 |
| commit | 4cd933009dd6ce87fe710acfca6b1f786d51040c (patch) | |
| tree | 67e50c2ab347ddc2ac3e5b6a5cf5a4cfc44cc8d0 /usr/tr.c | |
| parent | fs: mount the builtins at /bin (diff) | |
| download | gbos-4cd933009dd6ce87fe710acfca6b1f786d51040c.tar.gz gbos-4cd933009dd6ce87fe710acfca6b1f786d51040c.tar.xz gbos-4cd933009dd6ce87fe710acfca6b1f786d51040c.zip | |
usr: touch, tr, rev, cut, uniq
/# ls -l /bin > b
/# cut -d \s -f 1 b | uniq -c
52 -
- touch FILE...: gbfs has no timestamps (an MBC5 cart has no RTC), so touch
does the half that means something here - make an empty file. O_APPEND is
exactly right: it creates, and it does not truncate what is already there.
- tr [-d] SET1 [SET2]: ranges (a-z), -d to delete, and a short SET2 padded
with its last character, like tr(1). ASCII only - a 128-entry map.
- rev, uniq [-c]: one line each, from a file or stdin like the other filters.
Two things the tests caught. A bare "-" is a SET, not a flag, so `tr . -`
has to skip flag-matching only when a letter follows the dash. And uniq
compared an empty "line" against the last run at EOF and flushed a phantom
blank one - a stray "1" in uniq -c output.
tr and cut also take \s \t \n \r escapes, because the shell has no quoting:
`-d ' '` arrives as three tokens, so a space or tab delimiter was simply
untypeable. Shell quoting would be the better fix; this makes the tools
usable today without touching the parser.
Banks 49-53; 10 program slots left in the 1 MB ROM.
Diffstat (limited to 'usr/tr.c')
| -rw-r--r-- | usr/tr.c | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/usr/tr.c b/usr/tr.c new file mode 100644 index 0000000..a98cf1d --- /dev/null +++ b/usr/tr.c @@ -0,0 +1,87 @@ +#include "gbos.h" +/* tr [-d] SET1 [SET2]: translate or delete characters, stdin to stdout. + * + * ls | tr a-z A-Z ranges work + * cat f | tr -d aeiou -d deletes instead of mapping + * echo a b | tr \\s \\n \\s \\t \\n name characters the shell cannot quote + * + * ASCII only (a 128-entry map on the stack), and like tr(1) a short SET2 is + * padded with its last character. */ + +#define SETMAX 64 + +/* The shell has no quoting, so a space or a tab cannot be typed as an + argument: "-d ' '" arrives as three tokens. Escapes are how you name them + here - \s space, \t tab, \n newline, \\ backslash. */ +static char unesc1(const char *s, unsigned char *used) { + *used = 1; + if (s[0] != '\\' || !s[1]) return s[0]; + *used = 2; + switch (s[1]) { + case 's': return ' '; + case 't': return '\t'; + case 'n': return '\n'; + case 'r': return '\r'; + default: return s[1]; + } +} + +/* expand "a-z0-9_" into the characters it names; returns how many */ +static unsigned char expand(const char *s, char *out, unsigned char max) { + unsigned char n = 0, i = 0; + char a, b; + while (s[i] && n < max) { + unsigned char ua, ub; + a = unesc1(s + i, &ua); + if (s[i + ua] == '-' && s[i + ua + 1]) { /* a range */ + b = unesc1(s + i + ua + 1, &ub); + while (a <= b && n < max) out[n++] = a++; + i += ua + 1 + ub; + } else { + out[n++] = a; + i += ua; + } + } + return n; +} + +void main(void) { + char *argv[8]; + char s1[SETMAX], s2[SETMAX]; + unsigned char map[128]; + unsigned char argc = argv_parse(argv, 8); + unsigned char del = hasflag(argv, argc, 'd'); + unsigned char i, n1 = 0, n2 = 0, k; + char *set1 = 0, *set2 = 0; + char c; + + for (i = 0; i < argc; i++) { + /* a bare "-" is a SET, not a flag: `tr . -` maps dots to dashes */ + if (argv[i][0] == '-' && argv[i][1]) continue; + if (!set1) set1 = argv[i]; + else if (!set2) set2 = argv[i]; + } + if (!set1 || (!del && !set2)) { + puts("usage: tr [-d] SET1 [SET2]"); + nl(); + sexit(2); + } + n1 = expand(set1, s1, SETMAX); + if (set2) n2 = expand(set2, s2, SETMAX); + + for (i = 0; i < 128; i++) map[i] = i; /* identity, then patch */ + for (k = 0; k < n1; k++) { + unsigned char from = (unsigned char)s1[k] & 127; + if (del) map[from] = 0; /* 0 = drop it */ + else map[from] = (unsigned char)s2[k < n2 ? k : n2 - 1]; + } + + for (;;) { + c = readc(); + if (c == EOF) break; + if ((unsigned char)c < 128) { + unsigned char o = map[(unsigned char)c]; + if (o) putc((char)o); + } else putc(c); + } +} |
