ropipe/ropipe.c

225 lines
5.9 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <ctype.h>
#define MAX_ROMAN_LEN 77 // 76 characters + 1 for the null terminator
#define MAX_INT_LEN 6 // 65535 (5 characters) + 1 for the null terminator
#define NUM_SYMBOLS 13
const uint16_t values[NUM_SYMBOLS] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
const char *symbols[NUM_SYMBOLS] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
const uint8_t symbols_len[NUM_SYMBOLS] = {1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1};
void print_usage(const char *program_name)
{
printf("Usage: %s [options] [input]\n", program_name);
printf("Options:\n");
printf(" -r: Convert a Roman numeral to an integer\n");
printf(" -s: Fails silently, with no output\n");
printf(" -k: Don't quit on invalid input\n");
printf(" -q: Accept quirky Roman numerals like IIII for IV (only valid with -r)\n");
printf(" -h: Display this help message\n");
}
void int_to_roman(uint16_t num, char result[MAX_ROMAN_LEN])
{
if (num == 0)
return;
char *result_ptr = result;
for (uint8_t i = 0; i < NUM_SYMBOLS; i++)
{
while (num >= values[i])
{
num -= values[i];
result_ptr += sprintf(result_ptr, "%s", symbols[i]);
}
}
}
void upper(char *str)
{
for (char *p = str; *p; p++)
{
*p = toupper(*p);
}
}
bool roman_to_int(char roman[MAX_ROMAN_LEN], uint16_t *result, bool allow_quirky)
{
*result = 0;
upper(roman);
const char *roman_ptr = roman;
int repetitions = 0;
char last_char = '\0';
for (uint8_t i = 0; i < NUM_SYMBOLS; i++)
{
while (strncmp(roman_ptr, symbols[i], symbols_len[i]) == 0)
{
// Validate repetition of the same numeral
if (!allow_quirky)
{
if (last_char == symbols[i][0])
{
repetitions++;
if ((symbols_len[i] == 1 && repetitions >= 3 && symbols[i][0] != 'M') ||
(symbols_len[i] == 2 && repetitions >= 1))
{
return false; // Invalid due to too many repetitions
}
}
else
{
repetitions = 0;
}
}
*result += values[i];
roman_ptr += symbols_len[i];
last_char = symbols[i][0];
}
}
return *roman_ptr == '\0';
}
typedef uint8_t flag;
#define ROMAN_TO_INT 0
#define INT_TO_ROMAN 1
#define SILENT 0
#define VERBOSE 1
#define QUIT 0
#define NO_QUIT 1
int handle_int_to_roman(flag verbosity, flag quit)
{
char line[MAX_INT_LEN];
while (fgets(line, MAX_INT_LEN, stdin) != NULL)
{
char *endptr;
errno = 0;
long num = strtol(line, &endptr, 10);
if (errno != 0 || (*endptr != '\n' && *endptr != '\0') || num <= 0 || num > UINT16_MAX)
{
if (verbosity == VERBOSE)
{
fprintf(stderr, "Error: input must be a non-zero positive integer less than or equal to %d\n", UINT16_MAX);
}
if (quit == QUIT)
{
return 1;
}
}
else
{
char result[MAX_ROMAN_LEN];
int_to_roman(num, result);
printf("%s\n", result);
}
}
return 0;
}
int handle_roman_to_int(flag verbosity, flag quit, bool allow_quirky)
{
char line[MAX_ROMAN_LEN];
while (fgets(line, MAX_ROMAN_LEN, stdin) != NULL)
{
line[strcspn(line, "\n")] = '\0';
uint16_t result;
if (!roman_to_int(line, &result, allow_quirky))
{
if (verbosity == VERBOSE)
{
fprintf(stderr, "Error: input must be a valid Roman numeral\n");
}
if (quit == QUIT)
{
return 1;
}
}
else
{
printf("%d\n", result);
}
}
return 0;
}
int main(int argc, char *argv[])
{
flag mode = INT_TO_ROMAN;
flag verbosity = VERBOSE;
flag quit = QUIT;
bool allow_quirky = false;
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
char *arg = argv[i];
if (arg[0] == '-')
{
for (int j = 1; arg[j] != '\0'; j++)
{
switch (arg[j])
{
case 'r':
mode = ROMAN_TO_INT;
break;
case 's':
verbosity = SILENT;
break;
case 'k':
quit = NO_QUIT;
break;
case 'q':
if (mode == ROMAN_TO_INT)
{
allow_quirky = true;
}
else
{
fprintf(stderr, "Error: Option -q is only valid with -r (Roman to integer conversion)\n");
return 1;
}
break;
case 'h':
print_usage(argv[0]);
return 0;
default:
fprintf(stderr, "Error: Unknown option '%c'\n", arg[j]);
return 1;
}
}
}
else
{
fprintf(stderr, "Error: Unknown argument '%s'\n", arg);
return 1;
}
}
}
switch (mode)
{
case INT_TO_ROMAN:
return handle_int_to_roman(verbosity, quit);
case ROMAN_TO_INT:
return handle_roman_to_int(verbosity, quit, allow_quirky);
default:
fprintf(stderr, "Invalid mode\n");
return 1;
}
}