# Trigraph translator

Recently I thought about what a program would look like if all its characters that could become trigraphs became trigraphs. For example

int main(void)
{
int array[10];
}


would become

int main(void)
??<
int array??(10??);
??>


As a result I decided to make a program using ANSI C89 that takes a file in and converts each character to its corresponding trigraph if it has one and outputs the result to another file.

#include <errno.h>
#include <stdio.h>
#include <string.h>

#define BUFFER_SIZE 1024
#define WRITE_BUFFER(x, size)                                            \
do {                                                                 \
memcpy(buffer + BUFFER_SIZE + write_buffer_length, (x), (size)); \
write_buffer_length += (size);                                   \
} while(0)                                                           \

int main(int argc, char **argv)
{
int j;

/* ignore the first argument */
--argc;
++argv;

/* check for invalid arguments */
if(argc % 2 != 0) {
fprintf(stderr, "Error: invalid arguments\n");
fprintf(stderr, "Usage: %s input output ...\n", argv[-1]);
fprintf(stderr, "Example: %s main.c result.c \n", argv[-1]);
return -1;
}

for(j = 0; j < argc; j += 2) {
char buffer[BUFFER_SIZE + BUFFER_SIZE * 3];

if(strcmp(argv[j], argv[j + 1]) == 0) {
printf("Warning: using the same file for input and output is not supported\n");
continue;
}

/* open a file for reading */
if((read_file = fopen(argv[j], "r")) == NULL) {
fprintf(stderr, "Error: could not open %s\n%s", argv[j], strerror(errno));
return -2;
}

/* open a file for writing */
if((write_file = fopen(argv[j + 1], "w")) == NULL) {
fprintf(stderr, "Error: could not open %s\n%s", argv[j + 1], strerror(errno));
return -3;
}

/* read the file in BUFFER_SIZE chunks */
size_t i, write_buffer_length;

/* process each character in the buffer
* and if needed convert it to a trigraph
*/

write_buffer_length = 0;
for (i = 0; i < bytes_read; ++i) {
char const ch = buffer[i];

switch (ch) {
case '#':
WRITE_BUFFER("\?\?=", 3);
break;
case '[':
WRITE_BUFFER("\?\?(", 3);
break;
case ']':
WRITE_BUFFER("\?\?)", 3);
break;
case '{':
WRITE_BUFFER("\?\?<", 3);
break;
case '}':
WRITE_BUFFER("\?\?>", 3);
break;
case '\\':
WRITE_BUFFER("\?\?/", 3);
break;
case '^':
WRITE_BUFFER("\?\?'", 3);
break;
case '~':
WRITE_BUFFER("\?\?-", 3);
break;
case '|':
WRITE_BUFFER("\?\?!", 3);
break;
default:
WRITE_BUFFER(&ch, 1);
break;
}
}

fwrite(buffer + BUFFER_SIZE, 1, write_buffer_length, write_file);
}

fclose(write_file);
}

return 0;
}


Your program could be a lot shorter and simpler if you followed the "Unix philosophy."

• Read from stdin and write to stdout. This eliminates your need to process arguments, and eliminates the for(j) loop.

• Trust fread and fwrite to do I/O buffering on their own. (They do.)

Then your entire program would be something like

#include <stdio.h>

void trigraph(char c) {
putchar('?');
putchar('?');
putchar(c);
}

int main() {
int c;
while ((c = getchar()) != EOF) {
switch (c) {
case '\\': trigraph('/'); break;
case '#': trigraph('='); break;
[...]
default: putchar(c); break;
}
}
}


Then if you want to transform a bunch of files at once, that's as easy as

for i in *.c ; do ./a.out < "$i" > "${i%.c}.trigraphed.c" ; done


For more on the Unix philosophy of writing simple "pipeline" programs that compose well, I recommend the book Software Tools, by Kernighan and Plauger; or for that matter The C Programming Language, by Kernighan and Ritchie.

For an additional exercise, you might try writing a program to reverse this operation: replace every trigraph with its corresponding ASCII character.

• For fun, it would be interesting to also see this code with trigraphs applied. – chux - Reinstate Monica Oct 25 at 15:27
• Well, you can run it through itself. ;) But that does remind me: perhaps the cases should cutely be written as case '??/': trigraph('/'); break; case '??=': trigraph('='); break; and so on. This would be either more self-documenting or less self-documenting, depending on your point of view. :) – Quuxplusone Oct 25 at 15:32
• There is absolutely no reason why WRITE_BUFFER should be a macro and not a function.

• Don't do exotic stuff like --argc; ++argv;. C programmers expect argument 0 to be the name of the executable. Similarly, argc % 2 is weird, why would you check for an even amount of arguments? Check for an exact amount of arguments, no more, no less.

• Similarly, you should be able to keep all error handling out of the loops and just do it once. Keep it simple.

• I'd expect the various valid trigraph sequences to be stored in some manner of table, rather than a big switch. For example, you could sacrifice 128 bytes data for a fast lookup table based on 7 bit ASCII:

const char trigraph [128] =
{
['#'] = '=',
['['] = '(',
...
};

char in  = ... // input from file
char out = trigraph[in];
if(out != 0) // was it a candidate for trigraph replacement?
{
// printf("??%c", out); etc

• the reason I check for an even amount of arguments is so every file read has a corresponding file to write to and that will not be true when there is an odd amount of arguments. – nullptr Oct 26 at 15:00
• @nullptr And why would someone ever use the program with more than 2 files? That's not a very good UI and you gain no performance from it either, since the massive bottleneck here is the file i/o and everything else is negligible. It's a potential security hole too. – Lundin Oct 27 at 7:25