master xplshn/aruu / cmd / posix / tr.c
  1/* See LICENSE file for copyright and license details. */
  2
  3#include <stdlib.h>
  4
  5#include "utf.h"
  6#include "util.h"
  7
  8static int cflag = 0;
  9static int dflag = 0;
 10static int sflag = 0;
 11
 12struct range {
 13  Rune   start;
 14  Rune   end;
 15  size_t quant;
 16};
 17
 18static struct {
 19  char *name;
 20  int (*check)(Rune);
 21} classes[] = {
 22    {"alnum", isalnumrune},
 23    {"alpha", isalpharune},
 24    {"blank", isblankrune},
 25    {"cntrl", iscntrlrune},
 26    {"digit", isdigitrune},
 27    {"graph", isgraphrune},
 28    {"lower", islowerrune},
 29    {"print", isprintrune},
 30    {"punct", ispunctrune},
 31    {"space", isspacerune},
 32    {"upper", isupperrune},
 33    {"xdigit", isxdigitrune},
 34};
 35
 36#define ISLOWERBIT 1U << 6
 37#define ISUPPERBIT 1U << 10
 38
 39static struct range *set1       = NULL;
 40static size_t        set1ranges = 0;
 41static unsigned      set1checks = 0;
 42static struct range *set2       = NULL;
 43static size_t        set2ranges = 0;
 44static unsigned      set2checks = 0;
 45
 46static int
 47check(Rune rune, unsigned checks)
 48{
 49  size_t i;
 50
 51  for (i = 0; checks && i < LEN(classes); i++, checks >>= 1)
 52    if (checks & 1 && classes[i].check(rune))
 53      return 1;
 54
 55  return 0;
 56}
 57
 58static size_t
 59rangelen(struct range r)
 60{
 61  return (r.end - r.start + 1) * r.quant;
 62}
 63
 64static size_t
 65setlen(struct range *set, size_t setranges)
 66{
 67  size_t len = 0, i;
 68
 69  for (i = 0; i < setranges; i++)
 70    len += rangelen(set[i]);
 71
 72  return len;
 73}
 74
 75static int
 76rstrmatch(Rune *r, char *s, size_t n)
 77{
 78  size_t i;
 79
 80  for (i = 0; i < n; i++)
 81    if (r[i] != s[i])
 82      return 0;
 83  return 1;
 84}
 85
 86static size_t
 87makeset(char *str, struct range **set, unsigned *checks)
 88{
 89  Rune  *rstr;
 90  size_t len, i, j, m, n;
 91  size_t q, setranges = 0;
 92  int    factor, base;
 93
 94  /* rstr defines at most len ranges */
 95  unescape(str);
 96  rstr = ereallocarray(NULL, utflen(str) + 1, sizeof(*rstr));
 97  len  = utftorunestr(str, rstr);
 98  *set = ereallocarray(NULL, len, sizeof(**set));
 99
100  for (i = 0; i < len; i++) {
101    if (rstr[i] == '[') {
102      j = i;
103    nextbrack:
104      if (j >= len)
105        goto literal;
106      for (m = j; m < len; m++)
107        if (rstr[m] == ']') {
108          j = m;
109          break;
110        }
111      if (j == i)
112        goto literal;
113
114      /* CLASSES [=EQUIV=] (skip) */
115      if (j - i > 3 && rstr[i + 1] == '=' && rstr[m - 1] == '=') {
116        if (j - i != 4)
117          goto literal;
118        (*set)[setranges].start = rstr[i + 2];
119        (*set)[setranges].end   = rstr[i + 2];
120        (*set)[setranges].quant = 1;
121        setranges++;
122        i = j;
123        continue;
124      }
125
126      /* CLASSES [:CLASS:] */
127      if (j - i > 3 && rstr[i + 1] == ':' && rstr[m - 1] == ':') {
128        for (n = 0; n < LEN(classes); n++) {
129          if (rstrmatch(rstr + i + 2, classes[n].name, j - i - 3)) {
130            *checks |= 1 << n;
131            i = j;
132            break;
133          }
134        }
135        if (n < LEN(classes))
136          continue;
137        eprintf("Invalid character class.\n");
138      }
139
140      /* REPEAT  [_*n] (only allowed in set2) */
141      if (j - i > 2 && rstr[i + 2] == '*') {
142        /* check if right side of '*' is a number */
143        q      = 0;
144        factor = 1;
145        base   = (rstr[i + 3] == '0') ? 8 : 10;
146        for (n = j - 1; n > i + 2; n--) {
147          if (rstr[n] < '0' || rstr[n] > '9') {
148            n = 0;
149            break;
150          }
151          q += (rstr[n] - '0') * factor;
152          factor *= base;
153        }
154        if (n == 0) {
155          j = m + 1;
156          goto nextbrack;
157        }
158        (*set)[setranges].start = rstr[i + 1];
159        (*set)[setranges].end   = rstr[i + 1];
160        (*set)[setranges].quant = q ? q : setlen(set1, MAX(set1ranges, 1));
161        setranges++;
162        i = j;
163        continue;
164      }
165
166      j = m + 1;
167      goto nextbrack;
168    }
169  literal:
170    /* RANGES [_-__-_], _-__-_ */
171    /* LITERALS _______ */
172    (*set)[setranges].start = rstr[i];
173
174    if (i < len - 2 && rstr[i + 1] == '-' && rstr[i + 2] >= rstr[i])
175      i += 2;
176    (*set)[setranges].end   = rstr[i];
177    (*set)[setranges].quant = 1;
178    setranges++;
179  }
180
181  free(rstr);
182  return setranges;
183}
184
185static void
186usage(void)
187{
188  eprintf("usage: %s [-cCds] set1 [set2]\n", argv0);
189}
190
191// ?man tr: translate characters
192// ?man arguments: set1 [set2]
193// ?man translate, squeeze, or delete characters from standard input
194int
195main(int argc, char *argv[])
196{
197  Rune   r, lastrune = 0;
198  size_t off1, off2, i, m;
199  int    ret = 0;
200
201  ARGBEGIN
202  {
203    // ?man -c: print count or perform stdout action
204    case 'c':
205    // ?man -C: specify option flag
206    case 'C':
207      cflag = 1;
208      break;
209    // ?man -d: specify directory
210    case 'd':
211      dflag = 1;
212      break;
213    // ?man -s: silent mode or print summary
214    case 's':
215      sflag = 1;
216      break;
217    default:
218      usage();
219  }
220  ARGEND
221
222  if (!argc || argc > 2 || (dflag == sflag && argc != 2) || (dflag && argc != 1))
223    usage();
224
225  set1ranges = makeset(argv[0], &set1, &set1checks);
226  if (argc == 2) {
227    set2ranges = makeset(argv[1], &set2, &set2checks);
228    /* sanity checks as we are translating */
229    if (!set2ranges && !set2checks)
230      eprintf("cannot map to an empty set.\n");
231    if (set2checks && set2checks != ISLOWERBIT && set2checks != ISUPPERBIT) {
232      eprintf("can only map to 'lower' and 'upper' class.\n");
233    }
234  }
235read:
236  if (!efgetrune(&r, stdin, "<stdin>")) {
237    ret |= fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>");
238    return ret;
239  }
240  if (argc == 1 && sflag)
241    goto write;
242  for (i = 0, off1 = 0; i < set1ranges; off1 += rangelen(set1[i]), i++) {
243    if (set1[i].start <= r && r <= set1[i].end) {
244      if (dflag) {
245        if (cflag)
246          goto write;
247        else
248          goto read;
249      }
250      if (cflag)
251        goto write;
252
253      /* map r to set2 */
254      if (set2checks) {
255        if (set2checks == ISLOWERBIT)
256          r = tolowerrune(r);
257        else
258          r = toupperrune(r);
259      } else {
260        off1 += r - set1[i].start;
261        if (off1 > setlen(set2, set2ranges) - 1) {
262          r = set2[set2ranges - 1].end;
263          goto write;
264        }
265        for (m = 0, off2 = 0; m < set2ranges; m++) {
266          if (off2 + rangelen(set2[m]) > off1) {
267            m++;
268            break;
269          }
270          off2 += rangelen(set2[m]);
271        }
272        m--;
273        r = set2[m].start + (off1 - off2) / set2[m].quant;
274      }
275      goto write;
276    }
277  }
278  if (check(r, set1checks)) {
279    if (cflag)
280      goto write;
281    if (dflag)
282      goto read;
283    if (set2checks) {
284      if (set2checks == ISLOWERBIT)
285        r = tolowerrune(r);
286      else
287        r = toupperrune(r);
288    } else {
289      r = set2[set2ranges - 1].end;
290    }
291    goto write;
292  }
293  if (!dflag && cflag) {
294    if (set2checks) {
295      if (set2checks == ISLOWERBIT)
296        r = tolowerrune(r);
297      else
298        r = toupperrune(r);
299    } else {
300      r = set2[set2ranges - 1].end;
301    }
302    goto write;
303  }
304  if (dflag && cflag)
305    goto read;
306write:
307  if (argc == 1 && sflag && r == lastrune) {
308    if (check(r, set1checks))
309      goto read;
310    for (i = 0; i < set1ranges; i++) {
311      if (set1[i].start <= r && r <= set1[i].end)
312        goto read;
313    }
314  }
315  if (argc == 2 && sflag && r == lastrune) {
316    if (set2checks && check(r, set2checks))
317      goto read;
318    for (i = 0; i < set2ranges; i++) {
319      if (set2[i].start <= r && r <= set2[i].end)
320        goto read;
321    }
322  }
323  efputrune(&r, stdout, "<stdout>");
324  lastrune = r;
325  goto read;
326}