Alexander Gromnitsky's Blog

say(6) from v10 Research UNIX

Latest update:

The source code for Tenth-Edition Research UNIX (1989) contains the file games/say.c (timestamp is 1984-04-27, Friday). It's not a speech synthesiser, but a simple little program that uses a pairs of

"EVERY MAN","HAS A PRICE",
"ANYTHING","GOES"

to generate new wisdom like "Anything has a price." Practically, a LLM (no).

For some unknown reason, the man page for it is located in the file man/mana/fortune.6:

Those old parts are entertaining enough to be read on their own, e.g.:

"CANDY","IS DANDY BUT LIQUOR IS QUICKER",
"A PLUMP BRIDE","MAKES A FAT WIFE",
"A ROSE BY ANY OTHER NAME","WOULD SMELL AS SWEET",
"TIME AND TIDE","WAITS FOR NO MAN",

The full list (185 pairs) is here. The main() is somewhat cryptic, as if somebody was training for the first IOCCC. Although GCC 13 still compiles it, say.c may print the same 'proverb' multiple times if its argv[1] is big enough.

I attempted to rewrite the game in a more 'modern' style in order to address the issue of the 'same proverb' repetition, but the code didn't become shorter. The relevant part:

char *PAIRS[] = {
"A ROLLING STONE","GATHERS NO MOSS",
"HISTORY","REPEATS ITSELF",
…
};

typedef struct { int left, right; } Proverb;

void mk_proverbs(char **src, int src_len, Proverb *dest) {
  int dest_idx = 0;
  for (int left = 0; left < src_len; left += 2) {
    for (int right = 1; right < src_len; right += 2) {
      if (right == left + 1) continue; /* ignore true wisdom */
      dest[dest_idx].left = left;
      dest[dest_idx].right = right;
      dest_idx++;
    }
  }
}

void shuffle(Proverb *arr, int size) {
  for (int i = size - 1; i > 0; i--) {
    int j = random() % (i + 1);
    Proverb temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
}

void lowercase(char *dest, char *src, int skip) {
  while ( (*dest++ = skip-- < 1 ? tolower(*src++) : *src++))
    ;
}

void print(char **src, Proverb *proverbs, int slice_len) {
  for (int idx = 0; idx < slice_len; ++idx) {
    Proverb p = proverbs[idx];
    char left[strlen(src[p.left])+1]; lowercase(left, src[p.left], 1);
    char right[strlen(src[p.right])+1]; lowercase(right, src[p.right], 0);
    printf("%s %s.\n", left, right);
  }
}

int main(int argc, char **argv) {
  srandom(time(0));
  int pairs_len = sizeof(PAIRS) / sizeof(PAIRS[0]);
  int templates_len = pairs_len / 2;
  int proverbs_len = templates_len * (templates_len - 1); /* max possible */

  int n = atoi(argc > 1 ? argv[1] : "1");
  if (n < 1 ) n = 1;
  if (n > proverbs_len) n = proverbs_len;

  Proverb proverbs[proverbs_len];
  mk_proverbs(PAIRS, pairs_len, proverbs);
  shuffle(proverbs, proverbs_len);
  print(PAIRS, proverbs, n);
}

It generates all the possible combinations (34040), shuffles them, and prints a slice.

$ ./new-say 5
Procrastination is not seemly for a fool.
The lion is the last refuge of the unimaginative.
Life makes waste.
Bad breath doesn't love a wall.
A friend in need is more precious than rubies.

Old vs. new in terms of lines of code:

$ echo say.c new-say.c | xargs -n1 scc | grep ^C | awk '{print $6}'
232
237

Can you make it shorter while still retaining readability?


Tags: ойті
Authors: ag