1// This file is part of SmallBASIC
2//
3// formating numbers and strings
4//
5// This program is distributed under the terms of the GPL v2.0 or later
6// Download the GNU Public License (GPL) from www.gnu.org
7//
8// Copyright(C) 2000 Nicholas Christopoulos
9
10#include "common/sys.h"
11#include "common/str.h"
12#include "common/fmt.h"
13#include "common/device.h"
14#include "common/pproc.h"
15#include "common/messages.h"
16#include "common/blib_math.h"
17
18#if defined OS_PREC64
19// limits for use with 64bit integer or 64bit fp algorithm
20#define FMT_xMIN 1e-8
21#define FMT_xMAX 1e+14
22#define FMT_RND 14
23#define FMT_xRND 1e+14
24#define FMT_xRND2 1e+13
25#else
26// limits for use with 32bit integer algorithm
27#define FMT_xMIN 1e-8 // lowest limit to use the exp. format
28#define FMT_xMAX 1e+9 // highest limit to use the exp. format
29#define FMT_RND 9 // rounding on x digits
30#define FMT_xRND 1e+9 // 1 * 10 ^ FMT_RND
31#define FMT_xRND2 1e+8 // 1 * 10 ^ (FMT_RND-1)
32#endif
33
34// PRINT USING; format-list
35#define MAX_FMT_N 128
36
37void bestfta_p(var_num_t x, char *dest, var_num_t minx, var_num_t maxx);
38void fmt_nmap(int dir, char *dest, char *fmt, char *src);
39void fmt_omap(char *dest, const char *fmt);
40int fmt_cdig(char *fmt);
41char *fmt_getnumfmt(char *dest, char *source);
42char *fmt_getstrfmt(char *dest, char *source);
43void fmt_addfmt(const char *fmt, int type);
44void fmt_printL(int output, intptr_t handle);
45
46typedef struct {
47 char *fmt; // the format or a string
48 int type; // 0 = string, 1 = numeric format, 2 = string format
49} fmt_node_t;
50
51static fmt_node_t fmt_stack[MAX_FMT_N]; // the list
52static int fmt_count; // number of elements in the list
53static int fmt_cur; // next format element to be used
54
55/*
56 * tables of powers :)
57 */
58static double nfta_eplus[] = {
59 1e+8, 1e+16, 1e+24, 1e+32, 1e+40, 1e+48, 1e+56, 1e+64, // 8
60 1e+72, 1e+80, 1e+88, 1e+96, 1e+104, 1e+112, 1e+120, 1e+128, // 16
61 1e+136, 1e+144, 1e+152, 1e+160, 1e+168, 1e+176, 1e+184, 1e+192, // 24
62 1e+200, 1e+208, 1e+216, 1e+224, 1e+232, 1e+240, 1e+248, 1e+256, // 32
63 1e+264, 1e+272, 1e+280, 1e+288, 1e+296, 1e+304 // 38
64};
65
66static double nfta_eminus[] = {
67 1e-8, 1e-16, 1e-24, 1e-32, 1e-40, 1e-48, 1e-56, 1e-64, // 8
68 1e-72, 1e-80, 1e-88, 1e-96, 1e-104, 1e-112, 1e-120, 1e-128, // 16
69 1e-136, 1e-144, 1e-152, 1e-160, 1e-168, 1e-176, 1e-184, 1e-192, // 24
70 1e-200, 1e-208, 1e-216, 1e-224, 1e-232, 1e-240, 1e-248, 1e-256, // 32
71 1e-264, 1e-272, 1e-280, 1e-288, 1e-296, 1e-304 // 38
72};
73
74/*
75 * Part of floating point to string (by using integers) algorithm
76 * where x any number 2^31 > x >= 0
77 */
78void fptoa(var_num_t x, char *dest) {
79 dest[0] = '\0';
80 sprintf(dest, VAR_INT_NUM_FMT, x);
81}
82
83/*
84 * Convert to text then remove righmost zeroes from the string
85 */
86void fptoa_rmzeros(var_num_t x, char *dest) {
87 fptoa(x, dest);
88 int end = strlen(dest);
89 while (end > 0 && dest[end - 1] == '0') {
90 end--;
91 }
92 dest[end] = '\0';
93}
94
95/*
96 * best float to string (lib)
97 *
98 * This is the real float-to-string routine.
99 * It used by the routines:
100 * bestfta(double x, char *dest)
101 * expfta(double x, char *dest)
102 */
103void bestfta_p(var_num_t x, char *dest, var_num_t minx, var_num_t maxx) {
104 var_num_t ipart, fpart, fdif;
105 var_int_t power = 0;
106 int sign, i;
107 char *d = dest;
108 char buf[64];
109
110 memset(buf, 0, sizeof(buf));
111
112 if (fabsl(x) == 0.0) {
113 strcpy(dest, "0");
114 return;
115 }
116
117 // find sign
118 sign = sgn(x);
119 if (sign < 0) {
120 *d++ = '-';
121 }
122 x = fabsl(x);
123
124 if (x >= 1E308) {
125 *d = '\0';
126 strcat(d, WORD_INF);
127 return;
128 } else if (x <= 1E-307) {
129 *d = '\0';
130 strcat(d, "0");
131 return;
132 }
133
134 // find power
135 if (x < minx) {
136 for (i = 37; i >= 0; i--) {
137 if (x < nfta_eminus[i]) {
138 x *= nfta_eplus[i];
139 power = -((i + 1) * 8);
140 } else {
141 break;
142 }
143 }
144
145 while (x < 1.0 && power > -307) {
146 x *= 10.0;
147 power--;
148 }
149 } else if (x > maxx) {
150 for (i = 37; i >= 0; i--) {
151 if (x > nfta_eplus[i]) {
152 x /= nfta_eplus[i];
153 power = ((i + 1) * 8);
154 } else {
155 break;
156 }
157 }
158
159 while (x >= 10.0 && power < 308) {
160 x /= 10.0;
161 power++;
162 }
163 }
164
165 // format left part
166 ipart = fabsl(fint(x));
167 fpart = fround(frac(x), FMT_RND) * FMT_xRND;
168 if (fpart >= FMT_xRND) { // rounding bug
169 ipart = ipart + 1.0;
170 if (ipart >= maxx) {
171 ipart = ipart / 10.0;
172 power++;
173 }
174 fpart = 0.0;
175 }
176
177 fptoa(ipart, buf);
178 strcpy(d, buf);
179 d += strlen(buf);
180
181 if (fpart > 0.0) {
182 // format right part
183 *d++ = '.';
184
185 fdif = frac(x) * FMT_xRND;
186 if (fdif < fpart) {
187 // rounded value has greater precision
188 fdif = fpart;
189 }
190
191 while (fdif < FMT_xRND2) {
192 fdif *= 10;
193 *d++ = '0';
194 }
195
196 fptoa_rmzeros(fpart, buf);
197 strcpy(d, buf);
198 d += strlen(buf);
199 }
200
201 if (power) {
202 // add the power
203 *d++ = 'E';
204 if (power > 0) {
205 *d++ = '+';
206 }
207 fptoa(power, buf);
208 strcpy(d, buf);
209 d += strlen(buf);
210 }
211
212 // finish
213 *d = '\0';
214}
215
216/*
217 * best float to string (user)
218 */
219void bestfta(var_num_t x, char *dest) {
220 bestfta_p(x, dest, FMT_xMIN, FMT_xMAX);
221}
222
223/*
224 * float to string (user, E mode)
225 */
226void expfta(var_num_t x, char *dest) {
227 bestfta_p(x, dest, 1.0, 1.0);
228 if (strchr(dest, 'E') == NULL) {
229 strcat(dest, "E+0");
230 }
231}
232
233/*
234 * format: map number to format
235 *
236 * dir = direction, 1 = left to right, -1 right to left
237 */
238void fmt_nmap(int dir, char *dest, char *fmt, char *src) {
239 char *p, *d, *s;
240
241 *dest = '\0';
242 if (dir > 0) {
243 //
244 // left to right
245 //
246 p = fmt;
247 d = dest;
248 s = src;
249 while (*p) {
250 switch (*p) {
251 case '#':
252 case '^':
253 if (*s) {
254 *d++ = *s++;
255 }
256 break;
257 case '0':
258 if (*s) {
259 *d++ = *s++;
260 } else {
261 *d++ = '0';
262 }
263 break;
264 default:
265 *d++ = *p;
266 }
267 p++;
268 }
269 *d = '\0';
270 } else {
271 //
272 // right to left
273 //
274 p = fmt + (strlen(fmt) - 1);
275 d = dest + (strlen(fmt) - 1);
276 *(d + 1) = '\0';
277 s = src + (strlen(src) - 1);
278 while (p >= fmt) {
279 switch (*p) {
280 case '#':
281 case '^':
282 if (s >= src) {
283 *d-- = *s--;
284 } else {
285 *d-- = ' ';
286 }
287 break;
288 case '0':
289 if (s >= src) {
290 *d-- = *s--;
291 } else {
292 *d-- = '0';
293 }
294 break;
295 default:
296 if (*p == ',') {
297 if (s >= src) {
298 if (*s == '-') {
299 *d-- = *s--;
300 } else {
301 *d-- = *p;
302 }
303 } else {
304 *d-- = ' ';
305 }
306 } else {
307 *d-- = *p;
308 }
309 }
310 p--;
311 }
312 }
313}
314
315/*
316 * format: map number-overflow to format
317 */
318void fmt_omap(char *dest, const char *fmt) {
319 char *p = (char *) fmt;
320 char *d = dest;
321
322 while (*p) {
323 switch (*p) {
324 case '#':
325 case '0':
326 case '^':
327 *d++ = '*';
328 break;
329 default:
330 *d++ = *p;
331 }
332
333 p++;
334 }
335 *d = '\0';
336}
337
338/*
339 * format: count digits
340 */
341int fmt_cdig(char *fmt) {
342 char *p = fmt;
343 int count = 0;
344
345 while (*p) {
346 switch (*p) {
347 case '#':
348 case '0':
349 case '^':
350 count++;
351 break;
352 }
353 p++;
354 }
355
356 return count;
357}
358
359/*
360 * format: format a number
361 *
362 * symbols:
363 * # = digit or space
364 * 0 = digit or zero
365 * ^ = exponential digit/format
366 * . = decimal point
367 * , = thousands
368 * - = minus for negative
369 * + = sign of number
370 */
371char *format_num(const char *fmt_cnst, var_num_t x) {
372 char *p, *fmt;
373 char left[64], right[64];
374 char lbuf[64] ;
375 int lc = 0, sign = 0;
376
377 char *dest = malloc(128);
378
379 // backup of format
380 fmt = malloc(strlen(fmt_cnst) + 1);
381 strcpy(fmt, fmt_cnst);
382
383 // check sign
384 if (strchr(fmt, '-') || strchr(fmt, '+')) {
385 sign = 1;
386 if (x < 0.0) {
387 sign = -1;
388 x = -x;
389 }
390 }
391
392 if (strchr(fmt_cnst, '^')) {
393 //
394 // E format
395 //
396 lc = fmt_cdig(fmt);
397 if (lc < 4) {
398 fmt_omap(dest, fmt);
399 free(fmt);
400 return dest;
401 }
402
403 // convert
404 expfta(x, dest);
405
406 // format
407 p = strchr(dest, 'E');
408 if (p) {
409 *p = '\0';
410 strlcpy(left, dest, sizeof(left));
411 strlcpy(right, p + 1, sizeof(right));
412 int lsz = strlen(left);
413 int rsz = strlen(right) + 1;
414
415 if (lc < rsz + 1) {
416 fmt_omap(dest, fmt);
417 free(fmt);
418 return dest;
419 }
420
421 if (lc < lsz + rsz + 1) {
422 left[lc - rsz] = '\0';
423 }
424 strlcpy(lbuf, left, sizeof(lbuf));
425 strlcat(lbuf, "E", sizeof(lbuf));
426 strlcat(lbuf, right, sizeof(lbuf));
427 fmt_nmap(-1, dest, fmt, lbuf);
428 } else {
429 strlcpy(left, dest, sizeof(left));
430 fmt_nmap(-1, dest, fmt, left);
431 }
432 } else {
433 //
434 // normal format
435 //
436
437 // rounding
438 p = strchr(fmt, '.');
439 if (p) {
440 x = fround(x, fmt_cdig(p + 1));
441 } else {
442 x = fround(x, 0);
443 }
444
445 // convert
446 bestfta(x, dest);
447 if (strchr(dest, 'E')) {
448 fmt_omap(dest, fmt);
449 free(fmt);
450 return dest;
451 }
452
453 // left & right parts
454 left[0] = right[0] = '\0';
455 p = strchr(dest, '.');
456 if (p) {
457 *p = '\0';
458 strlcpy(right, p + 1, sizeof(right));
459 }
460 strlcpy(left, dest, sizeof(left));
461
462 // map format
463 char rbuf[64];
464 int dp = 0;
465 rbuf[0] = lbuf[0] = '\0';
466 p = strchr(fmt, '.');
467 if (p) {
468 dp = 1;
469 *p = '\0';
470 fmt_nmap(1, rbuf, p + 1, right);
471 }
472
473 lc = fmt_cdig(fmt);
474 if (lc < strlen(left)) {
475 fmt_omap(dest, fmt_cnst);
476 free(fmt);
477 return dest;
478 }
479 fmt_nmap(-1, lbuf, fmt, left);
480
481 strcpy(dest, lbuf);
482 if (dp) {
483 strcat(dest, ".");
484 strcat(dest, rbuf);
485 }
486 }
487
488 // sign in format
489 if (sign) { // 24/6 Snoopy42 modifications
490 char *e;
491
492 e = strchr(dest, 'E');
493 if (e) { // special treatment for E format
494 p = strchr(dest, '+');
495 if (p && p < e) { // the sign bust be before the E
496 *p = (sign > 0) ? '+' : '-';
497 }
498 p = strchr(dest, '-');
499 if (p && p < e) {
500 *p = (sign > 0) ? ' ' : '-';
501 }
502 } else { // no E format
503 p = strchr(dest, '+');
504 if (p) {
505 *p = (sign > 0) ? '+' : '-';
506 }
507 p = strchr(dest, '-');
508 if (p) {
509 *p = (sign > 0) ? ' ' : '-';
510 }
511 }
512 }
513
514 // cleanup
515 free(fmt);
516 return dest;
517}
518
519/*
520 * format: format a string
521 *
522 * symbols:
523 * & the whole string
524 * ! the first char
525 * \\ segment
526 */
527char *format_str(const char *fmt_cnst, const char *str) {
528 if (strchr(fmt_cnst, '&')) {
529 int size = strlen(str) + 1;
530 char *dest = malloc(size);
531 strcpy(dest, str);
532 return dest;
533 }
534 if (strchr(fmt_cnst, '!')) {
535 char *dest = malloc(2);
536 dest[0] = str[0];
537 dest[1] = '\0';
538 return dest;
539 }
540
541 // segment
542 int ps = 0;
543 int pe = 0;
544 int lc = 0;
545 int count = 0;
546 const int fmtlen = strlen(fmt_cnst);
547 const int srclen = strlen(str);
548 char *p = (char *)fmt_cnst;
549 char *ss = NULL;
550 while (*p) {
551 if (*p == '\\' && lc != '_') {
552 if (count == 0) {
553 ss = p;
554 ps = (int) (p - fmt_cnst);
555 count++;
556 } else {
557 pe = p - fmt_cnst;
558 count++;
559 break;
560 }
561 } else if (count) {
562 count++;
563 }
564 lc = *p;
565 p++;
566 }
567
568 char *dest = malloc(fmtlen + 1);
569 memset(dest, ' ', fmtlen - 1);
570 dest[fmtlen] = '\0';
571 char *d = dest;
572 if (ps) {
573 memcpy(d, fmt_cnst, ps);
574 d += ps;
575 }
576
577 // convert
578 if (ss) {
579 int i, j;
580 for (i = j = 0; i < count; i++) {
581 switch (ss[i]) {
582 case '\\':
583 case ' ':
584 if (j < srclen) {
585 d[i] = str[j];
586 j++;
587 } else {
588 d[i] = ' ';
589 }
590 break;
591 default:
592 d[i] = ss[i];
593 }
594 }
595 }
596
597 d += count;
598 *d = '\0';
599 if (*(fmt_cnst + pe + 1) != '\0') {
600 strcat(dest, fmt_cnst + pe + 1);
601 }
602 return dest;
603}
604
605/*
606 * get numeric format
607 */
608char *fmt_getnumfmt(char *dest, char *source) {
609 int dp = 0, sign = 0, exitf = 0;
610 char *p = source;
611 char *d = dest;
612
613 while (*p) {
614 switch (*p) {
615 case '^':
616 case '#':
617 case '0':
618 case ',':
619 *d++ = *p;
620 break;
621 case '-':
622 case '+':
623 sign++;
624 if (sign > 1)
625 exitf = 1;
626 else
627 *d++ = *p;
628 break;
629 case '.':
630 dp++;
631 if (dp > 1) {
632 exitf = 1;
633 } else {
634 *d++ = *p;
635 }
636 break;
637 default:
638 exitf = 1;
639 }
640
641 if (exitf) {
642 break;
643 }
644 p++;
645 }
646
647 *d = '\0';
648 return p;
649}
650
651/*
652 * get string format
653 */
654char *fmt_getstrfmt(char *dest, char *source) {
655 char *p = source;
656 char *d = dest;
657
658 if (source[0] == '&' || source[0] == '!') {
659 *d++ = *source;
660 *d++ = '\0';
661 return p + 1;
662 }
663
664 while (*p) {
665 *d++ = *p++;
666 if (*p == '\\') {
667 *d++ = *p++;
668 break;
669 }
670 }
671
672 *d = '\0';
673 return p;
674}
675
676/*
677 * add format node
678 */
679void fmt_addfmt(const char *fmt, int type) {
680 fmt_node_t *node = &fmt_stack[fmt_count];
681 fmt_count++;
682 if (fmt_count >= MAX_FMT_N) {
683 panic("Maximum format-node reached");
684 }
685 node->fmt = malloc(strlen(fmt) + 1);
686 strcpy(node->fmt, fmt);
687 node->type = type;
688}
689
690/*
691 * cleanup format-list
692 */
693void free_format() {
694 for (int i = 0; i < fmt_count; i++) {
695 fmt_node_t *node = &fmt_stack[i];
696 free(node->fmt);
697 }
698
699 fmt_count = fmt_cur = 0;
700}
701
702/*
703 * The final format - create the format-list
704 * (that list it will be used later by fmt_printN and fmt_printS)
705 *
706 * '_' the next character is not belongs to format (simple string)
707 */
708void build_format(const char *fmt_cnst) {
709 char buf[1024];
710
711 free_format();
712
713 // backup of format
714 char *fmt = malloc(strlen(fmt_cnst) + 1);
715 strcpy(fmt, fmt_cnst);
716
717 char *p = fmt;
718 char *b = buf;
719 int nc = 0;
720 while (*p) {
721 switch (*p) {
722 case '_':
723 // store prev. buf
724 *b = '\0';
725 if (strlen(buf)) {
726 fmt_addfmt(buf, 0);
727 }
728 // store the new
729 buf[0] = *(p + 1);
730 buf[1] = '\0';
731 fmt_addfmt(buf, 0);
732 b = buf;
733 p++;
734 break;
735 case '-':
736 case '+':
737 case '^':
738 case '0':
739 case '#':
740 // store prev. buf
741 *b = '\0';
742 if (strlen(buf)) {
743 fmt_addfmt(buf, 0);
744 }
745 // get num-fmt
746 p = fmt_getnumfmt(buf, p);
747 fmt_addfmt(buf, 1);
748 b = buf;
749 nc = 1;
750 break;
751 case '&':
752 case '!':
753 case '\\':
754 // store prev. buf
755 *b = '\0';
756 if (strlen(buf)) {
757 fmt_addfmt(buf, 0);
758 }
759 // get str-fmt
760 p = fmt_getstrfmt(buf, p);
761 fmt_addfmt(buf, 2);
762 b = buf;
763 nc = 1;
764 break;
765 default:
766 *b++ = *p;
767 }
768
769 if (*p) {
770 if (nc) { // do not advance
771 nc = 0;
772 } else {
773 p++;
774 }
775 }
776 }
777
778 // store prev. buf
779 *b = '\0';
780 if (strlen(buf)) {
781 fmt_addfmt(buf, 0);
782 }
783 // cleanup
784 free(fmt);
785}
786
787/*
788 * print simple strings (parts of format)
789 */
790void fmt_printL(int output, intptr_t handle) {
791 if (fmt_count == 0) {
792 return;
793 } else {
794 fmt_node_t *node;
795 do {
796 node = &fmt_stack[fmt_cur];
797 if (node->type == 0) {
798 pv_write(node->fmt, output, handle);
799 fmt_cur++;
800 if (fmt_cur >= fmt_count) {
801 fmt_cur = 0;
802 }
803 }
804 } while (node->type == 0 && fmt_cur != 0);
805 }
806}
807
808/*
809 * print formated number
810 */
811void fmt_printN(var_num_t x, int output, intptr_t handle) {
812 if (fmt_count == 0) {
813 rt_raise(ERR_FORMAT_INVALID_FORMAT);
814 } else {
815 fmt_printL(output, handle);
816 fmt_node_t *node = &fmt_stack[fmt_cur];
817 fmt_cur++;
818 if (fmt_cur >= fmt_count) {
819 fmt_cur = 0;
820 }
821 if (node->type == 1) {
822 char *buf = format_num(node->fmt, x);
823 pv_write(buf, output, handle);
824 free(buf);
825 if (fmt_cur != 0) {
826 fmt_printL(output, handle);
827 }
828 } else {
829 rt_raise(ERR_FORMAT_INVALID_FORMAT);
830 }
831 }
832}
833
834/*
835 * print formated string
836 */
837void fmt_printS(const char *str, int output, intptr_t handle) {
838 if (fmt_count == 0) {
839 rt_raise(ERR_FORMAT_INVALID_FORMAT);
840 } else {
841 fmt_printL(output, handle);
842 fmt_node_t *node = &fmt_stack[fmt_cur];
843 fmt_cur++;
844 if (fmt_cur >= fmt_count) {
845 fmt_cur = 0;
846 }
847 if (node->type == 2) {
848 char *buf = format_str(node->fmt, str);
849 pv_write(buf, output, handle);
850 free(buf);
851 if (fmt_cur != 0) {
852 fmt_printL(output, handle);
853 }
854 } else {
855 rt_raise(ERR_FORMAT_INVALID_FORMAT);
856 }
857 }
858}
859
860