aboutsummaryrefslogtreecommitdiffstats
path: root/usr/tr.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-09-24 22:45:04 +0200
committeruser <user@clank>2026-09-24 22:45:04 +0200
commit4cd933009dd6ce87fe710acfca6b1f786d51040c (patch)
tree67e50c2ab347ddc2ac3e5b6a5cf5a4cfc44cc8d0 /usr/tr.c
parentfs: mount the builtins at /bin (diff)
downloadgbos-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.c87
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);
+ }
+}