7
\$\begingroup\$

I am learning C, and wrote this pig-latin translator.

If no argument is given, it operates on standard input. Otherwise, it operates on the arguments given.

Here are some examples:

$ echo "The quick brown fox jumps over Vladmir's lazy young Yggdrasil!" | ./piglatin
Ethay ickquay ownbray oxfay umpsjay overway Admir'svlay azylay oungyay Yggdrasilway!

$ ./piglatin "Now is the time, for all good men to come to the aid of their party"
Ownay isway ethay imetay, orfay allway oodgay enmay otay omecay otay ethay aidway ofway eirthay artypay


#include <err.h>
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define SUFFIXC  "ay"
#define SUFFIXV  "way" 

static void usage(void);
static void piglatin(const char *s);
static int isvowel(const char *s);

/* Encodes English-language phrases into Pig Latin. */
int
main(int argc, char *argv[])
{
    int c;

    while ((c = getopt(argc, argv, "h")) != -1) {
        switch (c) {
        case 'h':
        default:
            usage();
            break;
        }
    }
    argc -= optind;
    argv += optind;

    if (argc == 0) {
        char *line = NULL;
        size_t linesize = 0;
        ssize_t linelen;

        while ((linelen = getline(&line, &linesize, stdin)) != -1) {
            if (line[linelen - 1] == '\n')
                line[linelen - 1] = '\0';
            piglatin(line);
        }
        free(line);
        if (ferror(stdin))
            err(EXIT_FAILURE, "stdin");
    } else {
        while (argc-- > 0)
            piglatin(*argv++);
    }
    if (ferror(stdout))
        err(EXIT_FAILURE, "stdout");

    return EXIT_SUCCESS;
}

/* translate s into Pig Latin */
static void
piglatin(const char *s)
{
    const char *p, *onset, *root, *end, *suffix;
    bool upper;

    while (*s != '\0') {
        while (!isalpha(*s) && *s != '\'' && *s != '\0')
            putchar(*s++);

        upper = false;
        if (isupper(*s))
            upper = true;

        if (*s == '\0')
            break;

        onset = s;

        while (isalpha(*s) && !isvowel(s)) {
            if (*s == 'q' && *(s+1) == 'u')
                s++;
            s++;
        }
        root = s;

        while (isalpha(*s) || *s == '\'')
            s++;
        end = s;

        suffix = (onset == root) ? SUFFIXV : SUFFIXC;

        for (p = root; p != end; p++) {
            if (p == root && upper)
                putchar(toupper(*p));
            else
                putchar(*p);
        }

        for (p = onset; p != root; p++) {
            if (p == onset && upper)
                putchar(tolower(*p));
            else
                putchar(*p);
        }

        printf("%s", suffix);

    }

     printf("\n");
}

/* test if first letter of s is a vowel */
static int
isvowel(const char *s)
{
    switch (tolower(*s)) {
    case 'a': case 'e': case 'i': case 'o': case 'u':
        return 1;
    case 'y':
        return (isvowel(s+1) ? 0 : 1);
    }
    return 0;
}

static void
usage(void)
{
    (void) fprintf(stderr, "usage: piglatin [phrase...]\n");
    exit(EXIT_FAILURE);
}
\$\endgroup\$
4
\$\begingroup\$

Inadvertent case dependency?

Should not the below code also work with 'Q', 'U'?

if (*s == 'q' && *(s+1) == 'u')

char functions (advanced issue)`

The Std C library functions treat char * data as if it was accessed by unsigned char *.

This is important in select cases.

tolower(int ch), is...() are well defined for values in the unsigned char range and EOF, else UB. When *s < 0, tolower(*s) is a problem. Casting to (unsigned char) fixes that.

isvowel(const char *s) {
  // switch (tolower(*s)) {
  switch (tolower((unsigned char) *s)) {

With some extended ASCII encoding this is useful, yet with UTF8, the issue moot as tolower() is wholly inadequate.


Deeper pedantic detail

(unsigned char) *s is the wrong solution with non-2's complement and signed char (not seen these days) as code should access the data an unsigned char.

isvowel(const char *s) {
  const char *us = (const char *) s;
  switch (tolower(*us)) {

With 2's complement the result is the same with either approach. IAC, I doubt much C code these days would survive a new non-2's complement integer encoding so no real reason to worry about this deep detail.

Formatting

Code lacks {} in simple blocks. I find the later style easier to review and maintain. As with such style issues, code to your group's coding standard.

} else {
    while (argc-- > 0)
        piglatin(*argv++);
}

vs.

} else {
    while (argc-- > 0) {
        piglatin(*argv++); 
    }
}

Consider local variables, initialization and code reduction

//bool upper;
//...
//    upper = false;
//    if (isupper(*s))
//        upper = true;
      bool upper = isupper(*s);
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.