1 /* This is prints roman numerals.
2  * Inspired by http://www.differentpla.net/node/view/58
3  * Requires GMP
4  */
5 
6 /* we only need output here, so turn off other IO functions */
7 #define EX_UTILS_NO_USE_INPUT 1
8 #define EX_UTILS_NO_USE_OPEN 1
9 #include "ex_utils.h"
10 
11 #include <limits.h>
12 #include <gmp.h>
13 
14 /* do we want to test that it works? */
15 #define EX_ROMAN_USE_TST 0
16 
17 #define ROMAN_MAKE(num, out)                        \
18     {num, out, sizeof(out) - 1}                     \
19 
20 
roman_conv(Vstr_base * s1,size_t pos,const mpz_t num)21 static size_t roman_conv(Vstr_base *s1, size_t pos, const mpz_t num)
22 {
23   struct Roman_conv
24   {
25    const unsigned int num;
26    const char *str;
27    const size_t len;
28   };
29 
30   static struct Roman_conv conv[] =
31     {
32      ROMAN_MAKE(1000, "M"),
33      ROMAN_MAKE(900,  "CM"),
34      ROMAN_MAKE(500,  "D"),
35      ROMAN_MAKE(400,  "CD"),
36      ROMAN_MAKE(100,  "C"),
37      ROMAN_MAKE(90,   "XC"),
38      ROMAN_MAKE(50,   "L"),
39      ROMAN_MAKE(40,   "XL"),
40      ROMAN_MAKE(10,   "X"),
41      ROMAN_MAKE(9,    "IX"),
42      ROMAN_MAKE(5,    "V"),
43      ROMAN_MAKE(4,    "IV"),
44      ROMAN_MAKE(1,    "I"),
45     };
46   const struct Roman_conv *scan = conv;
47   unsigned int alen = sizeof(conv) / sizeof(conv[0]);
48   size_t ret = 0;
49   mpz_t tmp;
50 
51   mpz_init_set(tmp, num);
52   mpz_abs(tmp, tmp);
53 
54   while (alen)
55   {
56     while (mpz_cmp_ui(tmp, scan->num) >= 0)
57     {
58       mpz_sub_ui(tmp, tmp, scan->num);
59 
60       if (s1 && !vstr_add_buf(s1, pos, scan->str, scan->len))
61         return (0);
62 
63       pos += scan->len;
64       ret += scan->len;
65     }
66 
67     --alen;
68     ++scan;
69   }
70 
71 
72   return (ret);
73 }
74 
75 /* This is the custom formatter. */
ex__usr_roman_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec,const mpz_t val)76 static int ex__usr_roman_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec,
77                             const mpz_t val)
78 {
79   int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NUM; /* it's a number */
80   size_t len = 0;
81 
82   if (mpz_sgn(val) == -1)
83     flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NEG;
84 
85   len = roman_conv(NULL, 0, val);
86 
87   /* Not handled atm. */
88   spec->fmt_quote = 0;
89 
90   if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags))
91     return (FALSE);
92 
93   if (!roman_conv(base, pos, val))
94     return (FALSE);
95 
96   if (!vstr_sc_fmt_cb_end(base, pos, spec, len))
97     return (FALSE);
98 
99   return (TRUE);
100 }
101 
102 /* Extra, do binary output... */
ex_usr_roman_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec)103 static int ex_usr_roman_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec)
104 {
105   void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0);
106 
107   return (ex__usr_roman_cb(base, pos, spec, mpz));
108 }
109 
main(int argc,char * argv[])110 int main(int argc, char *argv[])
111 {
112   Vstr_base *s1 = ex_init(NULL);
113   mpz_t bignum;
114 
115   if (argc < 2)
116     errx(EXIT_FAILURE, "No count specified");
117 
118   vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
119   if (!vstr_sc_fmt_add_vstr(s1->conf, "{vstr:%p%zu%zu%u}") ||
120       !vstr_fmt_add(s1->conf, "<roman:%p>", ex_usr_roman_cb,
121                     VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END))
122     errno = ENOMEM, err(EXIT_FAILURE, "Custom formatters");
123 
124 #if EX_ROMAN_USE_TST
125   {
126     struct Roman_tst_conv
127     {
128      const char *i;
129      const char *o;
130     }
131     tsts[] =
132       {
133        {"0",   ""},
134        {"1",   "I"},
135        {"2",   "II"},
136        {"3",   "III"},
137        {"4",   "IV"},
138        {"5",   "V"},
139        {"6",   "VI"},
140        {"7",   "VII"},
141        {"8",   "VIII"},
142        {"9",   "IX"},
143        {"10",  "X"},
144        {"11",  "XI"},
145        {"12",  "XII"},
146        {"13",  "XIII"},
147        {"14",  "XIV"},
148        {"15",  "XV"},
149        {"16",  "XVI"},
150        {"17",  "XVII"},
151        {"18",  "XVIII"},
152        {"19",  "XIX"},
153        {"20",  "XX"},
154        {"21",  "XXI"},
155        {"29",  "XXIX"},
156        {"30",  "XXX"},
157        {"34",  "XXXIV"},
158        {"40",  "XL"},
159        {"50",  "L"},
160        {"60",  "LX"},
161        {"70",  "LXX"},
162        {"80",  "LXXX"},
163        {"90",  "XC"},
164        {"100", "C"},
165        {"190", "CXC"},
166        {"200", "CC"},
167        {"300", "CCC"},
168        {"400", "CD"},
169        {"500", "D"},
170        {"600", "DC"},
171        {"700", "DCC"},
172        {"800", "DCCC"},
173        {"900", "CM"},
174        {"1000", "M"},
175 
176        {"1900", "MCM"},
177        {"1975", "MCMLXXV"},
178        {"1989", "MCMLXXXIX"},
179        {"1999", "MCMXCIX"},
180        {"2000", "MM"},
181        {"2001", "MMI"},
182 #define M10() "MMMMMMMMMM"
183 #define M100()    M10() M10() M10() M10() M10() M10() M10() M10() M10() M10()
184 #define M1_000()  M100()M100()M100()M100()M100()M100()M100()M100()M100()M100()
185 #define M10_000() M1_000()M1_000()M1_000()M1_000()M1_000() \
186                   M1_000()M1_000()M1_000()M1_000()M1_000()
187 #define M100_000() M10_000()M10_000()M10_000()M10_000()M10_000() \
188                    M10_000()M10_000()M10_000()M10_000()M10_000()
189 #define M1_000_000() M100_000()M100_000()M100_000()M100_000()M100_000() \
190                      M100_000()M100_000()M100_000()M100_000()M100_000()
191 #define M10_000_000() M1_000_000()M1_000_000()M1_000_000()M1_000_000()M1_000_000() \
192                       M1_000_000()M1_000_000()M1_000_000()M1_000_000()M1_000_000()
193        {"10004", M10() "IV"},
194        {"12345", M10() "MMCCCXLV"},
195        {"10000000053", M10_000_000() "LIII"},
196       };
197     unsigned int alen = sizeof(tsts) / sizeof(tsts[0]);
198     struct Roman_tst_conv *scan = tsts;
199 
200 
201     while (alen)
202     {
203       mpz_init_set_str(bignum, scan->i, 0);
204 
205       vstr_del(s1, 1, s1->len);
206       vstr_add_fmt(s1, s1->len, "$<roman:%p>", (void *)bignum);
207 
208       if (!vstr_cmp_cstr_eq(s1, 1, s1->len, scan->o))
209         errx(EXIT_FAILURE, "Tst failed(%s): %s",
210              scan->o, vstr_export_cstr_ptr(s1, 1, s1->len));
211 
212       --alen;
213       ++scan;
214     }
215   }
216 #endif
217 
218   mpz_init_set_str(bignum, argv[1], 0);
219 
220   vstr_del(s1, 1, s1->len);
221   vstr_add_fmt(s1, s1->len, " Input: %s\n", argv[1]);
222   vstr_add_fmt(s1, s1->len, " Roman: $<roman:%p>\n", (void *)bignum);
223 
224   if (s1->conf->malloc_bad)
225     errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
226 
227   io_put_all(s1, STDOUT_FILENO);
228 
229   exit (ex_exit(s1, NULL));
230 }
231