-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcb.c
179 lines (164 loc) · 5.43 KB
/
cb.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright (c) 2022 Michael Breen (https://mbreen.com)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char* program_name;
// Usage message split on the program name.
static const char* const usage[] = {
"Usage: ", " A B\n\n"
"Read whole numbers in base A and output in base B.\n"
"The alphabet used is the environment variable DIGITS.\n"
"DIGITS must be printable ASCII and contain at least as\n"
"many characters as A or B, whichever is greater.\n"
"If DIGITS is not set, the default alphabet is\n"
"0123456789abcdefghijklmnopqrstuvwxyz\n\n"
"Examples:\n"
" # convert hexadecimal to decimal:\n"
" echo deadbeef | ", " 16 10\n"
" # convert a date to vigesimal, custom alphabet:\n"
" export DIGITS=0123456789cjzwfsbvxq\n"
" echo 2021-12-25 | ", " 10 20\n\n"
"Note: Only whole numbers are supported. Punctuation\n"
"characters not present in the alphabet are treated as\n"
"separators between numbers.\n\n"
"Author: Michael Breen (https://mbreen.com)"
// VERSION
};
// Output the program name followed by msg and '\n' to stderr.
static void error_msg(const char *msg) {
fputs(program_name, stderr);
fputs(": ", stderr);
fputs(msg, stderr);
fputs("\n", stderr);
}
// Exit with Usage, optionally preceded by an error message.
static void exit_usage(const char* msg) {
if (msg)
error_msg(msg);
size_t i = 0;
for (; i < sizeof(usage)/sizeof(usage[0]) - 1; ++i) {
fputs(usage[i], stderr);
fputs(program_name, stderr);
}
fputs(usage[i], stderr);
exit(1);
}
static const char* get_alphabet(void) {
const char *digchars = getenv("DIGITS");
if (!digchars || !digchars[0])
return "0123456789abcdefghijklmnopqrstuvwxyz";
return digchars;
}
// Report an error in the alphabet and exit.
static void err_digits(const char* msg) {
error_msg(msg);
fputs("DIGITS: ", stderr);
fputs(get_alphabet(), stderr);
fputs("\n\n", stderr);
exit_usage(NULL);
}
static void overflow(void) {
error_msg("overflow");
exit(2);
}
#define isalpha(x) (((c)|0x20) >= 'a' && ((c)|0x20) <= 'z')
#define isdecimal(x) ((unsigned) (x) - '0' < 10)
// Numeric value of digit or radix. For a digit, -1 means "none".
typedef signed char digval;
// Convert an argument of 1 or 2 decimal digits to a number.
static digval arg2num(char *arg) {
if (!isdecimal(arg[0]))
exit_usage("arguments must be 1 or 2 decimal digits");
int val = arg[0] - '0';
if (isdecimal(arg[1]) && !arg[2])
val = val * 10 + arg[1] - '0';
else if (arg[1])
exit_usage("arguments must be 1 or 2 decimal digits");
return (digval) val;
}
// Report printable/DEL/non-ASCII character as invalid and exit.
static void invalid_input_digit(int c) {
fputs(program_name, stderr);
fputs(": ", stderr);
fputs("input character ",stderr);
if (c < 0x7f) {
fputs("'", stderr);
fputc(c, stderr);
fputs("' not in alphabet\n", stderr);
} else {
fputs("outside ASCII range\n", stderr);
}
exit(1);
}
#define MAX_LEN 4096
typedef struct {
digval radix;
int len;
digval digits[MAX_LEN]; // least significant first
} numbr;
// Make *num = *num * factor + addend.
static void mult_add(numbr *num, digval factor, digval addend) {
int j, carry = addend;
for (j = 0; carry || (factor && j < num->len); ++j) {
if (j < num->len)
carry += num->digits[j] * factor;
else if (j >= MAX_LEN)
overflow();
num->digits[j] = (digval) (carry % num->radix);
carry /= num->radix;
}
num->len = j;
}
static numbr out;
int main(int argc, char **argv) {
program_name = argv[0];
for (const char *ip = program_name; *ip; ++ip)
if (*ip == '/')
program_name = ip + 1;
if (argc != 3)
exit_usage(NULL);
digval base_in = arg2num(argv[1]);
out.len = 0;
out.radix = arg2num(argv[2]);
if (base_in < 2 || out.radix < 2)
exit_usage("minimum base 2");
const char *digit_chars = get_alphabet();
digval digit_vals[0x80];
for (int j = 0; j < 0x80; ++j)
digit_vals[j] = -1;
for (digval v = 0; (size_t) v < strlen(digit_chars); ++v) {
int i = digit_chars[v];
if (i < ' ' || i > '~')
err_digits("$DIGITS must be printable ASCII");
if (digit_vals[i] != -1)
err_digits("digit repeated in $DIGITS");
digit_vals[i] = v;
}
if ((int) strlen(digit_chars) < (
base_in > out.radix ? base_in : out.radix))
err_digits("$DIGITS too short");
int digit_read = 0;
for (;;) {
int c = getchar();
digval val = c & 0x80 ? -1 : digit_vals[c];
if (val >= 0 && val < base_in) {
// Accumulate number.
mult_add(&out, base_in, val);
digit_read = 1;
} else {
if (isalpha(c) || isdecimal(c) || c >= 0x7f)
invalid_input_digit(c);
// Output accumulated number, if any.
if (digit_read) {
if (!out.len)
out.digits[out.len++] = 0;
for (int j = out.len - 1; j >= 0; --j)
putchar(digit_chars[out.digits[j]]);
digit_read = out.len = 0;
}
if (c == EOF)
return 0;
putchar(c);
}
}
}